package link.at17.mid.tushare.data.service;

import java.time.LocalDateTime;
import java.time.temporal.Temporal;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.function.Function;

import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;

import link.at17.mid.tushare.annotation.UpdateMethod;
import link.at17.mid.tushare.dao.StockMinuteDao;
import link.at17.mid.tushare.data.crawler.QueryWay;
import link.at17.mid.tushare.data.crawler.tushare.TushareCrawler;
import link.at17.mid.tushare.data.crawler.tushare.TushareCrawlerResult;
import link.at17.mid.tushare.data.crawler.tushare.TushareRequestBody;
import link.at17.mid.tushare.data.models.StockValue;
import link.at17.mid.tushare.data.models.StockValueEx;
import link.at17.mid.tushare.data.models.interfaces.ITsStockInfo;
import link.at17.mid.tushare.data.models.interfaces.ITsTradeDate;
import link.at17.mid.tushare.data.validator.AllowedEnum;
import link.at17.mid.tushare.enums.StockSpan;
import lombok.extern.slf4j.Slf4j;

@Service
@Slf4j
public class StockMinuteService {

    @Autowired
    StockMinuteDao stockMinuteDao;

    @Autowired
    StockInfoService stockInfoService;

    @Autowired
    TushareCrawler tushareCrawler;

    @Autowired
    RedissonClient redis;

    /**
     * 更新由 Tushare 获取的数据
     * 
     * @param list
     * @return
     */
    public boolean insertOrUpdateList(List<JSONObject> list, StockSpan stockSpan) {
        return SqlHelper.retBool(stockMinuteDao.insertOrUpdateList(list, stockSpan));
    }

    /**
     * 获取最新交易日<br/>
     * 获取到股票的最新交易日
     * 
     * @param stockSpan 分钟线频率
     * @param stockInfo
     * @return
     */
    public LocalDateTime getLatestTradeDate(StockSpan stockSpan, ITsStockInfo stockInfo) {
        return stockMinuteDao.getLatestTradeDate(stockSpan, stockInfo);
    }

    /**
     * 获取指定 freq 下的所有交易日
     * 
     * @param stockSpan 分钟线频率
     * @param stockInfo
     * @return
     */
    public List<LocalDateTime> getAllTradeDates(StockSpan stockSpan, ITsStockInfo stockInfo) {
        return stockMinuteDao.getAllTradeDates(stockSpan, stockInfo);
    }

    /**
     * 获取指定 freq 下的数据缺失日，包括分钟数据不全日<br/>
     * 如：60 分钟频率下，一日内 K 线数应为 240/60 + 1 = 5 条，则小于 5 条的日期都将被列为缺失日期
     * 
     * @param stockSpan 分钟线频率
     * @param stockInfo
     * @return
     */
    public List<LocalDateTime> getAllMissingDates(StockSpan stockSpan, ITsStockInfo stockInfo) {
        return stockMinuteDao.getAllMissingDates(stockSpan, stockInfo);
    }

    /**
     * 获取前复权日线数据
     * 
     * @param stockCode 股票代码，不允许为空
     * @param stockSpan 分钟线频率
     * @param startDate 开始日期，留空则为股票上市日起
     * @param endDate   结束日期，留空则为最新一个交易日
     * @return
     */
    public List<StockValue> getQfqMinute(String stockCode, StockSpan stockSpan, Temporal startDate, Temporal endDate) {
        return stockMinuteDao.getQfqMinute(stockCode, stockSpan, startDate, endDate);
    }

    /**
     * 获取前复权日线数据
     * 
     * @param stockCode 股票代码，不允许为空
     * @param endDate   结束日期，留空则为最新一个交易日
     * @param before    多少个交易日以前，留空则查询上市以来所有数据
     * @return
     */
    public List<StockValue> getQfqMinuteBefore(String stockCode, StockSpan stockSpan, Temporal endDate, Long before) {
        return stockMinuteDao.getQfqMinuteBefore(stockCode, stockSpan, endDate, before);
    }

    /**
     * 获取前复权日线 Ex 数据
     * 
     * @param stockCode 股票代码，不允许为空
     * @param endDate   结束日期，留空则为最新一个交易日
     * @param before    多少个交易日以前，留空则查询上市以来所有数据
     * @return
     */
    public List<StockValueEx> getExQfqMinuteBefore(String stockCode, StockSpan stockSpan, Temporal endDate,
            Long before) {
        return stockMinuteDao.getExQfqMinuteBefore(stockCode, stockSpan, endDate, before);
    }

    /**
     * <b><a href="https://tushare.pro/document/1?doc_id=290">更新分钟K数据</a></b><br/>
     * <p>
     * 有权限时，每分钟500次，每次8000行数据，总量不限制
     * </p>
     * <font color="red">请先更新交易日历和股票列表后再调用该方法，否则可能造成股票分钟 K 数据缺失</font>
     * 
     * @param queryWay  滚动查询方式，仅支持 StockSpan.ByStock... 系列
     * @param stockSpan 股票粒度，仅支持 StockSpan.Minute - StockSpan.SixtyMinute
     * @see TushareCrawler#rollingQueryByDate
     * @see TushareCrawler#rollingQueryByStock
     * @see link.at17.mid.tushare.enums.StockSpan
     */
    @UpdateMethod(name="分钟 K 数据", order=9)
    public boolean updateData(
            @AllowedEnum({"ByDateUpdate", "ByStockCrossCheck", "ByDateAll" })
            QueryWay queryWay,
            @AllowedEnum({"Minute", "Minute5", "Minute15", "Minute30", "Minute60"})
            StockSpan stockSpan) {
        Assert.isTrue(Objects.nonNull(stockSpan), "stockSpan 不允许为空");
        Assert.isTrue(Objects.nonNull(stockSpan.getMin()), "不支持的 StockSpan：" + stockSpan + ", 仅支持分钟数据类型");
        Assert.isTrue(queryWay.compareTo(QueryWay.ByStock) >= 0, "不支持的 QueryWay！");
        String freq = stockSpan.getMin() + "min";
        TushareRequestBody baseRequest = new TushareRequestBody("stk_mins")
                .addFields("ts_code,trade_time,open,close,high,low,vol,amount").addParam("freq", freq);
        Function<List<JSONObject>, Boolean> function = (t) -> {
            stockMinuteDao.insertOrUpdateList(t, stockSpan);
            return true;
        };
        // 特殊重写：获取最新日期和所有交易日，该重写仅对更新数据生效
        ITsTradeDate minTradeDate = new ITsTradeDate() {
            @Override
            public LocalDateTime getLatestTradeDate(ITsStockInfo stockInfo) {
                return stockMinuteDao.getLatestTradeDate(stockSpan, stockInfo);
            }

            @Override
            public List<LocalDateTime> getAllTradeDates(ITsStockInfo stockInfo) {
                return stockMinuteDao.getAllTradeDates(stockSpan, stockInfo);
            }

            @Override
            public List<LocalDateTime> getAllMissingDates(ITsStockInfo stockInfo) {
                return stockMinuteDao.getAllMissingDates(stockSpan, stockInfo);
            }
        };
        List<Future<TushareCrawlerResult>> executeResult = tushareCrawler.rollingQueryByStock(baseRequest,
                stockInfoService.list(), minTradeDate, function, 8000L, queryWay, stockSpan);
        log.info("{} 分钟 K 数据更新完成", stockSpan.getMin());
        for (Future<TushareCrawlerResult> f : executeResult) {
            try {
                TushareCrawlerResult result = f.get();
                JSONObject request = f.get().getRequest();
                if (result.isFatal()) {
                    log.error("{} 分钟 K 数据未获取：{}，发生致命错误，将不会重试：{}", stockSpan.getMin(), request.toJSONString());
                } else if (!result.isSuccess()) {
                    log.warn("{} 分钟 K 数据未获取：{}", stockSpan.getMin(), request.toJSONString());
                }
            } catch (InterruptedException | ExecutionException e) {
                log.error("检查 {} 分钟 K 数据执行结果时发生错误\r\n{}", stockSpan.getMin(), e);
            }
        }
        if (baseRequest.hasFingerprint()) {
            redis.getBucket(baseRequest.getFingerprint()).delete();
        }
        return true;
    }

}
