"""Pydantic record types mirroring the SQLite tables.

Every numeric column documented as ``NUMERIC`` in
``state/migrations/0001_init.sql`` is exposed as :class:`decimal.Decimal`
on the Python side. The repository layer is responsible for serialising
to ``TEXT`` (using ``str``) when writing and parsing back when reading,
so precision is never lost via ``float`` coercion.
"""

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, Field

__all__ = [
    "DecisionRecord",
    "DvolSnapshot",
    "InstructionRecord",
    "ManualAction",
    "MarketSnapshotRecord",
    "OptionChainQuoteRecord",
    "PositionRecord",
    "PositionStatus",
    "SystemStateRecord",
]


PositionStatus = Literal[
    "proposed",
    "awaiting_fill",
    "open",
    "closing",
    "closed",
    "cancelled",
]


class PositionRecord(BaseModel):
    """Row of the ``positions`` table."""

    model_config = ConfigDict(extra="forbid")

    proposal_id: UUID
    spread_type: str
    asset: str = "ETH"
    expiry: datetime
    short_strike: Decimal
    long_strike: Decimal
    short_instrument: str
    long_instrument: str
    n_contracts: int
    spread_width_usd: Decimal
    spread_width_pct: Decimal
    credit_eth: Decimal
    credit_usd: Decimal
    max_loss_usd: Decimal
    spot_at_entry: Decimal
    dvol_at_entry: Decimal
    delta_at_entry: Decimal
    eth_price_at_entry: Decimal
    proposed_at: datetime
    opened_at: datetime | None = None
    closed_at: datetime | None = None
    close_reason: str | None = None
    debit_paid_eth: Decimal | None = None
    pnl_eth: Decimal | None = None
    pnl_usd: Decimal | None = None
    status: PositionStatus
    created_at: datetime
    updated_at: datetime


class InstructionRecord(BaseModel):
    """Row of the ``instructions`` table."""

    model_config = ConfigDict(extra="forbid")

    instruction_id: UUID
    proposal_id: UUID
    kind: Literal["open_combo", "close_combo"]
    payload_json: str
    sent_at: datetime
    acknowledged_at: datetime | None = None
    filled_at: datetime | None = None
    cancelled_at: datetime | None = None
    actual_fill_eth: Decimal | None = None
    actual_fees_eth: Decimal | None = None


class DecisionRecord(BaseModel):
    """Row of the ``decisions`` table.

    ``id`` is :class:`int` and may be ``None`` before the row has been
    inserted; the repository sets it after the auto-increment fires.
    """

    model_config = ConfigDict(extra="forbid")

    id: int | None = None
    decision_type: Literal["entry_check", "exit_check", "kelly_recalib"]
    proposal_id: UUID | None = None
    timestamp: datetime
    inputs_json: str
    outputs_json: str
    action_taken: str | None = None
    notes: str | None = None


class DvolSnapshot(BaseModel):
    """Row of the ``dvol_history`` table."""

    model_config = ConfigDict(extra="forbid")

    timestamp: datetime
    asset: str  # "ETH", "BTC"
    dvol: Decimal
    spot: Decimal


class MarketSnapshotRecord(BaseModel):
    """Row of the ``market_snapshots`` table.

    Single point in time, single asset. Every numeric field is
    optional because the ``market_snapshot`` collector is best-effort:
    a single MCP failure NULLs the affected metric without dropping
    the row.
    """

    model_config = ConfigDict(extra="forbid")

    timestamp: datetime
    asset: str  # "ETH", "BTC"
    spot: Decimal | None = None
    dvol: Decimal | None = None
    realized_vol_30d: Decimal | None = None
    iv_minus_rv: Decimal | None = None
    funding_perp_annualized: Decimal | None = None
    funding_cross_annualized: Decimal | None = None
    dealer_net_gamma: Decimal | None = None
    gamma_flip_level: Decimal | None = None
    oi_delta_pct_4h: Decimal | None = None
    liquidation_long_risk: str | None = None
    liquidation_short_risk: str | None = None
    macro_days_to_event: int | None = None
    fetch_ok: bool
    fetch_errors_json: str | None = None


class OptionChainQuoteRecord(BaseModel):
    """Row of the ``option_chain_snapshots`` table.

    One row per (snapshot_ts, instrument) — the same ``timestamp`` is
    shared by every quote prelevato nello stesso tick del cron.  Tutti
    i campi numerici sono opzionali perché il collector è
    best-effort: un ticker mancante non invalida il resto della chain.
    """

    model_config = ConfigDict(extra="forbid")

    timestamp: datetime
    asset: str
    instrument_name: str
    strike: Decimal
    expiry: datetime
    option_type: Literal["C", "P"]
    bid: Decimal | None = None
    ask: Decimal | None = None
    mid: Decimal | None = None
    iv: Decimal | None = None
    delta: Decimal | None = None
    gamma: Decimal | None = None
    theta: Decimal | None = None
    vega: Decimal | None = None
    open_interest: int | None = None
    volume_24h: int | None = None
    book_depth_top3: int | None = None


class ManualAction(BaseModel):
    """Row of the ``manual_actions`` table."""

    model_config = ConfigDict(extra="forbid")

    id: int | None = None
    kind: Literal[
        "approve_proposal",
        "reject_proposal",
        "force_close",
        "arm_kill",
        "disarm_kill",
        "run_cycle",
    ]
    proposal_id: UUID | None = None
    payload_json: str | None = None
    created_at: datetime
    consumed_at: datetime | None = None
    consumed_by: str | None = None
    result: str | None = None


class SystemStateRecord(BaseModel):
    """Singleton row of the ``system_state`` table."""

    model_config = ConfigDict(extra="forbid")

    id: int = Field(default=1)
    kill_switch: int = 0
    kill_reason: str | None = None
    kill_at: datetime | None = None
    last_health_check: datetime
    last_kelly_calib: datetime | None = None
    config_version: str
    started_at: datetime
    last_audit_hash: str | None = None
    auto_pause_until: datetime | None = None
    auto_pause_reason: str | None = None
