package quant.rich.emoney.crawler;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.function.Function;
import java.util.stream.Collectors;

import lombok.AccessLevel;
import lombok.Data;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import nano.CandleStickNewResponse.CandleStickNew_Response.CandleStick;
import nano.CandleStickNewWithIndexExResponse.CandleStickNewWithIndexEx_Response;
import nano.CandleStickNewWithIndexExResponse.CandleStickNewWithIndexEx_Response.IndLine;
import nano.CandleStickRequest.CandleStick_Request;
import nano.CandleStickWithIndexRequest.CandleStickWithIndex_Request;
import nano.CandleStickWithIndexRequest.CandleStickWithIndex_Request.IndexInfo;
import nano.IndexCalcNewExResponse.IndexCalcNewEx_Response.outputline;
import quant.rich.emoney.client.EmoneyClient;
import quant.rich.emoney.entity.postgre.EmoneyIndex;
import quant.rich.emoney.enums.StockSpan;
import quant.rich.emoney.exception.EmoneyRequestException;
import quant.rich.emoney.util.DateUtils;
import quant.rich.emoney.util.StockCodeUtils;

/**
 * 执行爬虫时最终被调用的方法。该方法使用 2422（多指标、带 K 线）更新指标数据
 */
@Data
@Slf4j
public class EmoneyIndexCallable implements Callable<EmoneyCrawlerResult<EmoneyIndexCallable>> {
    
    private static final int LIMIT_SIZE = 250;
    
    private EmoneyCrawlerResult<EmoneyIndexCallable> result;
    
    private String tsCode;
    
    private StockSpan stockSpan;
    
    private Long beginPosition;
    
    private Long totalNeeds;
    
    private Function<List<EmoneyIndex>, Boolean> func;
    
    private IndexInfo[] indexInfos;
    
    @Setter(AccessLevel.PRIVATE)
    private String indexNames;
    
    public EmoneyIndexCallable setIndexInfos(IndexInfo[] indexInfos) {
        this.indexInfos = indexInfos;
        indexNames = Arrays.stream(indexInfos)
                .map(IndexInfo::getIndexName)
                .collect(Collectors.joining(", "));
        return this;
    }
    /**
     * 初始化一个 Callable
     * @param tsCode
     * @param beginPosition 起始位置，一般为当日或当时，如 20230921L
     * @param stockSpan 数据粒度
     * @param totalNeeds 经过计算需要的总数据数
     * @param func 消费 {@code List<EmoneyIndex>} 的方法，返回的布尔值辅助判断本次调用是否成功
     * @param indexInfos 指标类型数组
     */
    public EmoneyIndexCallable(
            String tsCode,
            Long beginPosition,
            StockSpan stockSpan,
            Long totalNeeds,
            Function<List<EmoneyIndex>, Boolean> func,
            IndexInfo...indexInfos) {
        
        this.tsCode = tsCode;
        this.beginPosition = beginPosition;
        this.stockSpan = stockSpan;
        setIndexInfos(indexInfos);
        this.totalNeeds = totalNeeds;
        this.func = func;
        
    }
    
    /**
     * 从 EmoneyIndexCallable 恢复 Callable
     * @param request
     */
    public EmoneyIndexCallable(EmoneyIndexCallable other) {
        this.tsCode = other.tsCode;
        this.beginPosition = other.beginPosition;
        this.stockSpan = other.stockSpan;
        this.indexInfos = other.indexInfos;
        this.indexNames = other.indexNames;
        this.totalNeeds = other.totalNeeds;
        this.func = other.func;
    }

    @Override
    public EmoneyCrawlerResult<EmoneyIndexCallable> call() throws Exception {

        List<EmoneyIndex> emoneyIndexList = new ArrayList<>();
        while (totalNeeds > 0) {

            // 组装请求
            /*
             * 2422 CANDLE_STICK_V3
             * 该种方式除返回指标和对应时间外，还会返回对应时间粒度的 K 线（蜡烛图），数据量会比 2921 大。
             * 使用 beginPosition, 如 20231230L 和 limitSize, 如 400 向前追溯
             */
            CandleStickWithIndex_Request request = new CandleStickWithIndex_Request();
            CandleStick_Request candleRequest = new CandleStick_Request();
            candleRequest.setBeginPosition(beginPosition)
                .setDataPeriod(stockSpan.getEmoneyCode())
                .setExFlag(0)
                .setGoodsId(StockCodeUtils.tsCodeToGoodsId(tsCode))
                .setLastVolume(0)
                .setLimitSize(Math.min(totalNeeds, LIMIT_SIZE))
                .setReqFhsp(false)
                .setReqHisShares(false);
            request.candleRequest = candleRequest;
            
            /*IndexInfo[] indexInfos = new IndexInfo[indexNames.length];
            
            for (int i = 0; i < indexNames.length; i++) {
                IndexInfo indexInfo = new IndexInfo().setIndexName(indexNames[i]);
                indexInfos[i] = indexInfo;
            }*/
            request.indexRequest = indexInfos;

            result = new EmoneyCrawlerResult<EmoneyIndexCallable>().setCallable(this).setSuccess(false);
                
            
            CandleStickNewWithIndexEx_Response response;
            try {
                // 时间戳作为 xRequestId
                response = EmoneyClient.post(
                        request, 
                        CandleStickNewWithIndexEx_Response.class, 
                        2422, 
                        System.currentTimeMillis());
            }
            catch (EmoneyRequestException e) {
                return result.setSuccess(false).setException(e);
            }
            
            log.info("Emoney index [{}] for {} called, totalNeeds left: {}, current received length/limitSize: {}/{}",
                    indexNames, tsCode, totalNeeds, response.kLines.length, LIMIT_SIZE);
            
            for (int i = 0; i < response.kLines.length; i++) {
                
                CandleStick candleStick = response.kLines[i];
                
                long dateTime = candleStick.getDatetime();
                LocalDateTime date = DateUtils.longDatetimeToLocalDateTime(dateTime);
                
                for (IndexInfo indexInfo : indexInfos) {
                    IndLine indLine = response.indexDatas.get(indexInfo.getIndexName());
                    for (outputline lineValue : indLine.lineValue) {
                        
                        EmoneyIndex emoneyIndex = new EmoneyIndex()
                                .setIndexName(indexInfo.getIndexName())
                                .setDate(date).setLineName(lineValue.getLineName())
                                .setLineShape(lineValue.getLineShape())
                                .setStockSpan(stockSpan)
                                .setTsCode(tsCode)
                                .setValue(lineValue.lineData[i]);
                        
                        emoneyIndexList.add(emoneyIndex);
                    }
                }
                
            }

            Thread.sleep(2500);
            
            // 判断数据是否结束了
            if (response.kLines.length < LIMIT_SIZE) {
                break;
            }

            totalNeeds -= LIMIT_SIZE;
            
            // 重设起始点为返回数据的最旧时间
            beginPosition = response.kLines[0].getDatetime();
        }
        Boolean b = true;
        if (!emoneyIndexList.isEmpty()) {
            try {
                 b = func.apply(emoneyIndexList);
            }
            catch (Exception e) {
                return result.setSuccess(false).setException(e);
            }
        }
        
        return result.setSuccess(b);
        
    }

}
