package web

import (
	"errors"
	"fmt"
	"strconv"
	"time"

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

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

type ExpertConfig struct {
	Voice         string
	Hello         string
	Wechat        string
	Region        []string
	NameKeyword   []string
	Produce       bool
	DefaultName   bool
	DefaultAvatar bool
}

func (s *WebServer) routeGetDevices(c fiber.Ctx) error {
	devices := []model.Device{}
	stmt := table.Device.SELECT(table.Device.AllColumns).ORDER_BY(table.Device.ID)
	if err := stmt.QueryContext(c.UserContext(), s.db, &devices); err != nil {
		return err
	}

	expertsIds := lo.FilterMap(devices, func(device model.Device, _ int) (Expression, bool) {
		if device.Expert == "" {
			return nil, false
		}

		return String(device.Expert), true
	})

	if len(expertsIds) == 0 {
		return c.JSON(devices)
	}

	type listDevice struct {
		model.Device `json:",inline"`
		ExpertName   string
	}

	// find experts by ids
	experts := []model.Expert{}
	stmt = table.Expert.SELECT(table.Expert.AllColumns).WHERE(table.Expert.UID.IN(expertsIds...))
	if err := stmt.QueryContext(c.UserContext(), s.db, &experts); err != nil {
		return err
	}

	expertsMap := make(map[string]string)
	for _, expert := range experts {
		expertsMap[expert.UID] = expert.RealName
	}

	list := make([]listDevice, 0, len(devices))
	for _, device := range devices {
		if expertName, ok := expertsMap[device.Expert]; ok {
			list = append(list, listDevice{
				Device:     device,
				ExpertName: expertName,
			})
		} else {
			list = append(list, listDevice{
				Device:     device,
				ExpertName: "未设置",
			})
		}
	}
	return c.JSON(list)
}

func (s *WebServer) routeGetDevice(c fiber.Ctx) error {
	deviceID := c.Params("uuid")
	var device model.Device
	err := table.Device.SELECT(table.Device.AllColumns).WHERE(table.Device.UUID.EQ(String(deviceID))).QueryContext(c.UserContext(), s.db, &device)
	if err != nil {
		if errors.Is(err, qrm.ErrNoRows) {
			// create new device
			device.UUID = deviceID
			_, err = table.Device.INSERT(table.Device.AllColumns.Except(table.Device.ID)).MODEL(device).ExecContext(c.UserContext(), s.db)
			if err != nil {
				return err
			}
			return c.JSON(nil)
		}
		return err
	}

	return c.JSON(device)
}

func (s *WebServer) routeGetDeviceFollower(c fiber.Ctx) error {
	if s.pendingItems == nil {
		s.pendingItems = make(map[int32]time.Time)
	}

	// get device
	deviceID := c.Params("uuid")
	var device model.Device
	err := table.Device.SELECT(table.Device.AllColumns).WHERE(table.Device.UUID.EQ(String(deviceID))).QueryContext(c.UserContext(), s.db, &device)
	if err != nil {
		if errors.Is(err, qrm.ErrNoRows) {
			// create new device
			device.UUID = deviceID
			_, err = table.Device.INSERT(table.Device.AllColumns.Except(table.Device.ID)).MODEL(device).ExecContext(c.UserContext(), s.db)
			if err != nil {
				return err
			}
			return c.JSON(nil)
		}
		return err
	}

	if device.Expert == "" {
		return c.JSON(nil)
	}

	if device.State == StateStop {
		return c.JSON(nil)
	}

	tbl := table.Follower

	lastID := c.Query("last", "")
	if lastID != "" {
		id, err := strconv.Atoi(lastID)
		if err != nil {
			return err
		}

		// remove from pending
		s.listLock.Lock()
		delete(s.pendingItems, int32(id))
		s.listLock.Unlock()

		tbl.
			UPDATE(table.Follower.Followed).
			SET(Int32(1)).
			WHERE(table.Follower.ID.EQ(Int32(int32(id)))).
			ExecContext(c.UserContext(), s.db)
	}

	pendingIDs := []Expression{}
	for i := range s.pendingItems {
		pendingIDs = append(pendingIDs, Int32(i))
	}

	pendingIDs = []Expression{}

	// get device expert

	var expert model.Expert
	err = table.Expert.SELECT(table.Expert.AllColumns).WHERE(table.Expert.UID.EQ(String(device.Expert))).QueryContext(c.UserContext(), s.db, &expert)
	if err != nil {
		return err
	}
	c.Response().Header.Set("x-expert-uid", expert.UID)
	c.Response().Header.Set("x-config-at", fmt.Sprintf("%d", expert.ConfigAt))

	if expert.State == StateStop {
		return c.JSON(nil)
	}

	condition := tbl.Followed.EQ(Int32(0)).
		AND(tbl.ExpertUID.EQ(String(device.Expert))).
		AND(tbl.ID.NOT_IN(pendingIDs...)).
		AND(tbl.CreatedAt.GT(Int32(expert.Since)))

	stmt := tbl.
		SELECT(tbl.AllColumns).
		WHERE(condition).
		ORDER_BY(table.Follower.ID.DESC()).
		LIMIT(1)
	logrus.Debug(stmt.DebugSql())

	var follower model.Follower
	if err := stmt.QueryContext(c.UserContext(), s.db, &follower); err != nil {
		if errors.Is(err, qrm.ErrNoRows) {
			return c.JSON(nil)
		}
		return err
	}

	s.listLock.Lock()
	s.pendingItems[int32(follower.ID)] = time.Now()
	s.listLock.Unlock()

	return c.JSON(follower)
}

// routeSetDeviceExpert
func (s *WebServer) routeSetDeviceExpert(c fiber.Ctx) error {
	deviceID := c.Params("uuid")
	uid := c.Params("uid")

	type body struct {
		Name string `json:"name"`
	}

	b := &body{}
	if err := c.Bind().JSON(b); err != nil {
		return err
	}

	var device model.Device
	err := table.Device.SELECT(table.Device.AllColumns).WHERE(table.Device.UUID.EQ(String(deviceID))).QueryContext(c.UserContext(), s.db, &device)
	if err != nil {
		return err
	}

	var expert model.Expert
	err = table.Expert.SELECT(table.Expert.UID).WHERE(table.Expert.UID.EQ(String(uid))).QueryContext(c.UserContext(), s.db, &expert)
	if err != nil {
		return err
	}

	device.Expert = expert.UID
	_, err = table.Device.UPDATE().SET(
		table.Device.Expert.SET(String(device.Expert)),
		table.Device.Name.SET(String(b.Name)),
	).WHERE(table.Device.UUID.EQ(String(deviceID))).ExecContext(c.UserContext(), s.db)
	if err != nil {
		return err
	}

	return c.JSON(nil)
}

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

	uuid := c.Params("uuid")

	tbl := table.Device
	_, err := tbl.
		UPDATE().
		SET(
			tbl.State.SET(String(state.State)),
			tbl.Note.SET(String(state.Note)),
		).
		WHERE(tbl.UUID.EQ(String(uuid))).
		ExecContext(c.UserContext(), s.db)

	return err
}
