package com.lanyuanxiaoyao.leopard.server.service.task;

import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.lanyuanxiaoyao.leopard.core.entity.FinanceIndicator;
import com.lanyuanxiaoyao.leopard.core.entity.Stock;
import com.lanyuanxiaoyao.leopard.core.entity.StockCollection;
import com.lanyuanxiaoyao.leopard.core.repository.StockCollectionRepository;
import com.lanyuanxiaoyao.leopard.core.repository.StockRepository;
import com.yomahub.liteflow.annotation.LiteflowComponent;
import com.yomahub.liteflow.core.NodeComponent;
import java.time.LocalDate;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.math3.stat.descriptive.DescriptiveStatistics;

/**
 * 金字塔选股
 *
 * @author lanyuanxiaoyao
 * @version 20250917
 */
@Slf4j
@LiteflowComponent("pyramid_stock_selector")
public class PyramidStockSelector extends NodeComponent {
  private final StockRepository stockRepository;
  private final StockCollectionRepository stockCollectionRepository;

  public PyramidStockSelector(StockRepository stockRepository, StockCollectionRepository stockCollectionRepository) {
    this.stockRepository = stockRepository;
    this.stockCollectionRepository = stockCollectionRepository;
  }

  @Override
  public void process() {
    // 选择至少有最近5年财报的股票
    var stocks = stockRepository.findAllByIndicatorsSizeGreaterThanEqual(5);
    var stocksMap = stocks.stream().collect(Collectors.toMap(Stock::getCode, stock -> stock));
    var scores = stocks.stream().map(Stock::getCode).collect(Collectors.toMap(code -> code, code -> 0));
    for (Stock stock : stocks) {
      var recentIndicators = stock.getIndicators()
        .stream()
        .sorted((a, b) -> b.getYear() - a.getYear())
        .limit(5)
        .toList();
      var latestIndicator = recentIndicators.getFirst();

      var roeScore = 0;
      if (recentIndicators.stream().noneMatch(indicator -> indicator.getReturnOnEquity() == null || indicator.getReturnOnEquity() < 0)) {
        var averageRoe = recentIndicators.stream()
          .map(FinanceIndicator::getReturnOnEquity)
          .map(item -> ObjectUtil.defaultIfNull(item, 0.0))
          .mapToDouble(Double::doubleValue)
          .average()
          .orElse(0.0);
        if (averageRoe >= 35) {
          roeScore = 550;
        } else if (averageRoe >= 30) {
          roeScore = 500;
        } else if (averageRoe >= 25) {
          roeScore = 450;
        } else if (averageRoe >= 20) {
          roeScore = 400;
        } else if (averageRoe >= 15) {
          roeScore = 350;
        } else if (averageRoe >= 10) {
          roeScore = 300;
        }
      }
      scores.put(stock.getCode(), scores.get(stock.getCode()) + roeScore);

      var roaScore = 0;
      if (recentIndicators.stream().noneMatch(indicator -> indicator.getReturnOnAssets() == null)) {
        var averageRoa = recentIndicators.stream()
          .map(FinanceIndicator::getReturnOnAssets)
          .mapToDouble(Double::doubleValue)
          .average()
          .orElse(0.0);
        if (averageRoa >= 15) {
          roaScore = 100;
        } else if (averageRoa >= 11) {
          roaScore = 80;
        } else if (averageRoa >= 7) {
          roaScore = 50;
        }
      }
      scores.put(stock.getCode(), scores.get(stock.getCode()) + roaScore);

      var netProfitScore = 0;
      if (recentIndicators.stream().noneMatch(indicator -> indicator.getNetProfit() == null)) {
        var averageNetProfit = recentIndicators.stream()
          .map(FinanceIndicator::getNetProfit)
          .mapToDouble(Double::doubleValue)
          .average()
          .orElse(0.0);
        if (averageNetProfit >= 10000.0 * 10000000) {
          netProfitScore = 150;
        } else if (averageNetProfit >= 1000.0 * 10000000) {
          netProfitScore = 100;
        }
      }
      scores.put(stock.getCode(), scores.get(stock.getCode()) + netProfitScore);

      var cashScore = 0;
      if (
        ArrayUtil.isAllNotNull(latestIndicator.getTotalAssetsTurnover(), latestIndicator.getCashAndCashEquivalentsToTotalAssetsRatio())
          && (
          latestIndicator.getTotalAssetsTurnover() > 0.8 && latestIndicator.getCashAndCashEquivalentsToTotalAssetsRatio() >= 0.1
            || latestIndicator.getTotalAssetsTurnover() <= 0.8 && latestIndicator.getCashAndCashEquivalentsToTotalAssetsRatio() >= 0.2
        )
      ) {
        cashScore = 50;
      }
      scores.put(stock.getCode(), scores.get(stock.getCode()) + cashScore);

      if (ObjectUtil.isNotNull(latestIndicator.getDaysAccountsReceivableTurnover()) && latestIndicator.getDaysAccountsReceivableTurnover() <= 30) {
        scores.put(stock.getCode(), scores.get(stock.getCode()) + 20);
      }
      if (ObjectUtil.isNotNull(latestIndicator.getDaysInventoryTurnover()) && latestIndicator.getDaysInventoryTurnover() <= 30) {
        scores.put(stock.getCode(), scores.get(stock.getCode()) + 20);
      }
      if (ArrayUtil.isAllNotNull(latestIndicator.getDaysAccountsReceivableTurnover(), latestIndicator.getDaysInventoryTurnover())) {
        if (latestIndicator.getDaysAccountsReceivableTurnover() + latestIndicator.getDaysInventoryTurnover() <= 40) {
          scores.put(stock.getCode(), scores.get(stock.getCode()) + 20);
        } else if (latestIndicator.getDaysAccountsReceivableTurnover() + latestIndicator.getDaysInventoryTurnover() <= 60) {
          scores.put(stock.getCode(), scores.get(stock.getCode()) + 10);
        }
      }

      if (recentIndicators.stream().noneMatch(indicator -> indicator.getOperatingGrossProfitMargin() == null)) {
        var stat = new DescriptiveStatistics();
        recentIndicators.stream()
          .map(FinanceIndicator::getOperatingGrossProfitMargin)
          .mapToDouble(Double::doubleValue)
          .forEach(stat::addValue);
        if (stat.getStandardDeviation() <= 0.3) {
          scores.put(stock.getCode(), scores.get(stock.getCode()) + 50);
        }
      }

      var operatingSafeMarginScore = 0;
      if (ObjectUtil.isNotNull(latestIndicator.getOperatingSafetyMarginRatio())) {
        if (latestIndicator.getOperatingSafetyMarginRatio() >= 70) {
          operatingSafeMarginScore = 50;
        } else if (latestIndicator.getOperatingSafetyMarginRatio() >= 50) {
          operatingSafeMarginScore = 30;
        } else if (latestIndicator.getOperatingSafetyMarginRatio() >= 30) {
          operatingSafeMarginScore = 10;
        }
      }
      scores.put(stock.getCode(), scores.get(stock.getCode()) + operatingSafeMarginScore);

      var netProfitAscendingScore = 0;
      if (recentIndicators.stream().noneMatch(indicator -> indicator.getNetProfit() == null)) {
        if (recentIndicators.get(0).getNetProfit() > recentIndicators.get(1).getNetProfit()) {
          netProfitAscendingScore += 30;
        } else {
          netProfitAscendingScore -= 30;
        }
        if (recentIndicators.get(1).getNetProfit() > recentIndicators.get(2).getNetProfit()) {
          netProfitAscendingScore += 25;
        } else {
          netProfitAscendingScore -= 25;
        }

        if (recentIndicators.get(2).getNetProfit() > recentIndicators.get(3).getNetProfit()) {
          netProfitAscendingScore += 20;
        } else {
          netProfitAscendingScore -= 20;
        }

        if (recentIndicators.get(3).getNetProfit() > recentIndicators.get(4).getNetProfit()) {
          netProfitAscendingScore += 15;
        } else {
          netProfitAscendingScore -= 15;
        }
      }
      scores.put(stock.getCode(), scores.get(stock.getCode()) + netProfitAscendingScore);

      var cashAscendingScore = 0;
      if (recentIndicators.stream().noneMatch(indicator -> indicator.getCashAndCashEquivalents() == null)) {
        if (recentIndicators.get(0).getCashAndCashEquivalents() > recentIndicators.get(1).getCashAndCashEquivalents()) {
          cashAscendingScore += 30;
        } else {
          cashAscendingScore -= 30;
        }

        if (recentIndicators.get(1).getCashAndCashEquivalents() > recentIndicators.get(2).getCashAndCashEquivalents()) {
          cashAscendingScore += 25;
        } else {
          cashAscendingScore -= 25;
        }

        if (recentIndicators.get(2).getCashAndCashEquivalents() > recentIndicators.get(3).getCashAndCashEquivalents()) {
          cashAscendingScore += 20;
        } else {
          cashAscendingScore -= 20;
        }

        if (recentIndicators.get(3).getCashAndCashEquivalents() > recentIndicators.get(4).getCashAndCashEquivalents()) {
          cashAscendingScore += 15;
        } else {
          cashAscendingScore -= 15;
        }
      }
      scores.put(stock.getCode(), scores.get(stock.getCode()) + cashAscendingScore);
    }
    var first50 = scores.entrySet()
      .stream()
      .sorted((e1, e2) -> e2.getValue() - e1.getValue())
      .limit(50)
      .map(entry -> stocksMap.get(entry.getKey()))
      .collect(Collectors.toSet());
    var collection = new StockCollection();
    collection.setName(StrUtil.format("金字塔选股 ({})", LocalDate.now()));
    collection.setDescription("");
    collection.setStocks(first50);
    stockCollectionRepository.save(collection);
  }
}
