from __future__ import annotations

from dataclasses import dataclass
from decimal import Decimal

from src.execution.cost_model import CostModel, TradeCostInput
from src.execution.order_intent import OrderIntent


@dataclass(frozen=True)
class PaperFill:
    intent: OrderIntent
    fill_price: Decimal
    fee_usd: Decimal
    slippage_usd: Decimal
    net_notional_usd: Decimal
    entry_fee_usd: Decimal = Decimal("0")
    exit_fee_usd: Decimal = Decimal("0")
    spread_cost_usd: Decimal = Decimal("0")
    slippage_cost_usd: Decimal = Decimal("0")
    funding_cost_usd: Decimal = Decimal("0")
    estimated_roundtrip_cost_pct: Decimal = Decimal("0")
    blocked_by_cost: bool = False
    blocked_reason: str = "ok"


class PaperExecutor:
    def __init__(self, *, fee_rate: Decimal = Decimal("0.00045"), slippage_bps: Decimal = Decimal("2"), cost_model: CostModel | None = None) -> None:
        self.fee_rate = fee_rate
        self.slippage_bps = slippage_bps
        self.cost_model = cost_model or CostModel(taker_fee_rate=fee_rate, min_slippage_pct=slippage_bps / Decimal("100"))

    def execute(self, intent: OrderIntent, *, mark_price: Decimal, half_spread_pct: Decimal | None = None, expected_move_pct: Decimal | None = None, depth_penalty_pct: Decimal = Decimal("0"), hold_hours: Decimal = Decimal("0")) -> PaperFill:
        notional_hint = intent.estimated_notional_usd if intent.estimated_notional_usd > 0 else mark_price * intent.size
        cost = self.cost_model.estimate(
            TradeCostInput(
                notional_usd=notional_hint,
                expected_move_pct=expected_move_pct if expected_move_pct is not None else Decimal("999"),
                half_spread_pct=half_spread_pct if half_spread_pct is not None else Decimal("0"),
                depth_penalty_pct=depth_penalty_pct,
                hold_hours=hold_hours,
            )
        )
        slip_pct = max(self.cost_model.min_slippage_pct, half_spread_pct or Decimal("0"), depth_penalty_pct)
        slip = mark_price * slip_pct / Decimal("100")
        fill_price = mark_price + slip if intent.side == "buy" else mark_price - slip
        notional = fill_price * intent.size
        entry_fee = notional * self.fee_rate
        return PaperFill(
            intent=intent,
            fill_price=fill_price,
            fee_usd=entry_fee,
            slippage_usd=slip * intent.size,
            net_notional_usd=notional - entry_fee,
            entry_fee_usd=cost.entry_fee_usd,
            exit_fee_usd=cost.exit_fee_usd,
            spread_cost_usd=cost.spread_cost_usd,
            slippage_cost_usd=cost.slippage_cost_usd,
            funding_cost_usd=cost.funding_cost_usd,
            estimated_roundtrip_cost_pct=cost.roundtrip_cost_pct,
            blocked_by_cost=not cost.allowed,
            blocked_reason=cost.reason,
        )
