package web

import (
	"encoding/json"
	"fmt"
	"time"

	"dyproxy/.gen/model"
	"dyproxy/.gen/table"

	. "github.com/go-jet/jet/v2/sqlite"
	"github.com/gofiber/fiber/v3"
	"github.com/sirupsen/logrus"
)

func (s *WebServer) routeGetExpertConfig(c fiber.Ctx) error {
	uid := c.Params("uid", "")
	var user model.Expert
	err := table.Expert.SELECT(table.Expert.AllColumns).WHERE(table.Expert.UID.EQ(String(uid))).QueryContext(c.UserContext(), s.db, &user)
	if err != nil {
		return err
	}

	if user.Config == "" {
		user.Config = "{}"
	}

	var config ExpertConfig
	if err := json.Unmarshal([]byte(user.Config), &config); err != nil {
		return err
	}
	c.Response().Header.Set("x-config-at", fmt.Sprintf("%d", user.ConfigAt))
	return c.JSON(config)
}

func (s *WebServer) routeGetExpert(c fiber.Ctx) error {
	uid := c.Params("uid", "")
	var user model.Expert
	err := table.Expert.SELECT(table.Expert.AllColumns).WHERE(table.Expert.UID.EQ(String(uid))).QueryContext(c.UserContext(), s.db, &user)
	if err != nil {
		return err
	}
	return c.JSON(user)
}

func (s *WebServer) routeGetExperts(c fiber.Ctx) error {
	stmt := table.Expert.SELECT(table.Expert.AllColumns).ORDER_BY(table.Expert.ID)

	var rows []model.Expert
	if err := stmt.Query(s.db, &rows); err != nil {
		return err
	}

	sql := `SELECT
    expert_uid,
    COUNT(*) AS total,
    SUM(case when followed = 1 then 1 else 0 end) AS followed
FROM
    follower
GROUP BY
    expert_uid;`
	r, err := s.db.QueryContext(c.UserContext(), sql)
	if err != nil {
		return err
	}

	type statistics struct {
		UserUID  string
		Total    int32
		Followed int32
	}

	var statisticsItems []statistics
	for r.Next() {
		item := statistics{}
		if err := r.Scan(&item.UserUID, &item.Total, &item.Followed); err != nil {
			return err
		}

		statisticsItems = append(statisticsItems, item)
	}

	type resp struct {
		Focus        int32
		Total        int32
		Conf         ExpertConfig
		model.Expert `json:",inline"`
	}
	var users []resp

	for _, row := range rows {
		stat := statistics{}
		// get item from statisticsItems where row.UID == item.UserUID
		for _, item := range statisticsItems {
			if row.UID == item.UserUID {
				stat = item
				break
			}
		}

		var conf ExpertConfig
		if row.Config == "" {
			row.Config = "{}"
		}
		if err := json.Unmarshal([]byte(row.Config), &conf); err != nil {
			logrus.Error(err)
			continue
		}

		row.Config = ""
		users = append(users, resp{
			Focus:  stat.Followed,
			Total:  stat.Total,
			Conf:   conf,
			Expert: row,
		})
	}

	return c.JSON(users)
}

func (s *WebServer) routePostExperts(c fiber.Ctx) error {
	expert := &model.Expert{}
	if err := c.Bind().JSON(expert); err != nil {
		return err
	}

	tbl := table.Expert
	_, err := tbl.
		INSERT(tbl.AllColumns.Except(tbl.ID)).
		MODEL(expert).
		ON_CONFLICT(tbl.UID).
		DO_NOTHING().
		ExecContext(c.UserContext(), s.db)

	return err
}

func (s *WebServer) routePatchExpertConfig(c fiber.Ctx) error {
	var data struct {
		Since        int32 `json:"since"`
		ExpertConfig `json:",inline"`
	}
	if err := c.Bind().JSON(&data); err != nil {
		return err
	}

	uid := c.Params("uid")
	tbl := table.Expert

	// get expert by uid
	var expert model.Expert
	if err := tbl.SELECT(tbl.AllColumns).WHERE(tbl.UID.EQ(String(uid))).QueryContext(c.UserContext(), s.db, &expert); err != nil {
		return err
	}
	if expert.Config == "" {
		expert.Config = "{}"
	}

	newExpertConfig, err := json.Marshal(data.ExpertConfig)
	if err != nil {
		return err
	}

	_, err = tbl.
		UPDATE().
		SET(
			tbl.Config.SET(String(string(newExpertConfig))),
			tbl.ConfigAt.SET(Int(time.Now().Unix())),
		).
		WHERE(tbl.UID.EQ(String(uid))).
		ExecContext(c.UserContext(), s.db)

	return err
}

const (
	StateNormal = ""
	StateStop   = "stop"
)

func (s *WebServer) routePatchExpertState(c fiber.Ctx) error {
	var state struct {
		State string `json:"state"`
	}
	if err := c.Bind().JSON(&state); err != nil {
		return err
	}

	uid := c.Params("uid")

	tbl := table.Expert
	_, err := tbl.
		UPDATE().
		SET(tbl.State.SET(String(state.State))).
		WHERE(tbl.UID.EQ(String(uid))).
		ExecContext(c.UserContext(), s.db)

	return err
}
