"""
FactorEngine — 因子计算引擎。

批量计算因子，处理技术/基本面/情绪因子的不同数据需求。
"""

import copy

import pandas as pd

from factors.base import BaseFactor
from factors.fundamental.roe import ROEFactor
from factors.fundamental.pe_pb import PEFactor, PBFactor, EPFactor

FUNDAMENTAL_FACTOR_TYPES = (ROEFactor, PEFactor, PBFactor, EPFactor)


def _is_sentiment(factor: BaseFactor) -> bool:
    return getattr(factor, "category", "") == "sentiment"


class FactorEngine:
    """因子计算引擎。"""

    def __init__(self, data_manager, sentiment_engine=None):
        """
        参数:
            data_manager: DataManager 实例。
            sentiment_engine: SentimentEngine 实例（可选，启用情绪因子时需提供）。
        """
        self._dm = data_manager
        self._sentiment_engine = sentiment_engine
        self._financial_cache: dict[str, pd.DataFrame] = {}

    def _get_financial(self, ts_code: str) -> pd.DataFrame:
        """获取财务数据（带缓存）。"""
        if ts_code not in self._financial_cache:
            df = self._dm.get_financial(ts_code)
            self._financial_cache[ts_code] = df
        return self._financial_cache[ts_code]

    def _resolve_factors(
        self, factors: list[BaseFactor], fina: pd.DataFrame
    ) -> list[BaseFactor]:
        """为每个股票 clone 基本面因子并注入财务数据。"""
        resolved = []
        for f in factors:
            if isinstance(f, FUNDAMENTAL_FACTOR_TYPES):
                f = copy.copy(f)
                f._financial_df = fina
            resolved.append(f)
        return resolved

    def compute(
        self,
        ts_code: str,
        factors: list[BaseFactor],
    ) -> pd.DataFrame:
        """
        对单只股票计算多个因子。

        参数:
            ts_code: 如 '000001.SZ'
            factors: 因子实例列表

        返回:
            DataFrame，index=trade_date，columns=因子名
        """
        if not factors:
            return pd.DataFrame()

        # 分离情绪因子（通过 SentimentEngine 处理）
        sent_factors = [f for f in factors if _is_sentiment(f)]
        other_factors = [f for f in factors if not _is_sentiment(f)]

        # 收集所有需要的列
        required_cols = set()
        has_fundamental = False
        for f in other_factors:
            required_cols.update(f.get_required_columns())
            if isinstance(f, FUNDAMENTAL_FACTOR_TYPES):
                has_fundamental = True

        # 获取日线数据
        daily = self._dm.get_daily(ts_code)
        if daily.empty:
            return pd.DataFrame()

        daily = daily.set_index("trade_date").sort_index()

        # 获取财务数据（如有基本面因子）
        fina = self._dm.get_financial(ts_code) if has_fundamental else pd.DataFrame()

        # 为当前股票解析因子（clone 基本面因子注入财务数据）
        resolved_factors = self._resolve_factors(other_factors, fina)

        # 逐因子计算
        results = {}
        for factor in resolved_factors:
            try:
                series = factor.calculate(daily)
                results[factor.name] = series.astype("float64")
            except Exception as e:
                print(f"[WARN] 因子 {factor.name} 计算失败 ({ts_code}): {e}")
                results[factor.name] = pd.Series(float("nan"), index=daily.index)

        # 情绪因子：通过 SentimentEngine 计算后合并
        if sent_factors and self._sentiment_engine:
            try:
                sent_df = self._sentiment_engine.compute(ts_code)
                for f in sent_factors:
                    if f.name in sent_df.columns:
                        results[f.name] = sent_df[f.name]
                    else:
                        results[f.name] = pd.Series(float("nan"), index=daily.index)
            except Exception as e:
                print(f"[WARN] 情绪因子计算失败 ({ts_code}): {e}")
                for f in sent_factors:
                    results[f.name] = pd.Series(float("nan"), index=daily.index)

        factor_df = pd.DataFrame(results)
        factor_df.index.name = "trade_date"
        return factor_df

    def compute_batch(
        self,
        ts_codes: list[str],
        factors: list[BaseFactor],
    ) -> dict[str, pd.DataFrame]:
        """
        批量计算多只股票的因子。

        返回:
            {ts_code: factor_df}
        """
        results = {}
        total = len(ts_codes)
        for i, ts_code in enumerate(ts_codes):
            try:
                results[ts_code] = self.compute(ts_code, factors)
            except Exception as e:
                print(f"[WARN] {ts_code} 因子计算失败: {e}")
                results[ts_code] = pd.DataFrame()
            if (i + 1) % 50 == 0:
                print(f"[FactorEngine] 进度: {i + 1}/{total}")
        return results

    def compute_universe(
        self,
        factors: list[BaseFactor],
        date: str,
        ts_codes: list[str] | None = None,
    ) -> pd.DataFrame:
        """
        计算全市场某一天的因子截面。

        参数:
            factors: 因子列表
            date: 目标日期 'YYYYMMDD'
            ts_codes: 股票列表，None 表示全部

        返回:
            DataFrame，index=ts_code，columns=因子名
        """
        if ts_codes is None:
            stocks = self._dm.get_stock_list()
            ts_codes = list(stocks.index)

        rows = []
        for ts_code in ts_codes:
            daily = self._dm.get_daily(ts_code)
            if daily.empty:
                continue
            daily = daily.set_index("trade_date")
            if date not in daily.index:
                continue

            row = {"ts_code": ts_code}
            fina = self._get_financial(ts_code)
            resolved = self._resolve_factors(factors, fina)
            for factor in resolved:
                try:
                    series = factor.calculate(daily)
                    row[factor.name] = series.get(date, float("nan"))
                except Exception:
                    row[factor.name] = float("nan")
            rows.append(row)

        if not rows:
            return pd.DataFrame()
        result = pd.DataFrame(rows).set_index("ts_code")
        return result
