"""Contracts between the live orchestrator, broker session, and strategy plugins."""

from __future__ import annotations

from dataclasses import dataclass, field
from datetime import date, datetime
from enum import Enum
from pathlib import Path
from typing import Any, Protocol, runtime_checkable


class RiskAction(str, Enum):
    ALLOW = "allow"
    BLOCK_NEW_ENTRIES = "block_new_entries"
    HALT_ALL = "halt_all"


@dataclass(frozen=True)
class PositionLeg:
    """Normalized open leg from IB (or strategy state)."""

    con_id: int
    symbol: str
    sec_type: str
    expiry: str
    strike: float
    right: str
    position: float
    avg_cost: float
    market_value: float
    unrealized_pnl: float


@dataclass(frozen=True)
class PortfolioSnapshot:
    """Account + positions as seen by the broker at cycle start."""

    as_of: datetime
    net_liquidation_usd: float
    available_funds_usd: float
    excess_liquidity_usd: float
    maintenance_margin_usd: float
    unrealized_pnl_usd: float
    realized_pnl_today_usd: float
    margin_utilization: float
    positions: tuple[PositionLeg, ...]
    positions_by_symbol: dict[str, tuple[PositionLeg, ...]] = field(default_factory=dict)
    raw_account_tags: dict[str, float] = field(default_factory=dict)

    def open_option_legs(self) -> tuple[PositionLeg, ...]:
        return tuple(p for p in self.positions if p.sec_type == "OPT")

    def gross_notional_usd(self) -> float:
        return sum(abs(p.market_value) for p in self.positions)


@dataclass
class RiskVerdict:
    action: RiskAction
    reasons: list[str] = field(default_factory=list)
    allow_exits: bool = True
    allow_new_entries: bool = True

    def merge_block_entries(self, reason: str) -> None:
        self.action = RiskAction.BLOCK_NEW_ENTRIES
        self.allow_new_entries = False
        self.reasons.append(reason)


@dataclass
class StrategyCycleReport:
    strategy_id: str
    ok: bool
    entries_attempted: int = 0
    exits_attempted: int = 0
    messages: list[str] = field(default_factory=list)
    metadata: dict[str, Any] = field(default_factory=dict)


@dataclass
class StrategyContext:
    """Per-cycle context passed to every live strategy."""

    run_id: str
    today: date
    now_et: datetime
    ib: Any
    portfolio: PortfolioSnapshot
    platform_risk: RiskVerdict
    strategy_risk: RiskVerdict
    recommend_only: bool
    force_entry_now: bool
    entry_after_et: str
    entry_before_et: str | None
    strategy_id: str
    strategy_config: dict[str, Any]
    capital_budget_usd: float
    repo_root: Path
    log_dir: Path
    state_dir: Path
    max_new_entries_per_day: int
    max_open_positions: int
    enabled_for_entries: bool


@runtime_checkable
class LiveStrategy(Protocol):
    """Plugin interface: one implementation per live book."""

    strategy_id: str

    async def run_cycle(self, ctx: StrategyContext) -> StrategyCycleReport:
        """Manage exits first, then entries if risk + gates allow."""
        ...

    async def audit(self, ctx: StrategyContext) -> StrategyCycleReport:
        """Optional reconciliation vs broker (default: no-op)."""
        ...
