package quote

import (
	"encoding/json"
	"fmt"
	"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"` // 时间
	IsFill        bool      `json:"f"`  // 是否补充数据
}

// 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: 日线"
// @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)
}

// HistoryTikData Tik数据
type HistoryTikData struct {
	TimeStamp time.Time `json:"TS"`  // 行情时间文本
	PE        float64   `json:"PE"`  // 现价
	Vol       int       `json:"Vol"` // 现量
	TT        float64   `json:"TT"`  // 现金额
	Bid       float64   `json:"Bid"` // 买价
	BV        int       `json:"BV"`  // 买量
	Ask       float64   `json:"Ask"` // 卖价
	AV        int       `json:"AV"`  // 卖量
	HV        int       `json:"HV"`  // 持仓量
	HI        int       `json:"HI"`  // 单笔持仓
	TDR       int       `json:"TDR"` // 交易方向，0:买 1:卖
	TK        int       `json:"TK"`  // 交易类型
}

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

// QueryHistoryTikDatas 查询行情Tik数据
// @Summary 查询行情Tik数据
// @Produce json
// @Security ApiKeyAuth
// @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} HistoryTikData
// @Failure 500 {object} app.Response
// @Router /Quote/QueryHistoryTikDatas [get]
// @Tags 行情服务
func QueryHistoryTikDatas(c *gin.Context) {
	appG := app.Gin{C: c}

	// 获取请求参数
	var req QueryHistoryTikDatasReq
	if err := appG.C.ShouldBindQuery(&req); err != nil {
		logger.GetLogger().Errorf("QueryHistoryTikDatas 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("QueryHistoryTikDatas 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("QueryHistoryTikDatas failed: %s", err.Error())
			appG.Response(http.StatusBadRequest, e.ERROR_QUERY_TIME_FORMAT_FAIL, nil)
			return
		}
		endTime = &et
	}

	// 查询数据
	tikDatas, err := models.GetHistoryTikDatas(req.GoodsCode, startTime, endTime, req.Count, req.IsAsc)
	if err != nil {
		logger.GetLogger().Errorf("QueryHistoryTikDatas 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("QueryHistoryTikDatas failed: %s", err.Error())
		appG.Response(http.StatusBadRequest, e.ERROR_QUERY_FAIL, nil)
		return
	}

	// 计算最终价格
	rst := make([]HistoryTikData, 0)
	for _, v := range tikDatas {
		// 获取方向
		buyOrSell := 0
		if v.TDR == 83 { // (Ascii) B-66 S-83
			buyOrSell = 1
		}
		rst = append(rst, HistoryTikData{
			TimeStamp: time.Unix(int64(v.AT), 0),
			PE:        utils.IntToFloat64(v.PE, int(goods.Decimalplace)),
			Vol:       v.Vol,
			TT:        float64(v.TT),
			Bid:       utils.IntToFloat64(v.Bid, int(goods.Decimalplace)),
			BV:        v.BV,
			Ask:       utils.IntToFloat64(v.Ask, int(goods.Decimalplace)),
			AV:        v.AV,
			HV:        v.HV,
			HI:        v.HI,
			TDR:       buyOrSell,
			TK:        v.TK,
		})
	}

	// 查询成功
	logger.GetLogger().Debugln("QueryHistoryTikDatas 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"`    // 商品代码
	OutGoodsCode string          `json:"outGoodsCode"` // 外部商品代码
	DecimalPlace int             `json:"decimalPlace"` // 小数位
	TradeDate    string          `json:"tradeDate"`    // 交易日
	StartTime    time.Time       `json:"startTime"`    // 开始时间
	EndTime      time.Time       `json:"endTime"`      // 结束时间
	PreSettle    float64         `json:"preSettle"`    // 昨结价
	Count        int             `json:"Count"`        // 交易日应有数据个数
	HistoryDatas []HistoryData   `json:"historyDatas"` // 历史数据
	RunSteps     []TSDataRunStep `json:"runSteps"`     // 交易日开休市计划
}

// TSDataRunStep 分时图交易日开休市计划
type TSDataRunStep struct {
	Groupid      int32     `json:"groupid"`      // 分组ID
	Tradeweekday int32     `json:"tradeweekday"` // 交易日归属 - 0:星期天、1:星期一、2:星期二、3:星期三、4:星期四、5:星期五、6:星期六
	Sectionid    int32     `json:"sectionid"`    // 从 1 开始 往下编 [0为系统清盘、结算时间] SectionId = 0时，开始时间=清盘时间  开始周几= 清盘周几， 结束时间=结算时间 结束周几=结算周几
	Runstep      int32     `json:"runstep"`      // 运行阶段 - 2:连续交易
	Startweekday int32     `json:"startweekday"` // 起始周几
	Starttime    string    `json:"starttime"`    // 起始时间(HH:mm)
	Endweekday   int32     `json:"endweekday"`   // 结束周几
	Endtime      string    `json:"endtime"`      // 结束时间(HH:mm)
	Startflag    int32     `json:"startflag"`    // 开始日标识 - (-1:上日 0:当日 1:次日 )
	Endflag      int32     `json:"endflag"`      // 结束日标识 - (-1:上日 0:当日 1:次日 )
	Start        time.Time `json:"start"`        // 真实开始时间
	End          time.Time `json:"end"`          // 真实结束时间
}

// QueryTSData 分时图数据查询
// @Summary 分时图数据查询
// @Produce json
// @Security ApiKeyAuth
// @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
	}

	// FIXME: - 一些不常变化的数据（如市场信息、商品信息等）应缓存到Redis中, 或缓存到服务内存
	// 获取商品信息
	goods, err := models.GetGoodsByGoodsCode(req.GoodsCode)
	if goods == nil {
		logger.GetLogger().Errorf("QueryTSData failed: %s", err.Error())
		appG.Response(http.StatusBadRequest, e.ERROR_GET_GOODS_FAILED, nil)
		return
	}
	// 获取市场
	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.FindQuoteSourceGroupRunSteps(*goods)
		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)
			}
		}
	}

	// 获取目标商品的盘面信息
	quoteDays, err := models.GetQuoteDays("'" + goods.Outgoodscode + "'")
	if err != nil {
		logger.GetLogger().Errorf("QueryTSData failed: %s", err.Error())
		appG.Response(http.StatusBadRequest, e.ERROR_GET_GOODS_FAILED, nil)
		return
	}
	var preSettle float64
	var preSettleInt int
	if len(quoteDays) > 0 {
		if quoteDays[0].Presettle != 0 {
			preSettleInt = int(quoteDays[0].Presettle)
			preSettle = utils.IntToFloat64(preSettleInt, int(goods.Decimalplace))
		}
		if preSettle == 0 && quoteDays[0].Preclose != 0 {
			preSettleInt = int(quoteDays[0].Preclose)
			preSettle = utils.IntToFloat64(preSettleInt, int(goods.Decimalplace))
		}
	}

	// 构建返回数据
	// 注意：这里使用的是 marketRun.Tradedate2 行情交易日
	queryTSDataRsp := QueryTSDataRsp{
		GoodsCode:    goods.Goodscode,
		OutGoodsCode: goods.Outgoodscode,
		TradeDate:    marketRun.Tradedate2,
		DecimalPlace: int(goods.Decimalplace),
		PreSettle:    preSettle,
	}

	// 构建分时图可直接使用的开休市数据
	// 这里有一个知识点：TRADEWEEKDAY 与 STARTWEEKDAY，以及 TRADEWEEKDAY 与 ENDWEEKDAY 之间相差最多一天（管理端限制），
	//                所以目前并不支持正真的周五夜盘模式。我们在实现时不用做得太复杂。
	// 当前交易日(周几)对应的开休市计划
	tradeDate, err := time.ParseInLocation("20060102", marketRun.Tradedate2, 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 := int(v["tradeweekday"].(float64))
		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"].(float64) < curWeekRunSteps[j]["sectionid"].(float64)
	})

	// 把各开休市时间段转化为真实的时间
	// 关于开休市计划的时间顺序：管理端会按时间顺序添加开休市计划，所以交易日开始时间为第一条记录的开始时间，结束时间为最后一条记录的结束时间
	// 关于目标商品的交易日问题：目前只能从商品所属市场获取当前交易日，这样有两个问题，一是不能按常规显示最后一个有历史数据的交易日；二是目前所有外部商品的开休市计划都是一样的。
	timeFormat := "20060102 15:04"
	// 开始时间
	startInterval := getTradeDay(int(curWeekRunSteps[0]["tradeweekday"].(float64)), int(curWeekRunSteps[0]["startweekday"].(float64)))
	queryTSDataRsp.StartTime, _ = time.ParseInLocation(timeFormat, fmt.Sprintf("%s %s", marketRun.Tradedate2, curWeekRunSteps[0]["starttime"].(string)), time.Local)
	if startInterval != 0 {
		duration, _ := time.ParseDuration(fmt.Sprintf("%dh", startInterval*24))
		queryTSDataRsp.StartTime = queryTSDataRsp.StartTime.Add(duration)
	}
	// 结束时间
	index := len(curWeekRunSteps) - 1
	endInterval := getTradeDay(int(curWeekRunSteps[index]["tradeweekday"].(float64)), int(curWeekRunSteps[index]["endweekday"].(float64)))
	queryTSDataRsp.EndTime, _ = time.ParseInLocation(timeFormat, fmt.Sprintf("%s %s", marketRun.Tradedate2, curWeekRunSteps[index]["endtime"].(string)), time.Local)
	if endInterval != 0 {
		duration, _ := time.ParseDuration(fmt.Sprintf("%dh", endInterval*24))
		queryTSDataRsp.EndTime = queryTSDataRsp.EndTime.Add(duration)
	}
	// fmt.Printf("开始时间：%s 结束时间：%s\n", queryTSDataRsp.StartTime.Format(timeFormat), queryTSDataRsp.EndTime.Format(timeFormat))

	// 获取目标时间段的历史数据（1分钟周期）
	// 这里要注意：由于交易库和行情库由于GoodsCode大小写不一定对得上，所以在使用交易库的商品查询行情数据时间，都要使用OutGoodsCode字段
	cycleDatas, err := models.GetHistoryCycleDatas(models.CycleTypeMinutes1, queryTSDataRsp.OutGoodsCode, &queryTSDataRsp.StartTime, &queryTSDataRsp.EndTime, 0, true)
	if err != nil {
		logger.GetLogger().Errorf("QueryTSData failed: %s", err.Error())
		appG.Response(http.StatusBadRequest, e.ERROR_QUERY_FAIL, nil)
		return
	}
	// ==================== 补数据（补休市数据和缺少的数据）====================
	if len(cycleDatas) > 0 {
		// 补数据第一步：如果第一数据不是开始时间的，需要补数据
		sources := time.Unix(int64(cycleDatas[0].ST), 0)
		diff := sources.Sub(queryTSDataRsp.StartTime)
		if diff.Minutes() > 0 {
			minute := int(diff.Minutes())
			for i := 1; i <= minute; i++ {
				st := cycleDatas[0].ST - i*60
				stt := time.Unix(int64(st), 0).Format("2006-01-02 15:04:05")
				// 前面的数据使用昨结来补
				cycleDatas = append(cycleDatas, models.CycleData{
					GC:    cycleDatas[0].GC,
					Open:  preSettleInt,
					High:  preSettleInt,
					Low:   preSettleInt,
					Close: preSettleInt,
					TV:    0,
					TT:    0,
					HV:    0,
					SP:    0,
					ST:    st,
					SST:   stt,
					FI:    true,
				})
			}
		}
		// 接时间戳排序
		sort.Slice(cycleDatas, func(i int, j int) bool {
			return cycleDatas[i].ST < cycleDatas[j].ST
		})

		// 补数据第二步：按尾部的时间（当前服务器时间或最后休市时间）进行全补
		// 获取服务器时间
		s, _ := models.GetServerTime()
		endTime, err := time.ParseInLocation("2006-01-02T15:04:05Z", *s, time.Local)
		if err != nil {
			logger.GetLogger().Errorf("QueryTSData failed: %s", err.Error())
			appG.Response(http.StatusBadRequest, e.ERROR_QUERY_FAIL, nil)
			return
		}
		if endTime.After(queryTSDataRsp.EndTime) {
			endTime = queryTSDataRsp.EndTime
		}

		index := len(cycleDatas) - 1
		sources = time.Unix(int64(cycleDatas[index].ST), 0)
		diff = endTime.Sub(sources)
		if diff.Minutes() > 0 {
			minute := int(diff.Minutes())
			for i := 1; i <= minute; i++ {
				st := cycleDatas[index].ST + i*60
				stt := time.Unix(int64(st), 0).Format("2006-01-02 15:04:05")
				cycleDatas = append(cycleDatas, models.CycleData{
					GC:    cycleDatas[index].GC,
					Open:  cycleDatas[index].Close,
					High:  cycleDatas[index].Close,
					Low:   cycleDatas[index].Close,
					Close: cycleDatas[index].Close,
					TV:    0,
					TT:    0,
					HV:    0,
					SP:    0,
					ST:    st,
					SST:   stt,
					FI:    true,
				})
			}
		}
		// 接时间戳排序
		sort.Slice(cycleDatas, func(i int, j int) bool {
			return cycleDatas[i].ST < cycleDatas[j].ST
		})

		// 补数据第三步：补中间数据
		fillDatas := make([]models.CycleData, 0)
		for i := range cycleDatas {
			// 第一条记录跳过
			if i == 0 {
				continue
			}

			current := time.Unix(int64(cycleDatas[i].ST), 0)
			prev := time.Unix(int64(cycleDatas[i-1].ST), 0)
			diff := current.Sub(prev)
			if diff.Minutes() > 1 {
				minute := int(diff.Minutes())
				// 判断是否需要补数据，与上一条数据的间距不是一分钟
				if minute > 1 {
					// 注意补中间数据时，是不用补最后一条数据的，所以是 < minute
					for j := 1; j < minute; j++ {
						st := cycleDatas[i-1].ST + j*60
						stt := time.Unix(int64(st), 0).Format("2006-01-02 15:04:05")
						fillDatas = append(fillDatas, models.CycleData{
							GC:    cycleDatas[i-1].GC,
							Open:  cycleDatas[i-1].Close,
							High:  cycleDatas[i-1].Close,
							Low:   cycleDatas[i-1].Close,
							Close: cycleDatas[i-1].Close,
							TV:    0,
							TT:    0,
							HV:    0,
							SP:    0,
							ST:    st,
							SST:   stt,
							FI:    true,
						})
					}
				}
			}
		}
		// 加入到源数据
		cycleDatas = append(cycleDatas, fillDatas...)
		// 接时间戳排序
		sort.Slice(cycleDatas, func(i int, j int) bool {
			return cycleDatas[i].ST < cycleDatas[j].ST
		})
	} else {
		// TODO: - 下面这块操作需求确认
		// 如果查询结果是空数据，则使用昨结价补到服务器时间（或最后休市时间）
		// 获取服务器时间
		s, _ := models.GetServerTime()
		endTime, err := time.ParseInLocation("2006-01-02T15:04:05Z", *s, time.Local)
		if err != nil {
			logger.GetLogger().Errorf("QueryTSData failed: %s", err.Error())
			appG.Response(http.StatusBadRequest, e.ERROR_QUERY_FAIL, nil)
			return
		}
		if endTime.After(queryTSDataRsp.EndTime) {
			endTime = queryTSDataRsp.EndTime
		}

		diff := endTime.Sub(queryTSDataRsp.StartTime)
		minute := int(diff.Minutes())
		for i := 1; i <= minute; i++ {
			st := int(queryTSDataRsp.StartTime.Unix()) + i*60
			stt := time.Unix(int64(st), 0).Format("2006-01-02 15:04:05")
			cycleDatas = append(cycleDatas, models.CycleData{
				GC:    queryTSDataRsp.GoodsCode,
				Open:  preSettleInt,
				High:  preSettleInt,
				Low:   preSettleInt,
				Close: preSettleInt,
				TV:    0,
				TT:    0,
				HV:    0,
				SP:    0,
				ST:    st,
				SST:   stt,
				FI:    true,
			})
		}
		// 接时间戳排序
		sort.Slice(cycleDatas, func(i int, j int) bool {
			return cycleDatas[i].ST < cycleDatas[j].ST
		})
	}

	// 补数据第四步：清除掉开市计划外的数据
	// 先计算出每条计划明细的真正开始与结束时间
	queryTSDataRsp.RunSteps = make([]TSDataRunStep, 0)
	for _, v := range curWeekRunSteps {
		// 开始时间
		startInterval := getTradeDay(int(v["tradeweekday"].(float64)), int(v["startweekday"].(float64)))
		v["start"], _ = time.ParseInLocation(timeFormat, fmt.Sprintf("%s %s", marketRun.Tradedate2, v["starttime"].(string)), time.Local)
		if startInterval != 0 {
			duration, _ := time.ParseDuration(fmt.Sprintf("%dh", startInterval*24))
			v["start"] = v["start"].(time.Time).Add(duration)
		}
		// 结束时间
		endInterval := getTradeDay(int(v["tradeweekday"].(float64)), int(v["endweekday"].(float64)))
		v["end"], _ = time.ParseInLocation(timeFormat, fmt.Sprintf("%s %s", marketRun.Tradedate2, v["endtime"].(string)), time.Local)
		if endInterval != 0 {
			duration, _ := time.ParseDuration(fmt.Sprintf("%dh", endInterval*24))
			v["end"] = v["end"].(time.Time).Add(duration)
		}

		// map -> struct
		var tsDataRunStep TSDataRunStep
		jsonbody, err := json.Marshal(v)
		if err != nil {
			continue
		}
		if err := json.Unmarshal(jsonbody, &tsDataRunStep); err != nil {
			continue
		}
		queryTSDataRsp.RunSteps = append(queryTSDataRsp.RunSteps, tsDataRunStep)
	}
	// 最终返回的历史数据
	historyDatas := make([]HistoryData, 0)
	for _, cycleData := range cycleDatas {
		needAdd := false
		for _, runStep := range curWeekRunSteps {
			// 判断是否在开市计划内
			if cycleData.ST >= int(runStep["start"].(time.Time).Unix()) && cycleData.ST <= int(runStep["end"].(time.Time).Unix()) {
				needAdd = true
				break
			}
		}
		if needAdd {
			historyDatas = append(historyDatas, HistoryData{
				Opened:        utils.IntToFloat64(cycleData.Open, int(goods.Decimalplace)),
				Highest:       utils.IntToFloat64(cycleData.High, int(goods.Decimalplace)),
				Lowest:        utils.IntToFloat64(cycleData.Low, int(goods.Decimalplace)),
				Closed:        utils.IntToFloat64(cycleData.Close, int(goods.Decimalplace)),
				TotleVolume:   cycleData.TV,
				TotleTurnover: float64(cycleData.TT),
				HoldVolume:    cycleData.HV,
				Settle:        utils.IntToFloat64(cycleData.SP, int(goods.Decimalplace)),
				TimeStamp:     time.Unix(int64(cycleData.ST), 0),
				IsFill:        cycleData.FI,
			})
		}
	}
	queryTSDataRsp.HistoryDatas = historyDatas

	// 计算当前交易日应该有多少个历史数据，主要用于iOS
	for _, v := range queryTSDataRsp.RunSteps {
		diff := v.End.Sub(v.Start)
		queryTSDataRsp.Count += int(diff.Minutes())
	}

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

// getTradeDay 获取结算计划中天数间隔的方法
//	- tradeDay: 交易日周几
//	- weekDay: 开始或结束周几
//	- Returns: 天数间隔
func getTradeDay(tradeDay, weekDay int) int {
	if tradeDay == weekDay {
		return 0
	}
	if weekDay == 0 {
		weekDay = 7
	}
	betWeekDay := weekDay - tradeDay
	if betWeekDay < 0 {
		betWeekDay = betWeekDay + 7
	}
	if betWeekDay >= 4 {
		betWeekDay = betWeekDay - 7
	}

	return betWeekDay
}
