"""DraftMicroExecutor — simulates order fills for paper-trading mode.

No real exchange calls are made. Records to micro_positions table with
mode='draft'.

Architecture Design reference: §2.5 (Brownian-motion price paths for exit
simulation, 0.5% slippage + order book walk simulation).
"""

from __future__ import annotations

import json
import math
import random
import psycopg
import uuid
from datetime import datetime, timezone
from decimal import Decimal, ROUND_DOWN

import structlog

from src.micro_scanner.config import MicroConfig
from src.micro_scanner.contracts import MicroPosition, MicroSignal

log = structlog.get_logger(__name__)

# Draft-mode slippage for micro: 0.5% (Architecture Design §2.5)
_DRAFT_SLIPPAGE = Decimal("0.005")

# Brownian-motion parameters for price path simulation
_BM_DRIFT = 0.0002  # slight upward drift per step
_BM_VOLATILITY = 0.003  # per-step volatility
_BM_STEPS = 60  # simulates 60 time steps (1 per minute → 1h window)

_AUDIT_LOG_ENTRY_SUBMITTED = "entry_submitted"
_AUDIT_LOG_ENTRY_FILLED = "entry_filled"
_AUDIT_LOG_SL_PLACED = "stop_placed"


class DraftMicroExecutor:
    """Simulate micro entry fills with slippage + Brownian-motion price paths."""

    def __init__(self, config: MicroConfig, conn: psycopg.Connection) -> None:
        self._config = config
        self._conn = conn
        self._log = log.bind(component="draft_micro_executor")

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

    async def simulate_fill(
        self,
        signal: MicroSignal,
        entry_price: Decimal,
        quantity: Decimal,
        position_id: str | None = None,
    ) -> MicroPosition:
        """Simulate an immediate fill at entry_price + 0.5% slippage.

        Records to micro_positions with mode='draft'. Returns the
        constructed MicroPosition.
        """
        if position_id is None:
            position_id = str(uuid.uuid4())

        # Apply 0.5% buy slippage (price paid is slightly higher)
        avg_fill_price = (entry_price * (Decimal("1") + _DRAFT_SLIPPAGE)).quantize(
            Decimal("0.00000001"), rounding=ROUND_DOWN
        )

        stop_loss_pct = Decimal(str(self._config.stop_loss_pct))
        sl_buffer_pct = Decimal(str(self._config.stop_loss_limit_buffer_pct))

        stop_trigger = avg_fill_price * (Decimal("1") - stop_loss_pct / 100)
        stop_limit = avg_fill_price * (
            Decimal("1") - (stop_loss_pct + sl_buffer_pct) / 100
        )

        opened_at = datetime.now(timezone.utc)

        position = MicroPosition(
            id=position_id,
            signal_id=str(signal.signal_id),
            symbol=signal.symbol,
            mode="draft",
            status="open",
            closed_by=None,
            entry_price=avg_fill_price,
            qty=quantity,
            filled_qty=quantity,
            avg_fill_price=avg_fill_price,
            sl_order_id=f"draft-sl-{uuid.uuid4().hex[:8]}",
            stop_price=stop_trigger,
            opened_at=opened_at,
        )

        self._persist_position(position, stop_trigger, stop_limit)

        self._log.info(
            "draft_fill_simulated",
            symbol=signal.symbol,
            position_id=position_id,
            entry_price=str(entry_price),
            avg_fill_price=str(avg_fill_price),
            quantity=str(quantity),
            stop_trigger=str(stop_trigger),
            slippage_pct="0.5",
        )

        return position

    def simulate_price_path(
        self,
        entry_price: Decimal,
        steps: int = _BM_STEPS,
        drift: float = _BM_DRIFT,
        volatility: float = _BM_VOLATILITY,
        seed: int | None = None,
    ) -> list[Decimal]:
        """Generate a Brownian-motion price path for exit simulation.

        Returns a list of *steps* prices starting from entry_price.
        Used by exit simulation in testing / backtesting — not called
        during live operation.
        """
        rng = random.Random(seed)
        price = float(entry_price)
        path = [entry_price]

        for _ in range(steps - 1):
            shock = rng.gauss(0.0, 1.0)
            # Geometric Brownian motion: dS = S(μdt + σdW)
            price = price * math.exp(drift + volatility * shock)
            path.append(
                Decimal(str(price)).quantize(Decimal("0.00000001"), rounding=ROUND_DOWN)
            )

        return path

    def simulate_order_book_slippage(
        self,
        order_size_usdt: float,
        depth_levels: list[tuple[float, float]],
    ) -> Decimal:
        """Simulate order-book walk to estimate fill price.

        depth_levels — list of (price, base_qty) tuples, ask side, ascending.
        Returns the weighted average fill price as a Decimal.
        Falls back to best-ask price if book is too thin.
        """
        if not depth_levels:
            return Decimal("0")

        best_ask = depth_levels[0][0]
        remaining = order_size_usdt
        total_cost = 0.0
        total_filled = 0.0

        for price, qty in depth_levels:
            level_cost = price * qty
            if remaining <= level_cost:
                filled_qty = remaining / price
                total_cost += remaining
                total_filled += filled_qty
                remaining = 0.0
                break
            total_cost += level_cost
            total_filled += qty
            remaining -= level_cost

        if total_filled == 0:
            return Decimal(str(best_ask)) * (Decimal("1") + _DRAFT_SLIPPAGE)

        avg_price = total_cost / total_filled
        return Decimal(str(avg_price)).quantize(
            Decimal("0.00000001"), rounding=ROUND_DOWN
        )

    # ------------------------------------------------------------------
    # Persistence
    # ------------------------------------------------------------------

    def _persist_position(
        self,
        position: MicroPosition,
        stop_trigger: Decimal,
        stop_limit: Decimal,
    ) -> None:
        try:
            self._conn.execute(
                """
                INSERT INTO micro_positions (
                    id, signal_id, symbol, mode, status, closed_by,
                    entry_price, qty, filled_qty, avg_fill_price,
                    sl_order_id, trailing_stop_active, stop_price,
                    opened_at
                ) VALUES (
                    %s, %s, %s, %s, %s, %s,
                    %s, %s, %s, %s,
                    %s, %s, %s,
                    %s
                )
                """,
                (
                    position.id,
                    position.signal_id,
                    position.symbol,
                    position.mode,
                    position.status,
                    position.closed_by,
                    str(position.entry_price),
                    str(position.qty),
                    str(position.filled_qty),
                    str(position.avg_fill_price),
                    position.sl_order_id,
                    1 if position.trailing_stop_active else 0,
                    str(position.stop_price),
                    position.opened_at.isoformat(),
                ),
            )
            self._conn.commit()

            self._write_audit(
                position_id=position.id,
                event_type=_AUDIT_LOG_ENTRY_FILLED,
                symbol=position.symbol,
                detail={
                    "mode": "draft",
                    "avg_fill_price": str(position.avg_fill_price),
                    "qty": str(position.qty),
                    "stop_trigger": str(stop_trigger),
                    "stop_limit": str(stop_limit),
                    "slippage_pct": "0.5",
                },
            )

            self._write_audit(
                position_id=position.id,
                event_type=_AUDIT_LOG_SL_PLACED,
                symbol=position.symbol,
                detail={
                    "mode": "draft",
                    "sl_order_id": position.sl_order_id,
                    "stop_trigger": str(stop_trigger),
                    "stop_limit": str(stop_limit),
                },
            )

        except psycopg.Error as exc:
            self._log.exception(
                "draft_persist_position_failed",
                position_id=position.id,
                symbol=position.symbol,
                error=str(exc),
            )

    def _write_audit(
        self,
        position_id: str,
        event_type: str,
        symbol: str,
        detail: dict,
    ) -> None:
        try:
            self._conn.execute(
                """
                INSERT INTO trade_audit_log (position_id, event_type, symbol, detail, mode, created_at)
                VALUES (%s, %s, %s, %s, 'draft', NOW())
                """,
                (position_id, event_type, symbol, json.dumps(detail)),
            )
            self._conn.commit()
        except psycopg.Error as exc:
            self._log.warning(
                "draft_audit_log_write_failed",
                event_type=event_type,
                symbol=symbol,
                error=str(exc),
            )
