"""Exit-decision rule engine (``docs/03-algorithms.md §6``).

Pure function over a :class:`PositionSnapshot`. Triggers are evaluated
in the documented order; the first match wins. ``HOLD`` is returned
when no rule fires. The time-stop ``skip_if_close_to_profit`` exception
is interpreted as ``mark ≤ 70% × credit_received`` (re-using the same
units as the profit gate), which is the only interpretation that yields
non-trivial behaviour: rule 1 takes everything below 50% credit; the
exception covers the (50%, 70%] credit band where we wait for rule 1
to fire next cycle.
"""

from __future__ import annotations

from datetime import datetime
from decimal import Decimal
from typing import Literal
from uuid import UUID

from pydantic import BaseModel, ConfigDict

from cerbero_bite.config import SpreadType, StrategyConfig
from cerbero_bite.core.types import OptionLeg

__all__ = ["ExitAction", "ExitDecisionResult", "PositionSnapshot", "evaluate"]


ExitAction = Literal[
    "HOLD",
    "CLOSE_PROFIT",
    "CLOSE_PROFIT_PARTIAL",
    "CLOSE_STOP",
    "CLOSE_VOL",
    "CLOSE_VOL_HARVEST",
    "CLOSE_TIME",
    "CLOSE_DELTA",
    "CLOSE_AVERSE",
]


class PositionSnapshot(BaseModel):
    """Inputs for :func:`evaluate`. All ETH amounts are *totals* for the position."""

    model_config = ConfigDict(frozen=True, extra="forbid")

    proposal_id: UUID
    spread_type: SpreadType
    legs: list[OptionLeg]

    credit_received_eth: Decimal
    credit_received_usd: Decimal
    spot_at_entry: Decimal
    dvol_at_entry: Decimal
    expiry: datetime
    opened_at: datetime

    eth_price_usd_now: Decimal
    spot_now: Decimal
    dvol_now: Decimal
    mark_combo_now_eth: Decimal
    delta_short_now: Decimal
    return_4h_now: Decimal
    now: datetime


class ExitDecisionResult(BaseModel):
    """Action + human-readable reason + PnL estimate at the moment of decision."""

    model_config = ConfigDict(frozen=True, extra="forbid")

    action: ExitAction
    reason: str
    pnl_estimate_eth: Decimal
    pnl_estimate_usd: Decimal


def _days_to_expiry(snapshot: PositionSnapshot) -> Decimal:
    delta = snapshot.expiry - snapshot.now
    return Decimal(str(delta.total_seconds())) / Decimal("86400")


def _adverse_move(spread_type: SpreadType, return_4h: Decimal, threshold: Decimal) -> bool:
    if spread_type == "bull_put":
        return return_4h <= -threshold
    if spread_type == "bear_call":
        return return_4h >= threshold
    # iron_condor: adverse on either side
    return return_4h.copy_abs() >= threshold


def evaluate(snapshot: PositionSnapshot, cfg: StrategyConfig) -> ExitDecisionResult:
    """Return the exit action for the given position snapshot."""
    ec = cfg.exit
    credit = snapshot.credit_received_eth
    debit = snapshot.mark_combo_now_eth

    pnl_eth = credit - debit
    pnl_usd = pnl_eth * snapshot.eth_price_usd_now

    profit_take_thresh = credit * ec.profit_take_pct_of_credit
    stop_thresh = credit * ec.stop_loss_mark_x_credit
    skip_time_thresh = credit * ec.time_stop_skip_if_close_to_profit_pct
    days_left = _days_to_expiry(snapshot)

    def _result(action: ExitAction, reason: str) -> ExitDecisionResult:
        return ExitDecisionResult(
            action=action,
            reason=reason,
            pnl_estimate_eth=pnl_eth,
            pnl_estimate_usd=pnl_usd,
        )

    # 1. Profit take
    if debit <= profit_take_thresh:
        return _result(
            "CLOSE_PROFIT",
            f"mark {debit} ≤ {ec.profit_take_pct_of_credit:.0%} of credit {credit}",
        )

    # 1bis. Vol-collapse harvest (D): siamo IN profit (debit < credit) e
    # la DVOL è scesa di tot punti rispetto all'entry. Edge IV-RV già
    # catturato, non c'è motivo di tenere fino a profit_take. Esce
    # opportunisticamente quando il regime di vol che giustificava
    # l'entry non c'è più.
    if (
        ec.vol_harvest_dvol_decrease > 0
        and debit < credit
        and snapshot.dvol_now <= snapshot.dvol_at_entry - ec.vol_harvest_dvol_decrease
    ):
        return _result(
            "CLOSE_VOL_HARVEST",
            f"DVOL {snapshot.dvol_now} ≤ entry {snapshot.dvol_at_entry} − "
            f"{ec.vol_harvest_dvol_decrease}, harvest while in profit",
        )

    # 2. Stop loss
    if debit >= stop_thresh:
        return _result(
            "CLOSE_STOP",
            f"mark {debit} ≥ {ec.stop_loss_mark_x_credit}× credit {credit}",
        )

    # 3. Vol stop
    if snapshot.dvol_now >= snapshot.dvol_at_entry + ec.vol_stop_dvol_increase:
        return _result(
            "CLOSE_VOL",
            f"DVOL {snapshot.dvol_now} ≥ entry {snapshot.dvol_at_entry} "
            f"+ {ec.vol_stop_dvol_increase}",
        )

    # 4. Time stop with "close to profit" exception
    if days_left <= ec.time_stop_dte_remaining and debit > skip_time_thresh:
        return _result(
            "CLOSE_TIME",
            f"DTE {days_left:.2f} ≤ {ec.time_stop_dte_remaining} and mark "
            f"{debit} above skip threshold {skip_time_thresh}",
        )
    # When DTE ≤ 7 but mark is in the (50%, 70%]-credit "close to profit"
    # zone, we deliberately fall through; rule 1 will fire next cycle.

    # 5. Strike tested
    if snapshot.delta_short_now.copy_abs() >= ec.delta_breach_threshold:
        return _result(
            "CLOSE_DELTA",
            f"|delta_short| {snapshot.delta_short_now.copy_abs()} ≥ "
            f"{ec.delta_breach_threshold}",
        )

    # 6. Explosive adverse move
    if _adverse_move(snapshot.spread_type, snapshot.return_4h_now, ec.adverse_move_4h_pct):
        return _result(
            "CLOSE_AVERSE",
            f"4h return {snapshot.return_4h_now} adverse for {snapshot.spread_type}",
        )

    return _result("HOLD", "all triggers within tolerance")
