package seller

import (
	"Wine-Server/utils"
	"Wine-Server/utils/tables"
	"encoding/json"
	"errors"
	"github.com/gin-gonic/gin"
	"github.com/gorilla/websocket"
	"github.com/wechatpay-apiv3/wechatpay-go/core/auth/verifiers"
	"github.com/wechatpay-apiv3/wechatpay-go/core/downloader"
	"github.com/wechatpay-apiv3/wechatpay-go/core/notify"
)

func VersionHandler(ctx *gin.Context) {
	version := tables.VersionTable{}
	err := version.GetLatest()
	if err != nil {
		ctx.JSON(utils.HttpError, utils.Fail("Get latest version failed"))
		return
	}
	ctx.JSON(utils.HttpOk, utils.Success(version))
}

func updateWineInfo(trade *tradeRedis) (uint16, error) {
	wine := tables.WineTable{Id: trade.Wine}
	err := wine.Get()
	if err != nil {
		utils.Logger.Println("wine get error:", err)
		return 0, err
	}
	wine.Income += uint64(trade.Cash)
	wine.Order++
	err = wine.UpdateSelf()
	if err != nil {
		utils.Logger.Println("wine update error:", err)
		return 0, err
	}
	return uint16(50*float64(trade.Weight)/float64(wine.Density)/1000 + 0.5), nil
}

func updateDeviceInfo(trade *tradeRedis, volume uint16) (string, error) {
	table := tables.DeviceTable{Id: trade.Device}
	err := table.Get()
	if err != nil {
		utils.Logger.Println("device get error:", err)
		return "", err
	}
	table.Order++
	table.Income += uint64(trade.Cash)
	table.Wines[trade.Cell].Remain -= volume
	err = table.UpdateSelf()
	if err != nil {
		utils.Logger.Println("device update error:", err)
		return "", err
	}

	return table.Manager, nil
}

func updateManagerInfo(mid string, trade *tradeRedis) error {
	if mid == "" {
		utils.Logger.Println("blank manager id")
		return errors.New("blank manager id")
	}
	table := tables.ManagerTable{Id: mid}
	err := table.Get()
	if err != nil {
		utils.Logger.Println("manager get error:", err)
		return err
	}
	table.Order++
	table.Income += uint64(trade.Cash)
	err = table.UpdateSelf()
	if err != nil {
		utils.Logger.Println("manager update error:", err)
		return err
	}
	return nil
}

func WxPayHandler(ctx *gin.Context) {
	err := downloader.MgrInstance().RegisterDownloaderWithPrivateKey(
		utils.WxPayCli, utils.WxPrivateKey, utils.WxCertSeq, utils.WxMchId, utils.WxV3Key,
	)
	visitor := downloader.MgrInstance().GetCertificateVisitor(utils.WxMchId)
	handler, _ := notify.NewRSANotifyHandler(utils.WxV3Key, verifiers.NewSHA256WithRSAVerifier(visitor))
	var wxNotify notifyParam
	_, err = handler.ParseNotifyRequest(utils.WxPayCli, ctx.Request, &wxNotify)
	if wxNotify.TradeState != "SUCCESS" {
		utils.Logger.Printf("trade[%s] status[%s]\n", wxNotify.TradeNo, wxNotify.TradeState)
		ctx.JSON(utils.HttpOk, utils.Success(nil))
		return
	}
	dbKey := utils.Format("WxPay_%s", wxNotify.TradeNo)
	exist, err := utils.Redis.Exists(utils.WxPayCli, dbKey).Result()
	if exist != 1 {
		utils.Logger.Println("re-notified or expire of:", wxNotify.TradeNo)
		ctx.JSON(utils.HttpOk, utils.Success(nil))
		return
	}
	res, err := utils.Redis.Get(utils.WxPayCli, dbKey).Result()
	var dbTrade tradeRedis
	err = json.Unmarshal([]byte(res), &dbTrade)
	utils.Redis.Del(utils.WxPayCli, dbKey)

	volume, err := updateWineInfo(&dbTrade)
	manager, err := updateDeviceInfo(&dbTrade, volume)
	err = updateManagerInfo(manager, &dbTrade)
	wine := tables.WineTable{Id: dbTrade.Wine}
	_ = wine.Get()

	table := tables.TradeTable{
		Id: wxNotify.TransactionId, Trade: wxNotify.TradeNo, Device: dbTrade.Device, Payer: wxNotify.Payer.Openid,
		Wine: dbTrade.Wine, Price: wine.Price, Weight: dbTrade.Weight, Cash: dbTrade.Cash, Manager: manager,
	}
	err = table.Insert()
	if err != nil {
		ctx.JSON(utils.HttpError, utils.Fail("server error"))
		return
	}
	if device, din := utils.SellerDevices[dbTrade.Device]; din {
		if device.Online {
			_ = device.Conn.WriteJSON(utils.WsEvent("qrcodeScanned", nil))
			utils.Sleep(1)
			_ = device.Conn.WriteJSON(utils.WsEvent("orderPayed", volume))

			ctx.JSON(utils.HttpOk, utils.Success(nil))
		}
	}
	ctx.JSON(utils.HttpError, utils.Fail("server error"))
}

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

	var advList []tables.AdvertiseTable
	advList, err = tables.AdvertiseListAll()
	if err != nil {
		utils.Logger.Println("Query advertise failed:", err)
		_ = conn.WriteJSON(utils.WsError("Query advertise failed"))
		return
	}
	_ = conn.WriteJSON(utils.WsEvent("advResult", advList))

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

func keepAlive(conn *websocket.Conn, data any) {
	_ = conn.WriteJSON(utils.WsEvent("pon", data))
}

func getQrcode(device *tables.DeviceTable, conn *websocket.Conn, data any) {
	var param qrcodeParam
	err := utils.AnyTrans(data, &param)
	if err != nil {
		_ = conn.WriteJSON(utils.WsError("param error"))
		return
	}
	wine := tables.WineTable{Id: param.Id}
	err = wine.Get()
	if err != nil {
		utils.Logger.Printf("Query wine[%d] failed: %s\n", param.Id, err)
		_ = conn.WriteJSON(utils.WsError(utils.Format("no such wine: %d", param.Id)))
		return
	}
	cash := int(param.Weight) * int(wine.Price)
	if cash != param.Cash {
		utils.Logger.Printf("got a wrong cash[user: %d, need: %d]\n", param.Cash, cash)
		_ = conn.WriteJSON(utils.WsError(utils.Format("got a wrong cash[you: %d, real: %d]", param.Cash, cash)))
		return
	}
	_ = device.Get()
	trade := device.Id + utils.TimeString()
	store := tradeRedis{Device: device.Id, Cell: param.Cell, Wine: wine.Id, Weight: param.Weight, Cash: uint32(cash)}
	marshal, err := json.Marshal(store)
	if err != nil {
		utils.Logger.Println("marshal trade failed:", err)
		_ = conn.WriteJSON(utils.WsError("marshal trade failed"))
		return
	}
	err = utils.Redis.Set(utils.WxPayCli, utils.Format("WxPay_%s", trade), marshal, utils.Duration(1800)).Err()
	if err != nil {
		utils.Logger.Println("store trade failed:", err)
		_ = conn.WriteJSON(utils.WsError("store trade failed"))
		return
	}

	var img []byte
	img, err = utils.TryWxPay(trade, wine.Name, cash)
	if err != nil {
		utils.Logger.Println("access to wxpay failed:", err)
		_ = conn.WriteJSON(utils.WsError("access to Wechat Pay failed"))
		return
	}
	_ = conn.WriteJSON(utils.WsEvent("qrcodeOkayed", img))
}

func checkAuthCode(did string, conn *websocket.Conn, data any) {
	var param authParam
	err := utils.AnyTrans(data, &param)
	if err != nil {
		_ = conn.WriteJSON(utils.WsError("params error"))
		return
	}
	if param.Code != "284655" {
		_ = conn.WriteJSON(utils.WsEvent("authCodeResult", utils.JsonType{
			"type": param.Type, "ok": false,
		}))
		return
	}
	switch param.Type {
	case "Changing":
		// TODO: check redis `${did}_${param.who}_Change` === param.code, query real work
		_ = conn.WriteJSON(utils.WsEvent("authCodeResult", utils.JsonType{
			"type": param.Type, "ok": true, "work": []utils.JsonType{
				{
					"cell": 1,
					"old":  utils.JsonType{"id": 13026, "name": "某一款酒名-1", "remain": 300},
					"new":  utils.JsonType{"id": 13026, "name": "某一款酒名-1", "remain": 15000},
				},
				{
					"cell": 2,
					"old":  utils.JsonType{"id": 13027, "name": "某一款酒名-2", "remain": 286},
					"new":  utils.JsonType{"id": 13029, "name": "某一款酒名-3", "remain": 15000},
				},
				{
					"cell": 3,
					"old":  utils.JsonType{"id": 13027, "name": "某一款酒名-2", "remain": 286},
					"new":  utils.JsonType{"id": 13029, "name": "某一款酒名-3", "remain": 15000},
				},
				{
					"cell": 4,
					"old":  utils.JsonType{"id": 13027, "name": "某一款酒名-2", "remain": 286},
					"new":  utils.JsonType{"id": 13029, "name": "某一款酒名-3", "remain": 15000},
				},
			},
		}))
		break
	case "Fixing":
		// TODO: check redis `${did}_${param.who}_Fix` === param.code
		_ = conn.WriteJSON(utils.WsEvent("authCodeResult", utils.JsonType{
			"type": param.Type, "ok": true,
		}))
		break
	default:
		_ = conn.WriteJSON(utils.WsError("unrecognized open type"))
	}
}

func openResult(conn *websocket.Conn, data any) {
	var param resultParam
	err := utils.AnyTrans(data, &param)
	if err != nil {
		_ = conn.WriteJSON(utils.WsError("params error"))
		return
	}
	if worker, win := utils.WorkerWss[param.Who]; win {
		_ = worker.WriteJSON(utils.WsEvent("openResult", utils.JsonType{
			"type": param.Type, "result": param.Result,
		}))
		return
	}
	if debugger, din := utils.DebugWss[param.Who]; din {
		_ = debugger.WriteJSON(utils.WsEvent("openResult", utils.JsonType{
			"type": param.Type, "result": param.Result,
		}))
		return
	}
}

func workFinished(conn *websocket.Conn, data any) {
	var param finishParam
	err := utils.AnyTrans(data, &param)
	if err != nil {
		_ = conn.WriteJSON(utils.WsError("params error"))
		return
	}
	if worker, win := utils.WorkerWss[param.Who]; win {
		_ = worker.WriteJSON(utils.WsEvent("workFinished", param.Type))
		return
	}
	if debugger, din := utils.DebugWss[param.Who]; din {
		_ = debugger.WriteJSON(utils.WsEvent("workFinished", param.Type))
		return
	}
}
