package test

import (
	"Wine-Server/utils"
	"Wine-Server/utils/tables"
	"database/sql"
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/gorilla/websocket"
	"sync"
)

var upgrader = websocket.Upgrader{
	ReadBufferSize:  1024,
	WriteBufferSize: 1024,
	CheckOrigin:     utils.CheckOrigin,
}

var (
	devices    = make(map[string]*websocket.Conn)
	deviceLock sync.Mutex
	workers    = make(map[string]*websocket.Conn)
	workerLock sync.Mutex
)

func deviceAdd(seq string, conn *websocket.Conn) *tables.DeviceTable {
	device := tables.DeviceTable{Id: seq}
	err := device.Get()
	if err == sql.ErrNoRows { // insert new device
		err = device.Insert()
		if err != nil {
			_ = conn.WriteJSON(utils.WsError("New device join failed"))
			return nil
		}
		_ = device.Get()
	} else { // update last time
		device.Last = utils.TimeNow()
		err = device.Update(utils.JsonType{"last": device.Last})
		if err != nil {
			_ = conn.WriteJSON(utils.WsError("Update last active time failed"))
			return nil
		}
	}

	deviceLock.Lock()
	devices[seq] = conn
	deviceLock.Unlock()
	fmt.Printf("device connected: %s\n", seq)
	return &device
}

func deviceDel(seq string) {
	deviceLock.Lock()
	delete(devices, seq)
	deviceLock.Unlock()
	fmt.Printf("device[%s] disconnected\n", seq)
}

type Message struct {
	Event string `json:"event"`
	Data  any    `json:"data"`
}

func WsHandler(c *gin.Context) {
	conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
	if err != nil {
		c.JSON(utils.HttpError, utils.Fail("can't establish socket connect"))
		return
	}
	seq := c.DefaultQuery("seq", "")
	if seq == "" {
		_ = conn.WriteJSON(utils.WsError("device seq is required"))
		return
	}

	device := deviceAdd(seq, conn)
	if device == nil {
		return
	}
	for {
		var msg Message
		err = conn.ReadJSON(&msg)
		if err != nil {
			deviceDel(seq)
			return
		}
		switch msg.Event {
		case "getInfo":
			infoOfDevice(device, conn, msg.Data)
			break
		case "setLocation":
			updateLocation(device, conn, msg.Data)
			break
		case "getQrcode":
			break
		case "openResult":
			break
		default:
			_ = conn.WriteJSON(utils.WsError("unrecognized event"))
			break
		}
	}
}

func infoOfDevice(device *tables.DeviceTable, conn *websocket.Conn, data any) {
	var err error
	for i := 0; i < 4; i++ {
		err = device.Wines[i].Get()
		if err != nil {
			_ = conn.WriteJSON(utils.WsError("Query wine info failed"))
			return
		}
	}
	var advList []tables.AdvertiseTable
	advList, err = tables.AdvListAll()
	if err != nil {
		_ = conn.WriteJSON(utils.WsError("Query advertise failed"))
		return
	}

	var runParams []tables.ParamsTable
	runParams, err = tables.ParamsListAll()
	if err != nil {
		_ = conn.WriteJSON(utils.WsError("Query running params failed"))
		return
	}

	_ = conn.WriteJSON(utils.WsEvent("infoResult", utils.JsonType{
		"wines":     device.Wines,
		"position":  device.Addr,
		"params":    runParams,
		"advertise": advList,
	}))
}

func updateLocation(device *tables.DeviceTable, conn *websocket.Conn, data any) {
	if data == "" {
		_ = conn.WriteJSON(utils.WsError("Blank address is not allowed"))
		return
	}
	if device.Addr != data {
		err := device.Update(utils.JsonType{"addr": data})
		if err != nil {
			_ = conn.WriteJSON(utils.WsError("Update location failed"))
			return
		}
		_ = conn.WriteJSON(utils.WsEvent("locationResult", data))
		return
	}
	_ = conn.WriteJSON(utils.WsError("same location"))
}

type openParam struct {
	Seq  string `json:"seq"`
	Uid  string `json:"uid"`
	Type string `json:"type"`
}

func SendHandler(c *gin.Context) {
	conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
	if err != nil {
		c.JSON(utils.HttpError, utils.Fail("can't establish socket connect"))
		return
	}
	var param openParam
	err = c.ShouldBind(&param)
	if err != nil {
		_ = conn.WriteJSON(utils.WsError("param error"))
		return
	}
	if param.Seq == "" || param.Uid == "" || param.Type == "" {
		_ = conn.WriteJSON(utils.WsError("seq, uid, type is required"))
		return
	}
	if device, ok := devices[param.Seq]; ok {
		_ = device.WriteJSON(utils.WsEvent("openGate", param.Type))
	} else {
		_ = conn.WriteJSON(utils.WsError("no such device or device offline"))
	}

	workerLock.Lock()
	workers[param.Uid] = conn
	workerLock.Unlock()
	for {
		var msg Message
		err = conn.ReadJSON(&msg)
		if err != nil {
			workerLock.Lock()
			delete(workers, param.Uid)
			workerLock.Unlock()
			return
		}
	}
}
