import math
from datetime import date, datetime
from typing import Any

import polars as pl


CB_REALTIME_DERIVED_REQUIRED_COLUMNS = {
    "cb_ytm",
    "interst_list",
    "maturity_put_price",
    "conv_end_date",
    "redeem_price_rate",
    "redeem_count_days",
    "redeem_total_days",
    "conv_start_date",
    "redeem_trade_date_window",
    "stk_close_window",
    "conv_price_window",
    "call_event_status_window",
    "call_event_ann_date_window",
    "call_event_recal_date_window",
    "call_event_reg_date_window",
    "call_event_price_window",
    "call_event_priority_window",
}

_FORCE_REDEEM_SPECIAL_STATUSES = {
    "公告不强赎",
    "公告提示强赎",
    "公告强赎",
    "已公告强赎",
    "公告实施强赎",
    "公告到期赎回",
    "未到转股期",
}

_SINGLE_CASHFLOW_STATUSES = {
    "公告实施强赎",
    "公告提示强赎",
    "公告到期赎回",
}


def _to_date(value: Any) -> date | None:
    if value is None:
        return None
    if isinstance(value, datetime):
        return value.date()
    if isinstance(value, date):
        return value
    return datetime.fromisoformat(str(value)).date()


def _safe_float(value: Any) -> float | None:
    if value is None:
        return None
    try:
        result = float(value)
    except (TypeError, ValueError):
        return None
    if math.isnan(result):
        return None
    return result


def _safe_int(value: Any) -> int | None:
    if value is None:
        return None
    try:
        return int(value)
    except (TypeError, ValueError):
        return None


def calculate_cb_left_years(conv_end_date: Any, valuation_date: date) -> float | None:
    end_date = _to_date(conv_end_date)
    if end_date is None:
        return None
    return max((end_date - valuation_date).days + 1, 0) / 365


def calculate_cb_effective_maturity(row: dict[str, Any]) -> dict[str, Any]:
    conv_end_date = _to_date(row.get("conv_end_date"))
    maturity_put_price = _safe_float(row.get("maturity_put_price"))
    event_statuses = row.get("call_event_status_window") or []
    event_reg_dates = row.get("call_event_reg_date_window") or []
    event_prices = row.get("call_event_price_window") or []

    effective_conv_end_date = conv_end_date
    effective_maturity_put_price = maturity_put_price
    for index, status in enumerate(event_statuses):
        if status not in _SINGLE_CASHFLOW_STATUSES:
            continue
        event_reg_date = _to_date(
            event_reg_dates[index] if index < len(event_reg_dates) else None
        )
        event_price = _safe_float(event_prices[index] if index < len(event_prices) else None)
        if event_reg_date is not None:
            effective_conv_end_date = event_reg_date
        if event_price is not None:
            effective_maturity_put_price = event_price

    return {
        "_cb_effective_conv_end_date": effective_conv_end_date,
        "_cb_effective_maturity_put_price": effective_maturity_put_price,
    }


def calculate_cb_future_cashflows(
    row: dict[str, Any],
    valuation_date: date,
) -> list[tuple[date, float]]:
    effective_maturity = calculate_cb_effective_maturity(row)
    effective_conv_end_date = effective_maturity["_cb_effective_conv_end_date"]
    effective_maturity_put_price = effective_maturity["_cb_effective_maturity_put_price"]
    event_statuses = row.get("call_event_status_window") or []

    if any(status in _SINGLE_CASHFLOW_STATUSES for status in event_statuses):
        if effective_conv_end_date is None or effective_maturity_put_price is None:
            return []
        if effective_conv_end_date <= valuation_date:
            return []
        return [(effective_conv_end_date, effective_maturity_put_price)]

    pay_interest_day_list = row.get("pay_interest_day_list") or []
    interest_list = row.get("interst_list") or []
    maturity_put_price = _safe_float(row.get("maturity_put_price"))
    if not pay_interest_day_list or not interest_list or maturity_put_price is None:
        return []

    cash_list = [_safe_float(value) for value in interest_list]
    if any(value is None for value in cash_list):
        return []
    cash_list[-1] = maturity_put_price

    cashflows: list[tuple[date, float]] = []
    for pay_date_value, cash in zip(pay_interest_day_list, cash_list, strict=False):
        pay_date = _to_date(pay_date_value)
        if pay_date is None or pay_date <= valuation_date:
            continue
        cashflows.append((pay_date, cash))
    return cashflows


def calculate_cb_pure_value(row: dict[str, Any]) -> float | None:
    cb_ytm = _safe_float(row.get("cb_ytm"))
    valuation_date = _to_date(row.get("valuation_date"))
    if cb_ytm is None or valuation_date is None:
        return None

    cashflows = calculate_cb_future_cashflows(row, valuation_date)
    if cashflows:
        return sum(
            cash / ((1 + cb_ytm / 100) ** ((cashflow_date - valuation_date).days / 365))
            for cashflow_date, cash in cashflows
        )

    left_years = _safe_float(row.get("left_years"))
    maturity_put_price = _safe_float(row.get("maturity_put_price"))
    interest_list = row.get("interst_list")
    if left_years is None or maturity_put_price is None or not interest_list:
        return None

    cash_list = [_safe_float(value) for value in interest_list]
    if any(value is None for value in cash_list):
        return None
    count = min(math.ceil(left_years), len(cash_list))
    if count <= 0:
        return None

    cash_list[-1] = maturity_put_price
    remains = [left_years - offset for offset in range(count - 1, -1, -1)]
    return sum(
        cash / ((1 + cb_ytm / 100) ** remain)
        for cash, remain in zip(cash_list[-count:], remains, strict=True)
    )


def _active_force_redeem_event(row: dict[str, Any], valuation_date: date) -> str | None:
    event_statuses = row.get("call_event_status_window") or []
    event_ann_dates = row.get("call_event_ann_date_window") or []
    event_recal_dates = row.get("call_event_recal_date_window") or []
    event_priorities = row.get("call_event_priority_window") or []

    active_status: str | None = None
    active_priority = 0
    for index, status in enumerate(event_statuses):
        ann_date = _to_date(event_ann_dates[index] if index < len(event_ann_dates) else None)
        recal_date = _to_date(
            event_recal_dates[index] if index < len(event_recal_dates) else None
        )
        priority = _safe_int(event_priorities[index] if index < len(event_priorities) else None) or 0
        if ann_date is None or ann_date > valuation_date:
            continue
        if status == "公告不强赎" and recal_date is not None and valuation_date >= recal_date:
            continue
        if priority >= active_priority:
            active_status = status
            active_priority = priority
    return active_status


def _latest_non_call_reset_date(row: dict[str, Any], valuation_date: date) -> date | None:
    event_statuses = row.get("call_event_status_window") or []
    event_recal_dates = row.get("call_event_recal_date_window") or []

    reset_dates: list[date] = []
    for index, status in enumerate(event_statuses):
        if status != "公告不强赎":
            continue
        recal_date = _to_date(
            event_recal_dates[index] if index < len(event_recal_dates) else None
        )
        if recal_date is not None and recal_date <= valuation_date:
            reset_dates.append(recal_date)
    if not reset_dates:
        return None
    return max(reset_dates)


def _force_redeem_flags(row: dict[str, Any], valuation_date: date) -> list[tuple[date, bool]]:
    trade_dates = row.get("redeem_trade_date_window") or []
    stock_closes = row.get("stk_close_window") or []
    conv_prices = row.get("conv_price_window") or []
    redeem_price_rate = _safe_float(row.get("redeem_price_rate"))
    if redeem_price_rate is None:
        return []

    flags: list[tuple[date, bool]] = []
    for index, trade_date_value in enumerate(trade_dates):
        trade_date = _to_date(trade_date_value)
        stock_close = _safe_float(stock_closes[index] if index < len(stock_closes) else None)
        conv_price = _safe_float(conv_prices[index] if index < len(conv_prices) else None)
        if trade_date is None or trade_date > valuation_date:
            continue
        if stock_close is None or conv_price is None:
            continue
        force_redeem_price = round(conv_price * redeem_price_rate, 3)
        flags.append((trade_date, stock_close >= force_redeem_price))

    realtime_stock_close = _safe_float(row.get("close_stk"))
    realtime_conv_price = _safe_float(row.get("conv_price"))
    if realtime_stock_close is not None and realtime_conv_price is not None:
        realtime_flag = realtime_stock_close >= round(realtime_conv_price * redeem_price_rate, 3)
        if flags and flags[-1][0] == valuation_date:
            flags[-1] = (valuation_date, realtime_flag)
        else:
            flags.append((valuation_date, realtime_flag))
    return flags


def calculate_cb_redeem_state(row: dict[str, Any]) -> dict[str, Any]:
    valuation_date = _to_date(row.get("valuation_date"))
    if valuation_date is None:
        return {
            "is_call": None,
            "redeem_real_days": None,
            "redeem_remain_days": None,
        }

    redeem_count_days = _safe_int(row.get("redeem_count_days"))
    redeem_total_days = _safe_int(row.get("redeem_total_days"))
    conv_start_date = _to_date(row.get("conv_start_date"))
    active_status = _active_force_redeem_event(row, valuation_date)
    if conv_start_date is not None and valuation_date < conv_start_date:
        active_status = "未到转股期"
    historical_redeem_window_empty = not (row.get("redeem_trade_date_window") or [])
    flags = _force_redeem_flags(row, valuation_date)

    if active_status in _FORCE_REDEEM_SPECIAL_STATUSES:
        return {
            "is_call": active_status,
            "redeem_real_days": (
                None
                if active_status == "未到转股期" and historical_redeem_window_empty
                else 0
            ),
            "redeem_remain_days": None,
        }

    if redeem_count_days is None or redeem_total_days is None:
        return {
            "is_call": "强赎条款缺失",
            "redeem_real_days": None,
            "redeem_remain_days": None,
        }

    reset_date = _latest_non_call_reset_date(row, valuation_date)
    if conv_start_date is not None:
        flags = [(trade_date, flag) for trade_date, flag in flags if trade_date >= conv_start_date]
    if reset_date is not None:
        flags = [(trade_date, flag) for trade_date, flag in flags if trade_date >= reset_date]
    flags = flags[-redeem_total_days:]
    redeem_real_days = sum(1 for _, flag in flags if flag)
    redeem_remain_days = calculate_cb_redeem_remain_days(
        [flag for _, flag in flags],
        redeem_count_days,
        redeem_total_days,
    )
    if redeem_real_days >= redeem_count_days:
        is_call = "已满足强赎条件"
    else:
        is_call = f"{redeem_real_days}/{redeem_count_days}/{redeem_total_days}"

    return {
        "is_call": is_call,
        "redeem_real_days": redeem_real_days,
        "redeem_remain_days": redeem_remain_days,
    }


def calculate_cb_redeem_remain_days(
    redeem_flags: list[bool],
    redeem_count_days: int,
    redeem_total_days: int,
) -> int:
    for future_days in range(redeem_count_days + 1):
        simulated_flags = (redeem_flags + [True] * future_days)[-redeem_total_days:]
        if sum(1 for flag in simulated_flags if flag) >= redeem_count_days:
            return future_days
    return redeem_count_days


def plan_cb_realtime_derived_factors(
    lf: pl.LazyFrame,
    valuation_date: date,
) -> pl.LazyFrame:
    schema_names = set(lf.collect_schema().names())
    if not CB_REALTIME_DERIVED_REQUIRED_COLUMNS.issubset(schema_names):
        return lf
    if "pay_interest_day_list" not in schema_names:
        lf = lf.with_columns(
            pl.lit(None, dtype=pl.List(pl.Date)).alias("pay_interest_day_list")
        )

    redeem_state_dtype = pl.Struct(
        {
            "is_call": pl.String,
            "redeem_real_days": pl.Int64,
            "redeem_remain_days": pl.Int64,
        }
    )
    effective_maturity_dtype = pl.Struct(
        {
            "_cb_effective_conv_end_date": pl.Date,
            "_cb_effective_maturity_put_price": pl.Float64,
        }
    )
    return (
        lf.with_columns(
            pl.struct(
                [
                    "conv_end_date",
                    "maturity_put_price",
                    "call_event_status_window",
                    "call_event_reg_date_window",
                    "call_event_price_window",
                ]
            )
            .map_elements(calculate_cb_effective_maturity, return_dtype=effective_maturity_dtype)
            .alias("_cb_effective_maturity")
        )
        .unnest("_cb_effective_maturity")
        .with_columns(
            pl.struct(["_cb_effective_conv_end_date"])
            .map_elements(
                lambda row: calculate_cb_left_years(
                    row["_cb_effective_conv_end_date"],
                    valuation_date,
                ),
                return_dtype=pl.Float64,
            )
            .alias("left_years"),
        )
        .with_columns(
            pl.struct(
                [
                    "left_years",
                    "pay_interest_day_list",
                    "interst_list",
                    "conv_end_date",
                    "_cb_effective_maturity_put_price",
                    "cb_ytm",
                    "call_event_status_window",
                    "call_event_reg_date_window",
                    "call_event_price_window",
                ]
            )
            .map_elements(
                lambda row: calculate_cb_pure_value(
                    {
                        "left_years": row["left_years"],
                        "valuation_date": valuation_date,
                        "pay_interest_day_list": row["pay_interest_day_list"],
                        "interst_list": row["interst_list"],
                        "conv_end_date": row["conv_end_date"],
                        "maturity_put_price": row["_cb_effective_maturity_put_price"],
                        "cb_ytm": row["cb_ytm"],
                        "call_event_status_window": row["call_event_status_window"],
                        "call_event_reg_date_window": row["call_event_reg_date_window"],
                        "call_event_price_window": row["call_event_price_window"],
                    }
                ),
                return_dtype=pl.Float64,
            )
            .alias("pure_value"),
        )
        .with_columns(
            pl.col("_cb_effective_conv_end_date").alias("conv_end_date"),
            pl.col("_cb_effective_maturity_put_price").alias("maturity_put_price"),
        )
        .drop("_cb_effective_conv_end_date", "_cb_effective_maturity_put_price")
        .with_columns(pl.lit(valuation_date).alias("valuation_date"))
        .with_columns(
            pl.struct(
                [
                    "valuation_date",
                    "conv_start_date",
                    "redeem_price_rate",
                    "redeem_count_days",
                    "redeem_total_days",
                    "redeem_trade_date_window",
                    "stk_close_window",
                    "conv_price_window",
                    "call_event_status_window",
                    "call_event_ann_date_window",
                    "call_event_recal_date_window",
                    "call_event_priority_window",
                    "close_stk",
                    "conv_price",
                ]
            )
            .map_elements(calculate_cb_redeem_state, return_dtype=redeem_state_dtype)
            .alias("_cb_redeem_state")
        )
        .unnest("_cb_redeem_state")
        .drop("valuation_date")
    )
