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

import java.time.LocalDateTime;
import java.time.temporal.Temporal;
import java.util.List;
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 com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;

import link.at17.mid.tushare.annotation.UpdateMethod;
import link.at17.mid.tushare.dao.StockDailyDao;
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.service.BaseServiceImpl;
import lombok.extern.slf4j.Slf4j;

@Service
@Slf4j
public class StockDailyService extends BaseServiceImpl<StockDailyDao, StockValue> implements ITsTradeDate {

    @Autowired
    StockInfoService stockInfoService;
    
    @Autowired
    TushareCrawler tushareCrawler;
    
    @Autowired
    RedissonClient redis;
    
    /**
     * 更新由 Tushare 获取的数据
     * @param list
     * @return
     */
    public boolean insertOrUpdateList(List<JSONObject> list) {
        return SqlHelper.retBool(baseMapper.insertOrUpdateList(list));
    }
    
    /**
     * 获取除权日线数据 + 基本行情数据
     * @param tsCode Tushare 股票代码，不允许为空
     * @param endDate 结束日期(包含)，留空则为最新一个交易日
     * @param before 多少个交易日以前，留空则查询上市至 endDate 以来所有数据
     * @return
     */
    public List<StockValueEx> getExDailyBefore(String tsCode, Temporal endDate, Long before) {
        return baseMapper.getExDailyBefore(tsCode, endDate, before);
    }
    /**
     * 获取前复权日线数据
     * @param tsCode Tushare 股票代码，不允许为空
     * @param startDate 开始日期，留空则为股票上市日起
     * @param endDate 结束日期，null 则为最新一个交易日
     * @return
     */
    public List<StockValue> getQfqDaily(String tsCode, Temporal startDate, Temporal endDate) {
        return baseMapper.getQfqDaily(tsCode, startDate, endDate);
    }
    /**
     * 获取前复权日线数据
     * @param tsCode Tushare 股票代码，不允许为空
     * @param endDate 结束日期，留空则为最新一个交易日
     * @param before 多少个交易日以前，null 则查询上市以来所有数据
     * @return
     */
    public List<StockValue> getQfqDailyBefore(String tsCode, Temporal endDate, Long before) {
        return baseMapper.getQfqDailyBefore(tsCode, endDate, before);
    }
    /**
     * 获取前复权日线数据 + 基本行情数据
     * @param tsCode Tushare 股票代码，不允许为空
     * @param startDate 开始日期（包含），留空则为上市第一日
     * @param after 多少个交易日以前，null 则查询 startDate 以来所有数据
     * @return
     */
    public List<StockValueEx> getExQfqDailyAfter(String tsCode, Temporal startDate, Long after) {
        return baseMapper.getExQfqDailyAfter(tsCode, startDate, after);
    }
    /**
     * 获取前复权日线数据 + 基本行情数据
     * @param tsCode Tushare 股票代码，不允许为空
     * @param endDate 结束日期，留空则为最新一个交易日
     * @param before 多少个交易日以前，null 则查询上市以来所有数据
     * @return
     */
    public List<StockValueEx> getExQfqDailyBefore(String tsCode, Temporal endDate, Long before) {
        return baseMapper.getExQfqDailyBefore(tsCode, endDate, before);
    }
    /**
     * 获取前复权日线数据
     * @param tsCode Tushare 股票代码，不允许为空
     * @param startDate 开始日期（包含），留空则为上市第一日
     * @param after 多少个交易日以前，null 则查询 startDate 以来所有数据
     * @return
     */
    public List<StockValue> getQfqDailyAfter(String tsCode, Temporal startDate, Long after) {
        return baseMapper.getQfqDailyAfter(tsCode, startDate, after);
    }
    
    @Override
    public LocalDateTime getLatestTradeDate(ITsStockInfo stockInfo) {
        return baseMapper.getLatestTradeDate(stockInfo);
    }

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

    @Override
    public List<LocalDateTime> getAllMissingDates(ITsStockInfo stockInfo) {
        return baseMapper.getAllMissingDates(stockInfo);
    }
    
    /**
     * <b><a href="https://waditu.com/document/2?doc_id=27">更新日K数据</a></b><br/>
     * 基础积分每分钟内最多调取500次，每次5000条数据，相当于23年历史，用户获得超过5000积分正常调取无频次限制。
     * <br>
     * <font color="red">请先更新交易日历和股票列表后再调用该方法，否则可能造成股票日 K 数据缺失</font>
     * @param queryWay 滚动查询方式
     * @see TushareCrawler#rollingQueryByDate
     * @see TushareCrawler#rollingQueryByStock
     */

    @UpdateMethod(name="日线数据", order=2)
    public boolean updateData(
            @AllowedEnum({"ByDateUpdate", "ByStockCrossCheck", "ByDateAll"})
            QueryWay queryWay) {
        TushareRequestBody baseRequest = new TushareRequestBody("daily")
                .addFields("ts_code,trade_date,open,high,low,close,pre_close,change,pct_chg,vol,amount");
        Function<List<JSONObject>, Boolean> function = (t) -> {
                insertOrUpdateList(t);
            return true;
        };
        List<Future<TushareCrawlerResult>> executeResult;
        if (queryWay.compareTo(QueryWay.ByStock) >= 0) {
            executeResult = tushareCrawler.rollingQueryByStock(baseRequest, stockInfoService.list(), this, function, 5000L, queryWay, null);
        }
        else {
            executeResult = tushareCrawler.rollingQueryByDate(baseRequest, this, function, queryWay, null);
        }
        log.info("日 K 数据更新完成");
        for (Future<TushareCrawlerResult> f : executeResult) {
            try {
                TushareCrawlerResult result = f.get();
                JSONObject request = f.get().getRequest();
                if (result.isFatal()) {
                    log.error("日 K 数据未获取，发生致命错误，将不会重试：{}", request.toJSONString());
                }
                else if (!result.isSuccess()) {
                    log.warn("日 K 数据未获取：{}", request.toJSONString());
                }
            } catch (InterruptedException | ExecutionException e) {
                log.error("检查日 K 数据执行结果时发生错误", e);
            }
        }
        if (baseRequest.hasFingerprint()) {
            redis.getBucket(baseRequest.getFingerprint()).delete();
        }
        return true;
    }

}
