"""
PE / PB 估值因子。

基于日线收盘价 + 财务数据（EPS/每股净资产）计算。
"""

import pandas as pd

from factors.base import BaseFactor


class PEFactor(BaseFactor):
    """
    市盈率因子 = close / eps。

    eps 来自财务数据中的 'eps' 列或 TTM EPS。
    因子值越大表示估值越贵。
    """

    category = "fundamental"

    def __init__(self, financial_df: pd.DataFrame | None = None):
        """
        参数:
            financial_df: 含 'end_date' 和 'eps' 的 DataFrame。
        """
        self._financial_df = financial_df
        self.name = "pe"

    def calculate(self, df: pd.DataFrame) -> pd.Series:
        if self._financial_df is None or self._financial_df.empty:
            return pd.Series(float("nan"), index=df.index)

        eps_series = _map_to_daily(df, self._financial_df, "eps")
        close = df["close"]
        return close / eps_series.replace(0, float("nan"))

    def get_required_columns(self) -> list[str]:
        return ["close"]


class PBFactor(BaseFactor):
    """
    市净率因子 = close / bvps（每股净资产）。

    因子值越大表示估值越贵。
    """

    category = "fundamental"

    def __init__(self, financial_df: pd.DataFrame | None = None):
        self._financial_df = financial_df
        self.name = "pb"

    def calculate(self, df: pd.DataFrame) -> pd.Series:
        if self._financial_df is None or self._financial_df.empty:
            return pd.Series(float("nan"), index=df.index)

        bvps_series = _map_to_daily(df, self._financial_df, "bvps")
        return df["close"] / bvps_series.replace(0, float("nan"))

    def get_required_columns(self) -> list[str]:
        return ["close"]


class EPFactor(BaseFactor):
    """
    盈利收益率因子 = eps / close = 1 / PE。

    值越大表示估值越便宜，适合与动量等因子同向排序。
    """

    category = "fundamental"

    def __init__(self, financial_df: pd.DataFrame | None = None):
        self._financial_df = financial_df
        self.name = "ep"

    def calculate(self, df: pd.DataFrame) -> pd.Series:
        if self._financial_df is None or self._financial_df.empty:
            return pd.Series(float("nan"), index=df.index)

        eps_series = _map_to_daily(df, self._financial_df, "eps")
        return eps_series / df["close"].replace(0, float("nan")) * 100

    def get_required_columns(self) -> list[str]:
        return ["close"]


def _map_to_daily(
    daily_df: pd.DataFrame,
    fina_df: pd.DataFrame,
    column: str,
) -> pd.Series:
    """将季度财务数据填充到日线索引（前值填充）。"""
    fina = fina_df[["end_date", column]].dropna().copy()
    fina["end_date"] = fina["end_date"].astype(str)
    fina = fina.sort_values("end_date")

    result = pd.Series(float("nan"), index=daily_df.index)
    if fina.empty:
        return result

    dates = pd.to_datetime(daily_df.index, format="%Y%m%d", errors="coerce")
    fina_dates = pd.to_datetime(fina["end_date"], format="%Y%m%d", errors="coerce")

    for i, fina_date in enumerate(fina_dates):
        mask = dates >= fina_date
        if i + 1 < len(fina_dates):
            mask &= dates < fina_dates.iloc[i + 1]
        result[mask] = fina[column].iloc[i]

    return result.astype("float64")
