package com.lanyuanxiaoyao.leopard.core.task;

import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.lanyuanxiaoyao.leopard.core.entity.FinanceIndicator;
import com.lanyuanxiaoyao.leopard.core.entity.QFinanceIndicator;
import com.lanyuanxiaoyao.leopard.core.entity.Stock;
import com.lanyuanxiaoyao.leopard.core.helper.NumberHelper;
import com.lanyuanxiaoyao.leopard.core.repository.FinanceIndicatorRepository;
import com.lanyuanxiaoyao.leopard.core.repository.StockRepository;
import com.lanyuanxiaoyao.leopard.core.service.TuShareService;
import java.time.LocalDate;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

/**
 * 更新财务指标数据
 *
 * @author lanyuanxiaoyao
 * @version 20250924
 */
@Slf4j
@Component
public class UpdateFinanceIndicatorTask extends TaskRunner {
  private final StockRepository stockRepository;
  private final FinanceIndicatorRepository financeIndicatorRepository;

  private final TuShareService tuShareService;

  protected UpdateFinanceIndicatorTask(ApplicationContext context, StockRepository stockRepository, FinanceIndicatorRepository financeIndicatorRepository, TuShareService tuShareService) {
    super(context);
    this.stockRepository = stockRepository;
    this.financeIndicatorRepository = financeIndicatorRepository;
    this.tuShareService = tuShareService;
  }

  @Transactional(rollbackFor = Throwable.class)
  @Override
  public String process(Map<String, Object> params, StepUpdater updater) throws JsonProcessingException {
    var stocks = stockRepository.findAll();
    var currentYear = LocalDate.now().getYear();
    for (int year = 1990; year < currentYear; year++) {
      var balances = tuShareService.request(
        "balancesheet_vip",
        Map.of("period", LocalDate.of(year, 12, 31).format(TuShareService.TRADE_FORMAT)),
        List.of(
          "ts_code",
          "total_share",
          "cap_rese",
          "surplus_rese",
          "undistr_porfit",
          "cash_reser_cb",
          "accounts_receiv_bill",
          "accounts_pay",
          "inventories",
          "goodwill",
          "total_cur_assets",
          "total_nca",
          "total_cur_liab",
          "total_ncl",
          "total_liab",
          "total_hldr_eqy_inc_min_int",
          "total_assets"
        )
      );
      var balancesMap = balances.stream().collect(Collectors.toMap(
        map -> map.get("ts_code"),
        map -> map,
        (existing, replacement) -> existing
      ));
      var incomes = tuShareService.request(
        "income_vip",
        Map.of("period", LocalDate.of(year, 12, 31).format(TuShareService.TRADE_FORMAT)),
        List.of(
          "ts_code",
          "total_revenue",
          "total_cogs",
          "operate_profit",
          "oper_exp",
          "n_income"
        )
      );
      var incomesMap = incomes.stream().collect(Collectors.toMap(
        map -> map.get("ts_code"),
        map -> map,
        (existing, replacement) -> existing
      ));
      var cashFlows = tuShareService.request(
        "cashflow_vip",
        Map.of("period", LocalDate.of(year, 12, 31).format(TuShareService.TRADE_FORMAT)),
        List.of(
          "ts_code",
          "n_cashflow_act",
          "n_cashflow_inv_act",
          "n_cash_flows_fnc_act"
        )
      );
      var cashFlowsMap = cashFlows.stream().collect(Collectors.toMap(
        map -> map.get("ts_code"),
        map -> map,
        (existing, replacement) -> existing
      ));
      var finaIndicators = tuShareService.request(
        "fina_indicator_vip",
        Map.of("period", LocalDate.of(year, 12, 31).format(TuShareService.TRADE_FORMAT)),
        List.of(
          "ts_code",
          "ca_to_assets",
          "nca_to_assets",
          "currentdebt_to_debt",
          "longdeb_to_debt",
          "current_ratio",
          "quick_ratio",
          "ar_turn",
          "arturn_days",
          "inv_turn",
          "invturn_days",
          "fa_turn",
          "assets_turn",
          "roe_dt",
          "roa",
          "roa_dp",
          "total_revenue_ps"
        )
      );
      var finaIndicatorsMap = finaIndicators.stream().collect(Collectors.toMap(
        map -> map.get("ts_code"),
        map -> map,
        (existing, replacement) -> existing
      ));

      var financeIndicatorsMap = financeIndicatorRepository.findAll(QFinanceIndicator.financeIndicator.year.eq(year))
        .stream()
        .collect(Collectors.toMap(
          indicator -> indicator.getStock().getCode(),
          indicator -> indicator
        ));

      for (Stock stock : stocks) {
        var balance = balancesMap.get(stock.getCode());
        var income = incomesMap.get(stock.getCode());
        var cashFlow = cashFlowsMap.get(stock.getCode());
        var finaIndicator = finaIndicatorsMap.get(stock.getCode());
        if (stock.getListedDate().getYear() > year || ArrayUtil.<Object>isAllNull(balance, income, cashFlow, finaIndicator)) {
          continue;
        }
        var indicator = financeIndicatorsMap.getOrDefault(stock.getCode(), new FinanceIndicator());
        indicator.setStock(stock);
        indicator.setYear(year);
        if (ObjectUtil.isNotNull(balance)) {
          indicator.setTotalAssets(NumberHelper.parseDouble(balance.get("total_assets")));
          indicator.setTotalShareCapital(NumberHelper.parseDouble(balance.get("total_share")));
          indicator.setCapitalSurplus(NumberHelper.parseDouble(balance.get("cap_rese")));
          indicator.setSurplusReserve(NumberHelper.parseDouble(balance.get("surplus_rese")));
          indicator.setUndistributedProfit(NumberHelper.parseDouble(balance.get("undistr_porfit")));
          indicator.setCashAndCashEquivalents(NumberHelper.parseDouble(balance.get("cash_reser_cb")));
          indicator.setCashAndCashEquivalentsToTotalAssetsRatio(NumberHelper.safeDiv(indicator.getCashAndCashEquivalents(), indicator.getTotalAssets()));
          indicator.setAccountsReceivable(NumberHelper.parseDouble(balance.get("accounts_receiv_bill")));
          indicator.setAccountsReceivableToTotalAssetsRatio(NumberHelper.safeDiv(indicator.getAccountsReceivable(), indicator.getTotalAssets()));
          indicator.setAccountsPayable(NumberHelper.parseDouble(balance.get("accounts_pay")));
          indicator.setAccountsPayableToTotalAssetsRatio(NumberHelper.safeDiv(indicator.getAccountsPayable(), indicator.getTotalAssets()));
          indicator.setInventory(NumberHelper.parseDouble(balance.get("inventories")));
          indicator.setInventoryToTotalAssetsRatio(NumberHelper.safeDiv(indicator.getInventory(), indicator.getTotalAssets()));
          indicator.setGoodwill(NumberHelper.parseDouble(balance.get("goodwill")));
          indicator.setGoodwillToTotalAssetsRatio(NumberHelper.safeDiv(indicator.getGoodwill(), indicator.getTotalAssets()));
          indicator.setCurrentAssets(NumberHelper.parseDouble(balance.get("total_cur_assets")));
          indicator.setCurrentAssetsToTotalAssetsRatio(NumberHelper.safeDiv(indicator.getCurrentAssets(), indicator.getTotalAssets()));
          indicator.setFixedAssets(NumberHelper.parseDouble(balance.get("total_nca")));
          indicator.setFixedAssetsToTotalAssetsRatio(NumberHelper.safeDiv(indicator.getFixedAssets(), indicator.getTotalAssets()));
          indicator.setTotalLiabilities(NumberHelper.parseDouble(balance.get("total_liab")));
          indicator.setCurrentLiabilities(NumberHelper.parseDouble(balance.get("total_cur_liab")));
          indicator.setCurrentLiabilitiesToTotalAssetsRatio(NumberHelper.safeDiv(indicator.getCurrentLiabilities(), indicator.getTotalAssets()));
          indicator.setCurrentLiabilitiesToTotalLiabilitiesRatio(NumberHelper.safeDiv(indicator.getCurrentLiabilities(), indicator.getTotalLiabilities()));
          indicator.setLongTermLiabilities(NumberHelper.parseDouble(balance.get("total_ncl")));
          indicator.setLongTermLiabilitiesToTotalAssetsRatio(NumberHelper.safeDiv(indicator.getLongTermLiabilities(), indicator.getTotalAssets()));
          indicator.setLongTermLiabilitiesToTotalLiabilitiesRatio(NumberHelper.safeDiv(indicator.getLongTermLiabilities(), indicator.getTotalLiabilities()));
          indicator.setLiabilitiesToTotalAssetsRatio(NumberHelper.safeDiv(indicator.getTotalLiabilities(), indicator.getTotalAssets()));
          indicator.setShareholdersEquity(NumberHelper.parseDouble(balance.get("total_hldr_eqy_inc_min_int")));
          indicator.setShareholdersEquityToTotalAssetsRatio(NumberHelper.safeDiv(indicator.getShareholdersEquity(), indicator.getTotalAssets()));
        }
        if (ObjectUtil.isNotNull(income)) {
          indicator.setOperatingRevenue(NumberHelper.parseDouble(income.get("total_revenue")));
          indicator.setOperatingCost(NumberHelper.parseDouble(income.get("total_cogs")));
          indicator.setOperatingProfit(NumberHelper.parseDouble(income.get("operate_profit")));
          indicator.setOperatingExpenses(NumberHelper.parseDouble(income.get("oper_exp")));
          indicator.setNetProfit(NumberHelper.parseDouble(income.get("n_income")));
        }
        if (ObjectUtil.isNotNull(cashFlow)) {
          indicator.setCashFlowFromOperatingActivities(NumberHelper.parseDouble(cashFlow.get("n_cashflow_act")));
          indicator.setCashFlowFromInvestingActivities(NumberHelper.parseDouble(cashFlow.get("n_cashflow_inv_act")));
          indicator.setCashFlowFromFinancingActivities(NumberHelper.parseDouble(cashFlow.get("n_cash_flows_fnc_act")));
        }
        if (ObjectUtil.isNotNull(finaIndicator)) {
          indicator.setCurrentRatio(NumberHelper.parseDouble(finaIndicator.get("current_ratio")));
          indicator.setQuickRatio(NumberHelper.parseDouble(finaIndicator.get("quick_ratio")));
          indicator.setAccountsReceivableTurnover(NumberHelper.parseDouble(finaIndicator.get("ar_turn")));
          indicator.setDaysAccountsReceivableTurnover(NumberHelper.parseDouble(finaIndicator.get("arturn_days")));
          indicator.setInventoryTurnover(NumberHelper.parseDouble(finaIndicator.get("inv_turn")));
          indicator.setDaysInventoryTurnover(NumberHelper.parseDouble(finaIndicator.get("invturn_days")));
          indicator.setFixedAssetsTurnover(NumberHelper.parseDouble(finaIndicator.get("fa_turn")));
          indicator.setDaysFixedAssetsTurnover(NumberHelper.safeDiv(360.0, indicator.getFixedAssetsTurnover()));
          indicator.setTotalAssetsTurnover(NumberHelper.parseDouble(finaIndicator.get("assets_turn")));
          indicator.setDaysTotalAssetsTurnover(NumberHelper.safeDiv(360.0, indicator.getTotalAssetsTurnover()));
          indicator.setReturnOnEquity(NumberHelper.parseDouble(finaIndicator.get("roe_dt")));
          indicator.setReturnOnAssets(NumberHelper.parseDouble(finaIndicator.get("roa")));
          indicator.setOperatingGrossProfitMargin(NumberHelper.safeDiv(NumberHelper.safeMinus(indicator.getOperatingRevenue(), indicator.getOperatingCost()), indicator.getOperatingRevenue()));
          indicator.setOperatingProfitMargin(NumberHelper.safeDiv(indicator.getOperatingProfit(), indicator.getOperatingRevenue()));
          indicator.setOperatingSafetyMarginRatio(NumberHelper.safeDiv(indicator.getOperatingProfitMargin(), indicator.getOperatingGrossProfitMargin()));
          indicator.setNetProfitMargin(NumberHelper.parseDouble(finaIndicator.get("roa_dp")));
          indicator.setEarningsPerShare(NumberHelper.parseDouble(finaIndicator.get("total_revenue_ps")));
        }
        financeIndicatorRepository.save(indicator);
      }

      updater.update((year - 1990) * 1.0 / (currentYear - 1990));
    }
    return null;
  }
}
