package quote

import (
	"encoding/json"
	"mtp2_if/global/app"
	"mtp2_if/global/e"
	"mtp2_if/logger"
	"mtp2_if/models"
	"mtp2_if/utils"
	"net/http"
	"sort"
	"time"

	"github.com/gin-gonic/gin"
)

// HistoryData 历史数据
type HistoryData struct {
	Opened        float64   `json:"o"`  // 开盘价
	Highest       float64   `json:"h"`  // 最高价
	Lowest        float64   `json:"l"`  // 最低价
	Closed        float64   `json:"c"`  // 收盘价
	TotleVolume   int       `json:"tv"` // 总量
	TotleTurnover float64   `json:"tt"` // 总金额
	HoldVolume    int       `json:"hv"` // 持仓量
	Settle        float64   `json:"s"`  // 结算价，日线周期（包括）以上才有
	TimeStamp     time.Time `json:"ts"` // 时间
}

// QueryHistoryDatasReq 查询行情历史数据请求参数
type QueryHistoryDatasReq struct {
	CycleType int    `form:"cycleType" binding:"required"`
	GoodsCode string `form:"goodsCode"  binding:"required"`
	StartTime string `form:"startTime"`
	EndTime   string `form:"endTime"`
	Count     int    `form:"count"`
	IsAsc     bool   `form:"isAsc"`
}

// QueryHistoryDatas 查询行情历史数据
// @Summary 查询行情历史数据
// @Produce json
// @Security ApiKeyAuth
// @Param cycleType query int true "周期类型, 0-秒 1: 1分钟 2: 5分钟 3: 30分钟 4: 60分钟 120: 2小时 240: 4小时 11: 日线 10: Tik"
// @Param goodsCode query string true "商品代码"
// @Param startTime query string false "开始时间，格式：yyyy-MM-dd HH:mm:ss"
// @Param endTime query string false "结束时间，格式：yyyy-MM-dd HH:mm:ss"
// @Param count query int false "条数"
// @Param isAsc query bool false "是否按时间顺序排序（默认为时间倒序排序）"
// @Success 200 {object} HistoryData
// @Failure 500 {object} app.Response
// @Router /Quote/QueryHistoryDatas [get]
// @Tags 行情服务
func QueryHistoryDatas(c *gin.Context) {
	appG := app.Gin{C: c}

	// 获取请求参数
	var req QueryHistoryDatasReq
	if err := appG.C.ShouldBindQuery(&req); err != nil {
		logger.GetLogger().Errorf("QueryHistoryDatas failed: %s", err.Error())
		appG.Response(http.StatusBadRequest, e.INVALID_PARAMS, nil)
		return
	}

	// 转换时间
	timeFormat := "2006-01-02 15:04:05" // go中的时间格式化必须是这个时间
	var startTime *time.Time
	if len(req.StartTime) > 0 {
		st, err := time.ParseInLocation(timeFormat, req.StartTime, time.Local)
		if err != nil {
			logger.GetLogger().Errorf("QueryHistoryDatas failed: %s", err.Error())
			appG.Response(http.StatusBadRequest, e.ERROR_QUERY_TIME_FORMAT_FAIL, nil)
			return
		}
		startTime = &st
	}
	var endTime *time.Time
	if len(req.EndTime) > 0 {
		et, err := time.ParseInLocation(timeFormat, req.EndTime, time.Local)
		if err != nil {
			logger.GetLogger().Errorf("QueryHistoryDatas failed: %s", err.Error())
			appG.Response(http.StatusBadRequest, e.ERROR_QUERY_TIME_FORMAT_FAIL, nil)
			return
		}
		endTime = &et
	}

	// 查询数据
	cycleDatas, err := models.GetHistoryCycleDatas(models.CycleType(req.CycleType), req.GoodsCode, startTime, endTime, req.Count, req.IsAsc)
	if err != nil {
		logger.GetLogger().Errorf("QueryTSData failed: %s", err.Error())
		appG.Response(http.StatusBadRequest, e.ERROR_QUERY_FAIL, nil)
		return
	}

	// 获取目标商品信息
	goods, err := models.GetGoodsByGoodsCode(req.GoodsCode)
	if err != nil {
		logger.GetLogger().Errorf("QueryTSData failed: %s", err.Error())
		appG.Response(http.StatusBadRequest, e.ERROR_QUERY_FAIL, nil)
		return
	}

	// 计算最终价格
	rst := make([]HistoryData, 0)
	for _, v := range cycleDatas {
		historyData := HistoryData{
			Opened:        utils.IntToFloat64(v.Open, int(goods.Decimalplace)),
			Highest:       utils.IntToFloat64(v.High, int(goods.Decimalplace)),
			Lowest:        utils.IntToFloat64(v.Low, int(goods.Decimalplace)),
			Closed:        utils.IntToFloat64(v.Close, int(goods.Decimalplace)),
			TotleVolume:   v.TV,
			TotleTurnover: float64(v.TT),
			HoldVolume:    v.HV,
			Settle:        utils.IntToFloat64(v.SP, int(goods.Decimalplace)),
			TimeStamp:     time.Unix(int64(v.ST), 0),
		}

		rst = append(rst, historyData)
	}

	// 查询成功
	logger.GetLogger().Debugln("QueryHistoryDatas successed: %v", rst)
	appG.Response(http.StatusOK, e.SUCCESS, rst)
}

// QueryTSDataReq 分时图数据查询请求参数
type QueryTSDataReq struct {
	GoodsCode string `form:"goodsCode" binding:"required"` // 商品代码
}

// QueryTSDataRsp 分时图数据查询返回模型
type QueryTSDataRsp struct {
	GoodsCode    string        `json:"goodsCode"`    // 商品代码
	DecimalPlace int           `json:"decimalPlace"` // 小数位
	TradeDate    string        `json:"tradeDate"`    // 交易日
	StartTime    string        `json:"startTime"`    // 开始时间
	EndTime      string        `json:"endTime"`      // 结束时间
	PreSettle    float32       `json:"preSettle"`    // 昨结
	HistoryDatas []HistoryData `json:"historyDatas"` // 历史数据
}

// QueryTSData 分时图数据查询
// @Summary 分时图数据查询
// @Produce json
// @Param GoodsCode query string true "商品代码"
// @Success 200 {object} QueryTSDataRsp
// @Failure 500 {object} app.Response
// @Router /Quote/QueryTSData [get]
// @Tags 行情服务
func QueryTSData(c *gin.Context) {
	appG := app.Gin{C: c}

	// 获取请求参数
	var req QueryTSDataReq
	if err := appG.C.ShouldBindQuery(&req); err != nil {
		logger.GetLogger().Errorf("QueryTSData failed: %s", err.Error())
		appG.Response(http.StatusBadRequest, e.INVALID_PARAMS, nil)
		return
	}

	var queryTSDataRsp QueryTSDataRsp

	// FIXME: - 一些不常变化的数据（如市场信息、商品信息等）应缓存到Redis中, 或缓存到服务内存
	market, err := models.GetMarketByGoodsCode(req.GoodsCode)
	if err != nil {
		logger.GetLogger().Errorf("QueryTSData failed: %s", err.Error())
		appG.Response(http.StatusBadRequest, e.ERROR_QUERY_FAIL, nil)
		return
	}
	if market == nil {
		logger.GetLogger().Errorf("QueryTSData failed: %s", err.Error())
		appG.Response(http.StatusBadRequest, e.ERROR_GET_MARKET_FAILED, nil)
		return
	}
	// 获取目标品种交易日
	// FIXME: - 由于mtp2.0目前未同步外部交易所品种的当前交易日，
	//          故通道交易的品种目前只能在交易系统的外部市场中获
	//          取统一的交易日，后期应要求服务端同步外部数据
	marketRun, err := models.GetMarketRun(int(market.Marketid))
	if marketRun == nil {
		logger.GetLogger().Errorf("QueryTSData failed: %s", err.Error())
		appG.Response(http.StatusBadRequest, e.ERROR_GET_MARKETRUN_FAILED, nil)
		return
	}

	// 获取目标品种的开休市计划
	var runSteps []map[string]interface{}
	// 通道交易外部市场开休市计划表 - QuoteSourceGroupRunStep; 其它市场的 - MarketRunStepDetail
	if market.Trademode == 15 {
		// 外部市场
		sourceRunSteps, err := models.FindQuoteSourceGroupRunStepsByMarket(*market)
		if err != nil {
			logger.GetLogger().Errorf("QueryTSData failed: %s", err.Error())
			appG.Response(http.StatusBadRequest, e.ERROR_GET_RUNSTEP_FAILED, nil)
			return
		}
		for v := range sourceRunSteps {
			// struct -> json
			if jsonBytes, err := json.Marshal(v); err == nil {
				// json -> struct
				var runStepMap map[string]interface{}
				json.Unmarshal(jsonBytes, &runStepMap)

				runSteps = append(runSteps, runStepMap)
			}
		}
	}
	// 非外部市场或外部市场没有配置QuoteSourceGroupRunStep表数据的情况下，都从MarketRunStepDetail中获取数据
	if len(runSteps) == 0 {
		sourceRunSteps, err := models.FindMarketRunStepDetails(int(market.Marketid))
		if err != nil {
			logger.GetLogger().Errorf("QueryTSData failed: %s", err.Error())
			appG.Response(http.StatusBadRequest, e.ERROR_GET_RUNSTEP_FAILED, nil)
			return
		}
		for _, v := range sourceRunSteps {
			// struct -> json
			if jsonBytes, err := json.Marshal(v); err == nil {
				// json -> struct
				var runStepMap map[string]interface{}
				json.Unmarshal(jsonBytes, &runStepMap)

				runSteps = append(runSteps, runStepMap)
			}
		}
	}
	// 构建分时图可直接使用的开休市数据
	// 这里有一个知识点：TRADEWEEKDAY 与 STARTWEEKDAY，以及 TRADEWEEKDAY 与 ENDWEEKDAY 之间相差最多一天（管理端限制），
	//                所以目前并不支持正真的周五夜盘模式。我们在实现时不用做得太复杂。
	// 当前交易日(周几)对应的开休市计划
	tradeDate, err := time.ParseInLocation("2006-01-02", marketRun.Tradedate, time.Local)
	if err != nil {
		logger.GetLogger().Errorf("QueryTSData failed: %s", err.Error())
		appG.Response(http.StatusBadRequest, e.ERROR_QUERY_FAIL, nil)
		return
	}
	curWeekRunSteps := make([]map[string]interface{}, 0)
	for _, v := range runSteps {
		tradeWeekDay := v["tradeweekday"].(int)
		if tradeWeekDay == int(tradeDate.Weekday()) {
			curWeekRunSteps = append(curWeekRunSteps, v)
		}
	}
	// 获取不到可用的开休市计划
	if len(curWeekRunSteps) == 0 {
		logger.GetLogger().Errorf("QueryTSData failed: %s", err.Error())
		appG.Response(http.StatusBadRequest, e.ERROR_GET_RUNSTEP_FAILED, nil)
		return
	}
	// 按 SECTIONID 顺序排序（管理端会按时间顺序添加开休市计划）
	sort.Slice(curWeekRunSteps, func(i int, j int) bool {
		return curWeekRunSteps[i]["sectionid"].(int) < curWeekRunSteps[j]["sectionid"].(int)
	})
	// 把各开休市时间段转化为真实的时间
	//

	// 查询成功
	logger.GetLogger().Debugln("QueryTSData successed: %v", queryTSDataRsp)
	appG.Response(http.StatusOK, e.SUCCESS, queryTSDataRsp)
}
