"""Draft-mode trade simulator.

Simulates order fills with configurable slippage for backtesting and
paper-trading. No real exchange calls are made.
"""

from __future__ import annotations

import psycopg
import uuid
from datetime import datetime, timezone
from decimal import Decimal, ROUND_DOWN

import structlog

from src.config import SigilConfig
from src.contracts import OrderResult, OrderState, RiskDecision, TradeSignal

log = structlog.get_logger(__name__)

# Default tiered slippage — overridden by config when present
_DEFAULT_SLIPPAGE: dict[str, Decimal] = {
    "core": Decimal("0.001"),  # 0.1%
    "pump": Decimal("0.0075"),  # 0.75%
    "broad": Decimal("0.001"),  # 0.1% — same as core unless config overrides
    "micro": Decimal("0.005"),  # 0.5% — limit-maker entries, tighter than pump
}


class DraftTrader:
    """Simulates order execution for draft (paper-trading) mode."""

    def __init__(self, config: SigilConfig, db_conn: psycopg.Connection) -> None:
        self._config = config
        self._db = db_conn
        self._log = log.bind(component="draft_trader")

    # ------------------------------------------------------------------
    # Public API
    # ------------------------------------------------------------------

    def simulate_fill(
        self,
        signal: TradeSignal,
        risk_decision: RiskDecision,
        current_price: Decimal | None = None,  # NEW — injected market price
    ) -> OrderResult:
        """Simulate an immediate fill at current price plus slippage.

        Returns an OrderResult with status=SIMULATED and mode="draft".
        The simulated trade is recorded to the SQLite trades table.

        When current_price is provided and > 0 it is used directly (with
        slippage applied) instead of deriving a price from stop_loss_price.
        """
        if risk_decision.adjusted_quantity is None:
            raise ValueError(
                "risk_decision.adjusted_quantity must be set before calling simulate_fill"
            )

        slippage = self._get_slippage(signal.strategy_tier)
        fill_qty = risk_decision.adjusted_quantity

        if current_price is not None and current_price > 0:
            # Use the injected market price with slippage applied
            if signal.direction == "buy":
                fill_price = current_price * (Decimal("1") + slippage)
            else:
                fill_price = current_price * (Decimal("1") - slippage)
            fill_price = fill_price.quantize(Decimal("0.00000001"), rounding=ROUND_DOWN)
        else:
            # Derive a plausible fill price from adjusted_quantity / portfolio value
            # In draft mode we don't have a live price feed here, so we store a
            # sentinel value of 0; callers that need a price should inject it
            # via the signal or risk_decision.  The stop_loss_price gives us an
            # anchor we can work back from when set.
            fill_price = self._derive_fill_price(signal, risk_decision, slippage)

        client_order_id = self._make_client_order_id(signal)
        fee = (fill_qty * fill_price * Decimal("0.001")).quantize(Decimal("0.00000001"))

        result = OrderResult(
            order_id=None,
            client_order_id=client_order_id,
            status=OrderState.SIMULATED,
            filled_qty=fill_qty,
            avg_price=fill_price,
            fee=fee,
            mode="draft",
            timestamp=datetime.now(timezone.utc),
        )

        self._record_trade(signal, risk_decision, result)

        self._log.info(
            "draft_fill_simulated",
            signal_id=str(signal.signal_id),
            symbol=signal.symbol,
            direction=signal.direction,
            strategy_tier=signal.strategy_tier,
            fill_qty=str(fill_qty),
            fill_price=str(fill_price),
            slippage_pct=str(slippage * 100),
            client_order_id=client_order_id,
        )
        log.info(
            "pipeline_trace",
            stage="fill",
            price=float(fill_price),
            symbol=signal.symbol,
            mode="draft",
        )

        return result

    # ------------------------------------------------------------------
    # Private helpers
    # ------------------------------------------------------------------

    def _get_slippage(self, strategy_tier: str) -> Decimal:
        """Return slippage rate for the given strategy tier."""
        return _DEFAULT_SLIPPAGE.get(strategy_tier, _DEFAULT_SLIPPAGE["core"])

    def _derive_fill_price(
        self,
        signal: TradeSignal,
        risk_decision: RiskDecision,
        slippage: Decimal,
    ) -> Decimal:
        """Derive a simulated fill price.

        When a stop_loss_price is present in the risk decision we can work
        back to an approximate entry price.  Otherwise we return a sentinel
        Decimal("0") to indicate the price is unknown in draft mode.
        """
        if risk_decision.stop_loss_price and risk_decision.stop_loss_price > 0:
            stop_pct: Decimal
            if signal.strategy_tier == "pump":
                stop_pct = Decimal(str(self._config.pump.stop_loss_pct)) / Decimal(
                    "100"
                )
            else:
                stop_pct = Decimal(str(self._config.core.stop_loss_pct)) / Decimal(
                    "100"
                )

            # entry ≈ stop / (1 - stop_pct)
            entry = risk_decision.stop_loss_price / (Decimal("1") - stop_pct)

            # Apply slippage in the direction of trade
            if signal.direction == "buy":
                fill = entry * (Decimal("1") + slippage)
            else:
                fill = entry * (Decimal("1") - slippage)

            return fill.quantize(Decimal("0.00000001"), rounding=ROUND_DOWN)

        # No price anchor available — return 0 (caller must handle)
        return Decimal("0")

    @staticmethod
    def _make_client_order_id(signal: TradeSignal) -> str:
        """Generate a unique client order id for the simulated order."""
        short_id = str(uuid.uuid4()).replace("-", "")[:12]
        return f"{signal.strategy_tier}-draft-{short_id}-bot"

    def _record_trade(
        self,
        signal: TradeSignal,
        risk_decision: RiskDecision,
        result: OrderResult,
    ) -> None:
        """Persist the simulated trade to the trades table."""
        try:
            self._db.execute(
                """
                INSERT INTO trades (
                    exchange, symbol, side, order_type,
                    quantity, price, filled_quantity, filled_avg_price,
                    status, order_state, strategy_tier,
                    signal_confidence, signal_id,
                    client_order_id, exchange_order_id,
                    reason, mode
                ) VALUES (
                    'draft', %s, %s, 'market',
                    %s, %s, %s, %s,
                    'SIMULATED', 'SIMULATED', %s,
                    %s, %s,
                    %s, NULL,
                    'draft_simulation', 'draft'
                )
                """,
                (
                    signal.symbol,
                    signal.direction,
                    str(risk_decision.adjusted_quantity),
                    str(result.avg_price),
                    str(result.filled_qty),
                    str(result.avg_price),
                    signal.strategy_tier,
                    signal.confidence,
                    str(signal.signal_id),
                    result.client_order_id,
                ),
            )
            self._db.commit()
        except Exception:
            self._log.exception(
                "draft_trade_record_failed",
                signal_id=str(signal.signal_id),
                symbol=signal.symbol,
            )
