package factor_edb_series

import (
	"encoding/json"
	"eta/eta_index_lib/controllers"
	"eta/eta_index_lib/logic"
	"eta/eta_index_lib/models"
	"eta/eta_index_lib/services"
	"eta/eta_index_lib/utils"
	"fmt"
	"strconv"
	"sync"
	"time"
)

// FactorEdbSeriesController 因子指标系列
type FactorEdbSeriesController struct {
	controllers.BaseAuthController
}

// Recalculate
// @Title 因子指标系列-重计算
// @Description 因子指标系列-重计算
// @Success 200 {object} models.FactorEdbRecalculateReq
// @router /recalculate [post]
func (this *FactorEdbSeriesController) Recalculate() {
	br := new(models.BaseResponse).Init()
	defer func() {
		if br.ErrMsg == "" {
			br.IsSendEmail = false
		}
		this.Data["json"] = br
		this.ServeJSON()
	}()
	var req models.FactorEdbRecalculateReq
	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
		br.Msg = "参数解析异常！"
		br.ErrMsg = fmt.Sprintf("参数解析失败, Err: %v", e)
		return
	}
	if req.EdbInfoId <= 0 {
		br.Msg = "参数有误"
		br.ErrMsg = fmt.Sprintf("指标ID有误, EdbInfoId: %d", req.EdbInfoId)
		return
	}

	cacheKey := utils.CACHE_EDB_DATA_REFRESH + "_factor_edb_recalculate_" + req.EdbCode
	if utils.Rc.IsExist(cacheKey) {
		br.Ret = 501
		br.Success = true
		br.Msg = fmt.Sprintf("系统处理中, 请稍后重试, 指标编码: %s", req.EdbCode)
		return
	}
	utils.Rc.SetNX(cacheKey, 1, 10*time.Minute)
	defer func() {
		_ = utils.Rc.Delete(cacheKey)
	}()

	// 获取指标信息
	edbInfo, e := models.GetEdbInfoById(req.EdbInfoId)
	if e != nil {
		if e.Error() == utils.ErrNoRow() {
			br.Msg = "指标不存在"
			br.ErrMsg = fmt.Sprintf("指标不存在, EdbInfoId: %d, EdbCode: %s", req.EdbInfoId, req.EdbCode)
			return
		}
		br.Msg = "操作失败"
		br.ErrMsg = fmt.Sprintf("获取指标信息失败, Err: %v", e)
		return
	}

	// 查询指标关联的系列
	mappings := make([]*models.FactorEdbSeriesMapping, 0)
	{
		ob := new(models.FactorEdbSeriesMapping)
		cond := fmt.Sprintf(" AND %s = ?", ob.Cols().EdbInfoId)
		pars := make([]interface{}, 0)
		pars = append(pars, req.EdbInfoId)
		fields := []string{ob.Cols().FactorEdbSeriesId, ob.Cols().EdbInfoId, ob.Cols().EdbCode}
		list, e := ob.GetItemsByCondition(cond, pars, fields, "")
		if e != nil {
			br.Msg = "操作失败"
			br.ErrMsg = fmt.Sprintf("获取指标系列关联失败, Err: %v", e)
			return
		}
		mappings = list
	}
	if len(mappings) == 0 {
		br.Ret = 200
		br.Success = true
		br.Msg = "操作成功"
		return
	}

	// 获取系列信息
	seriesIds := make([]int, 0)
	seriesExist := make(map[int]bool)
	for _, v := range mappings {
		if !seriesExist[v.FactorEdbSeriesId] {
			seriesExist[v.FactorEdbSeriesId] = true
			seriesIds = append(seriesIds, v.FactorEdbSeriesId)
		}
	}
	if len(seriesIds) == 0 {
		br.Ret = 200
		br.Success = true
		br.Msg = "操作成功"
		return
	}
	seriesMap := make(map[int]*models.FactorEdbSeries, 0)
	{
		ob := new(models.FactorEdbSeries)
		cond := fmt.Sprintf(" AND %s IN (%s)", ob.Cols().PrimaryId, utils.GetOrmInReplace(len(seriesIds)))
		pars := make([]interface{}, 0)
		pars = append(pars, seriesIds)
		fields := []string{ob.Cols().PrimaryId, ob.Cols().EdbInfoType, ob.Cols().CalculateStep, ob.Cols().CalculateState}
		list, e := ob.GetItemsByCondition(cond, pars, fields, "")
		if e != nil {
			br.Msg = "操作失败"
			br.ErrMsg = fmt.Sprintf("获取指标系列失败, Err: %v", e)
			return
		}
		for _, v := range list {
			seriesMap[v.FactorEdbSeriesId] = v
		}
	}

	// 获取指标原数据
	edbData, e := models.GetEdbDataAllByEdbCode(edbInfo.EdbCode, edbInfo.Source, edbInfo.SubSource, 0)
	if e != nil {
		br.Msg = "操作失败"
		br.ErrMsg = fmt.Sprintf("获取指标数据失败, EdbCode: %s, Err: %v", edbInfo.EdbCode, e)
		return
	}
	if len(edbData) == 0 {
		br.Msg = "指标无数据"
		br.ErrMsg = fmt.Sprintf("指标无数据, EdbCode: %s", edbInfo.EdbCode)
		return
	}

	for _, v := range mappings {
		series := seriesMap[v.FactorEdbSeriesId]
		if series == nil {
			continue
		}
		// 系列无计算则忽略
		if series.CalculateState != models.FactorEdbSeriesCalculateNone || series.CalculateStep == "" {
			continue
		}
		var calculates []models.CalculatesReq
		if e = json.Unmarshal([]byte(series.CalculateStep), &calculates); e != nil {
			br.Msg = "计算步骤异常"
			br.ErrMsg = fmt.Sprintf("计算步骤异常, SeriesId: %d, Err: %v", series.FactorEdbSeriesId, e)
			return
		}

		// 重新计算相关数据
		e = services.FactorEdbStepCalculate(v.FactorEdbSeriesId, v.EdbInfoId, v.EdbCode, edbData, calculates)
		if e != nil {
			br.Msg = "因子指标计算失败"
			br.ErrMsg = fmt.Sprintf("因子指标计算失败, SeriesId: %d, EdbCode: %s, Err: %v", v.FactorEdbSeriesId, v.EdbCode, e)
			return
		}
	}

	br.Ret = 200
	br.Success = true
	br.Msg = "操作成功"
}

// ChartRecalculate
// @Title 图表数据-重计算
// @Description 图表数据-重计算
// @Success 200 {object} models.FactorEdbRecalculateReq
// @router /chart_recalculate [post]
func (this *FactorEdbSeriesController) ChartRecalculate() {
	br := new(models.BaseResponse).Init()
	defer func() {
		if br.ErrMsg == "" {
			br.IsSendEmail = false
		}
		this.Data["json"] = br
		this.ServeJSON()
	}()
	var req models.FactorEdbChartRecalculateReq
	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
		br.Msg = "参数解析异常！"
		br.ErrMsg = fmt.Sprintf("参数解析失败, Err: %v", e)
		return
	}
	if req.ChartInfoId <= 0 {
		br.Msg = "参数有误"
		br.ErrMsg = fmt.Sprintf("指标ID有误, ChartInfoId: %d", req.ChartInfoId)
		return
	}

	cacheKey := utils.CACHE_CHART_INFO_DATA + "_factor_chart_recalculate_" + strconv.Itoa(req.ChartInfoId)
	if utils.Rc.IsExist(cacheKey) {
		br.Ret = 501
		br.Success = true
		br.Msg = fmt.Sprintf("系统处理中, 请稍后重试, 图表ID: %s", req.ChartInfoId)
		return
	}
	utils.Rc.SetNX(cacheKey, 1, 10*time.Minute)
	defer func() {
		_ = utils.Rc.Delete(cacheKey)
	}()

	// 查询图表关联的系列指标
	mappings := make([]*models.FactorEdbSeriesChartMapping, 0)
	{
		ob := new(models.FactorEdbSeriesChartMapping)
		cond := fmt.Sprintf(" AND %s = ?", ob.Cols().ChartInfoId)
		pars := make([]interface{}, 0)
		pars = append(pars, req.ChartInfoId)
		fields := []string{ob.Cols().PrimaryId, ob.Cols().CalculateType, ob.Cols().CalculatePars, ob.Cols().FactorEdbSeriesId, ob.Cols().EdbInfoId}
		list, e := ob.GetItemsByCondition(cond, pars, fields, "")
		if e != nil {
			br.Msg = "操作失败"
			br.ErrMsg = fmt.Sprintf("获取指标系列关联失败, Err: %v", e)
			return
		}
		mappings = list
	}
	if len(mappings) == 0 {
		br.Ret = 200
		br.Success = true
		br.Msg = "操作成功"
		return
	}
	seriesIds := make([]int, 0)
	edbInfoIds := make([]int, 0)
	for _, v := range mappings {
		seriesIds = append(seriesIds, v.FactorEdbSeriesId)
		edbInfoIds = append(edbInfoIds, v.EdbInfoId)
	}

	// 获取因子指标及系列信息
	seriesIdItem := make(map[int]*models.FactorEdbSeries)
	{
		ob := new(models.FactorEdbSeries)
		cond := fmt.Sprintf(" AND %s IN (%s)", ob.Cols().PrimaryId, utils.GetOrmInReplace(len(seriesIds)))
		pars := make([]interface{}, 0)
		pars = append(pars, seriesIds)
		items, e := ob.GetItemsByCondition(cond, pars, []string{}, fmt.Sprintf("%s ASC", ob.Cols().PrimaryId))
		if e != nil {
			br.Msg = "获取失败"
			br.ErrMsg = "获取因子指标系列失败, Err: " + e.Error()
			return
		}
		for _, v := range items {
			seriesIdItem[v.FactorEdbSeriesId] = v
		}
	}
	edbIdItem := make(map[int]*models.EdbInfo)
	edbItems, e := models.GetEdbInfoByIdList(edbInfoIds)
	if e != nil {
		br.Msg = "获取失败"
		br.ErrMsg = "获取因子指标失败, Err: " + e.Error()
		return
	}
	for _, v := range edbItems {
		edbIdItem[v.EdbInfoId] = v
	}

	// 重新计算
	calculateWorkers := make(chan struct{}, 10)
	wg := sync.WaitGroup{}
	calculateDataOb := new(models.FactorEdbSeriesCalculateData)
	for _, v := range mappings {
		edbItem := edbIdItem[v.EdbInfoId]
		if edbItem == nil {
			continue
		}
		seriesItem := seriesIdItem[v.FactorEdbSeriesId]
		if seriesItem == nil {
			continue
		}

		wg.Add(1)
		go func(chartMapping *models.FactorEdbSeriesChartMapping, edb *models.EdbInfo, series *models.FactorEdbSeries) {
			defer func() {
				wg.Done()
				<-calculateWorkers
			}()
			calculateWorkers <- struct{}{}

			// 相关性计算
			if chartMapping.CalculateType == models.FactorEdbSeriesChartCalculateTypeCorrelation {
				// 解析计算参数
				if chartMapping.CalculatePars == "" {
					utils.FileLog.Info(fmt.Sprintf("相关性-计算参数为空, MappingId: %d", chartMapping.FactorEdbSeriesChartMappingId))
					return
				}
				var calculatePars models.FactorEdbSeriesChartCalculateCorrelationReq
				if e := json.Unmarshal([]byte(chartMapping.CalculatePars), &calculatePars); e != nil {
					utils.FileLog.Info(fmt.Sprintf("相关性-计算参数解析失败, MappingId: %d, err: %v", chartMapping.FactorEdbSeriesChartMappingId, e))
					return
				}

				// 获取标的指标信息及数据
				baseEdb, e := models.GetEdbInfoById(calculatePars.BaseEdbInfoId)
				if e != nil {
					utils.FileLog.Info(fmt.Sprintf("相关性-获取标的指标失败, MappingId: %d, err: %v", chartMapping.FactorEdbSeriesChartMappingId, e))
					return
				}
				calculateUnitDays, ok := utils.FrequencyDaysMap[calculatePars.CalculateUnit]
				if !ok {
					utils.FileLog.Info(fmt.Sprintf("相关性-错误的计算窗口频度, MappingId: %d", chartMapping.FactorEdbSeriesChartMappingId))
					return
				}
				calculateDays := calculatePars.CalculateValue * calculateUnitDays

				dataListA := make([]*models.EdbInfoSearchData, 0)
				{
					// 标的指标数据日期区间
					startDate := time.Now().AddDate(0, 0, -calculateDays).Format(utils.FormatDate)
					endDate := time.Now().Format(utils.FormatDate)
					startDateTime, _ := time.ParseInLocation(utils.FormatDate, startDate, time.Local)
					startDate = startDateTime.AddDate(0, 0, 1).Format(utils.FormatDate) // 不包含第一天
					switch baseEdb.EdbInfoType {
					case 0:
						list, e := models.GetEdbDataList(baseEdb.Source, baseEdb.SubSource, baseEdb.EdbInfoId, startDate, endDate)
						if e != nil {
							utils.FileLog.Info(fmt.Sprintf("相关性-获取标的指标数据失败, EdbInfoId: %d, err: %v", baseEdb.EdbInfoId, e))
							return
						}
						dataListA = models.TransEdbInfoDataList2SearchData(list)
					case 1:
						dataListA, _, e, _ = models.GetPredictDataListByPredictEdbInfo(baseEdb, 1, startDate)
						if e != nil {
							utils.FileLog.Info(fmt.Sprintf("相关性-获取标的指标数据失败, EdbInfoId: %d, err: %v", baseEdb.EdbInfoId, e))
							return
						}
					default:
						utils.FileLog.Info(fmt.Sprintf("相关性-标的指标类型异常, EdbInfoId: %d", baseEdb.EdbInfoId))
						return
					}
				}

				// 获取指标数据
				dataListB := make([]*models.EdbInfoSearchData, 0)
				if series.CalculateState == models.FactorEdbSeriesCalculated {
					cond := fmt.Sprintf(" AND %s = ? AND %s = ?", calculateDataOb.Cols().FactorEdbSeriesId, calculateDataOb.Cols().EdbInfoId)
					pars := make([]interface{}, 0)
					pars = append(pars, chartMapping.FactorEdbSeriesId, chartMapping.EdbInfoId)
					dataItems, e := calculateDataOb.GetItemsByCondition(cond, pars, []string{calculateDataOb.Cols().DataTime, calculateDataOb.Cols().Value}, fmt.Sprintf("%s ASC", calculateDataOb.Cols().DataTime))
					if e != nil {
						utils.FileLog.Info(fmt.Sprintf("相关性-获取因子指标数据失败, MappingId: %d, err: %v", chartMapping.FactorEdbSeriesChartMappingId, e))
						return
					}
					dataListB = models.TransEdbSeriesCalculateData2EdbDataList(dataItems)
				} else {
					switch edb.EdbInfoType {
					case 0:
						list, e := models.GetEdbDataList(edb.Source, edb.SubSource, edb.EdbInfoId, "", "")
						if e != nil {
							utils.FileLog.Info(fmt.Sprintf("相关性-获取因子指标数据失败, EdbInfoId: %d, err: %v", edb.EdbInfoId, e))
							return
						}
						dataListB = models.TransEdbInfoDataList2SearchData(list)
					case 1:
						dataListB, e = models.GetPredictEdbDataListAll(baseEdb, 1)
						if e != nil {
							utils.FileLog.Info(fmt.Sprintf("相关性-获取因子指标数据失败, EdbInfoId: %d, err: %v", edb.EdbInfoId, e))
							return
						}
					default:
						utils.FileLog.Info(fmt.Sprintf("相关性-因子指标类型异常, EdbInfoId: %d", edb.EdbInfoId))
						return
					}
				}

				// 计算相关性
				xEdbIdValue, yDataList, e := logic.CalculateCorrelation(calculatePars.LeadValue, calculatePars.LeadUnit, baseEdb.Frequency, edb.Frequency, dataListA, dataListB)
				if e != nil {
					utils.FileLog.Info(fmt.Sprintf("相关性-计算失败, EdbInfoId: %d", edb.EdbInfoId))
					return
				}

				// X及Y轴数据
				yData := yDataList[0].Value
				yLen := len(yData)
				values := make([]models.FactorEdbSeriesCorrelationMatrixValues, len(xEdbIdValue))
				for k, x := range xEdbIdValue {
					var y float64
					if k >= 0 && k < yLen {
						y = yData[k]
					}
					y = utils.SubFloatToFloat(y, 2)
					values[k] = models.FactorEdbSeriesCorrelationMatrixValues{
						XData: x, YData: y,
					}
				}

				// 更新计算结果
				b, e := json.Marshal(values)
				if e != nil {
					utils.FileLog.Info(fmt.Sprintf("相关性-计算结果JSON格式化失败, err: %v", e))
					return
				}
				chartMapping.CalculateData = string(b)
				chartMapping.ModifyTime = time.Now().Local()
				if e = chartMapping.Update([]string{chartMapping.Cols().CalculateData, chartMapping.Cols().ModifyTime}); e != nil {
					utils.FileLog.Info(fmt.Sprintf("相关性-更新矩阵数据失败, err: %v", e))
					return
				}
			}
		}(v, edbItem, seriesItem)
	}
	wg.Wait()

	br.Ret = 200
	br.Success = true
	br.Msg = "操作成功"
}
