"""SQLite state management for paper trading sessions."""
from __future__ import annotations

import datetime as dt
import sqlite3
import uuid
from dataclasses import dataclass
from pathlib import Path
from typing import Any

from apps.paper_trader.models import create_schema


@dataclass
class SessionRow:
    session_id: str
    session_name: str
    config_path: str
    initial_equity: float
    created_at: str
    status: str
    parking_preset: str | None = None
    idle_alpha_preset: str | None = None
    form4_sleeve_preset: str | None = None
    ownership_sleeve_preset: str | None = None
    risk_off_alpha_sleeve_preset: str | None = None


@dataclass
class StrategyStateRow:
    session_id: str
    symbol: str
    event_id: str
    engine_id: str
    entry_date: str
    stop_price: float
    target_price: float
    current_stop: float
    peak_price: float
    days_held: int
    trade_direction: str
    candidate_json: str
    plan_json: str
    status: str
    id: int | None = None
    order_id: str | None = None


@dataclass
class SessionStateRow:
    session_id: str
    consecutive_losses: int = 0
    cooldown_remaining: int = 0
    kill_switch_triggered: bool = False
    daily_new_risk_used: float = 0.0
    last_processed_date: str | None = None


@dataclass
class DailySnapshotRow:
    session_id: str
    date: str
    equity: float
    cash: float
    market_value: float
    daily_pnl: float | None = None
    total_pnl: float | None = None
    drawdown_pct: float | None = None
    open_position_count: int | None = None


class StateManager:
    """SQLite CRUD for paper trading state."""

    def __init__(self, db_path: str | Path) -> None:
        self.db_path = Path(db_path)
        create_schema(self.db_path)

    def _connect(self) -> sqlite3.Connection:
        conn = sqlite3.connect(str(self.db_path))
        conn.row_factory = sqlite3.Row
        return conn

    # ------------------------------------------------------------------ #
    # Sessions
    # ------------------------------------------------------------------ #

    def create_session(
        self,
        session_name: str,
        config_path: str,
        initial_equity: float,
        parking_preset: str | None = None,
        idle_alpha_preset: str | None = None,
        form4_sleeve_preset: str | None = None,
        ownership_sleeve_preset: str | None = None,
        risk_off_alpha_sleeve_preset: str | None = None,
    ) -> str:
        session_id = str(uuid.uuid4())[:8]
        created_at = dt.datetime.now(tz=dt.timezone.utc).isoformat()
        with self._connect() as conn:
            conn.execute(
                "INSERT INTO sessions (session_id, session_name, config_path, initial_equity, created_at, status, parking_preset, idle_alpha_preset, form4_sleeve_preset, ownership_sleeve_preset, risk_off_alpha_sleeve_preset) "
                "VALUES (?, ?, ?, ?, ?, 'active', ?, ?, ?, ?, ?)",
                (
                    session_id,
                    session_name,
                    config_path,
                    initial_equity,
                    created_at,
                    parking_preset,
                    idle_alpha_preset,
                    form4_sleeve_preset,
                    ownership_sleeve_preset,
                    risk_off_alpha_sleeve_preset,
                ),
            )
            conn.execute(
                "INSERT INTO session_state (session_id) VALUES (?)",
                (session_id,),
            )
        return session_id

    def get_session(self, session_name_or_id: str) -> SessionRow | None:
        with self._connect() as conn:
            row = conn.execute(
                "SELECT * FROM sessions WHERE session_id = ? OR session_name = ? LIMIT 1",
                (session_name_or_id, session_name_or_id),
            ).fetchone()
        if row is None:
            return None
        return SessionRow(**dict(row))

    def list_sessions(self) -> list[SessionRow]:
        with self._connect() as conn:
            rows = conn.execute(
                "SELECT * FROM sessions ORDER BY created_at"
            ).fetchall()
        return [SessionRow(**dict(r)) for r in rows]

    def set_session_status(self, session_id: str, status: str) -> None:
        with self._connect() as conn:
            conn.execute(
                "UPDATE sessions SET status = ? WHERE session_id = ?",
                (status, session_id),
            )

    def delete_session(self, session_id: str) -> None:
        """Delete a session and all related data."""
        with self._connect() as conn:
            for table in (
                "processed_phases", "processed_dates", "daily_snapshots",
                "trades", "processed_events", "strategy_states", "session_state",
            ):
                conn.execute(f"DELETE FROM {table} WHERE session_id = ?", (session_id,))
            conn.execute("DELETE FROM sessions WHERE session_id = ?", (session_id,))

    # ------------------------------------------------------------------ #
    # Strategy states
    # ------------------------------------------------------------------ #

    def get_open_strategy_states(self, session_id: str) -> list[StrategyStateRow]:
        with self._connect() as conn:
            rows = conn.execute(
                "SELECT * FROM strategy_states WHERE session_id = ? AND status IN ('open', 'partial')",
                (session_id,),
            ).fetchall()
        return [StrategyStateRow(**dict(r)) for r in rows]

    def get_strategy_state_by_symbol(
        self, session_id: str, symbol: str
    ) -> StrategyStateRow | None:
        with self._connect() as conn:
            row = conn.execute(
                "SELECT * FROM strategy_states WHERE session_id = ? AND symbol = ? AND status IN ('open', 'partial') LIMIT 1",
                (session_id, symbol),
            ).fetchone()
        if row is None:
            return None
        return StrategyStateRow(**dict(row))

    def save_strategy_state(self, session_id: str, state: StrategyStateRow) -> None:
        with self._connect() as conn:
            conn.execute(
                """INSERT INTO strategy_states
                   (session_id, symbol, event_id, engine_id, order_id, entry_date,
                    stop_price, target_price, current_stop, peak_price, days_held,
                    trade_direction, candidate_json, plan_json, status)
                   VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                   ON CONFLICT(session_id, symbol, status) DO UPDATE SET
                     current_stop=excluded.current_stop,
                     peak_price=excluded.peak_price,
                     days_held=excluded.days_held,
                     order_id=excluded.order_id""",
                (
                    session_id, state.symbol, state.event_id, state.engine_id,
                    state.order_id, state.entry_date, state.stop_price, state.target_price,
                    state.current_stop, state.peak_price, state.days_held,
                    state.trade_direction, state.candidate_json, state.plan_json, state.status,
                ),
            )

    def update_strategy_state(
        self,
        session_id: str,
        symbol: str,
        *,
        days_held: int | None = None,
        current_stop: float | None = None,
        peak_price: float | None = None,
        status: str | None = None,
    ) -> None:
        updates: list[str] = []
        values: list[Any] = []
        if days_held is not None:
            updates.append("days_held = ?")
            values.append(days_held)
        if current_stop is not None:
            updates.append("current_stop = ?")
            values.append(current_stop)
        if peak_price is not None:
            updates.append("peak_price = ?")
            values.append(peak_price)
        if status is not None:
            updates.append("status = ?")
            values.append(status)
        if not updates:
            return
        values.extend([session_id, symbol])
        with self._connect() as conn:
            conn.execute(
                f"UPDATE strategy_states SET {', '.join(updates)} "
                "WHERE session_id = ? AND symbol = ? AND status IN ('open', 'partial')",
                values,
            )

    def close_strategy_state(self, session_id: str, symbol: str) -> None:
        with self._connect() as conn:
            conn.execute(
                "DELETE FROM strategy_states "
                "WHERE session_id = ? AND symbol = ? AND status IN ('open', 'partial')",
                (session_id, symbol),
            )

    # ------------------------------------------------------------------ #
    # Session-level state
    # ------------------------------------------------------------------ #

    def get_session_state(self, session_id: str) -> SessionStateRow:
        with self._connect() as conn:
            row = conn.execute(
                "SELECT * FROM session_state WHERE session_id = ?",
                (session_id,),
            ).fetchone()
        if row is None:
            return SessionStateRow(session_id=session_id)
        d = dict(row)
        d["kill_switch_triggered"] = bool(d["kill_switch_triggered"])
        return SessionStateRow(**d)

    def update_session_state(self, state: SessionStateRow) -> None:
        with self._connect() as conn:
            conn.execute(
                """INSERT INTO session_state
                   (session_id, consecutive_losses, cooldown_remaining,
                    kill_switch_triggered, daily_new_risk_used, last_processed_date)
                   VALUES (?, ?, ?, ?, ?, ?)
                   ON CONFLICT(session_id) DO UPDATE SET
                     consecutive_losses=excluded.consecutive_losses,
                     cooldown_remaining=excluded.cooldown_remaining,
                     kill_switch_triggered=excluded.kill_switch_triggered,
                     daily_new_risk_used=excluded.daily_new_risk_used,
                     last_processed_date=excluded.last_processed_date""",
                (
                    state.session_id,
                    state.consecutive_losses,
                    state.cooldown_remaining,
                    int(state.kill_switch_triggered),
                    state.daily_new_risk_used,
                    state.last_processed_date,
                ),
            )

    # ------------------------------------------------------------------ #
    # Processed events
    # ------------------------------------------------------------------ #

    def has_processed_event(self, session_id: str, event_id: str) -> bool:
        with self._connect() as conn:
            row = conn.execute(
                "SELECT 1 FROM processed_events WHERE session_id = ? AND event_id = ?",
                (session_id, event_id),
            ).fetchone()
        return row is not None

    def record_processed_event(
        self,
        session_id: str,
        event_id: str,
        processed_date: str,
        action: str,
        skip_reason: str | None = None,
    ) -> None:
        with self._connect() as conn:
            conn.execute(
                "INSERT OR IGNORE INTO processed_events "
                "(session_id, event_id, processed_date, action, skip_reason) "
                "VALUES (?, ?, ?, ?, ?)",
                (session_id, event_id, processed_date, action, skip_reason),
            )

    # ------------------------------------------------------------------ #
    # Trades
    # ------------------------------------------------------------------ #

    def record_trade(
        self,
        session_id: str,
        symbol: str,
        engine_id: str | None,
        capital_bucket_id: str | None,
        entry_date: str | None,
        exit_date: str,
        entry_price: float | None,
        exit_price: float,
        exit_reason: str,
        shares: int,
        net_pnl: float,
        r_multiple: float,
        holding_days: int,
    ) -> str:
        trade_id = str(uuid.uuid4())
        with self._connect() as conn:
            conn.execute(
                "INSERT INTO trades (trade_id, session_id, symbol, engine_id, capital_bucket_id, entry_date, exit_date, "
                "entry_price, exit_price, exit_reason, shares, net_pnl, r_multiple, holding_days) "
                "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
                (
                    trade_id, session_id, symbol, engine_id, capital_bucket_id, entry_date, exit_date,
                    entry_price, exit_price, exit_reason, shares,
                    net_pnl, r_multiple, holding_days,
                ),
            )
        return trade_id

    def list_trades(self, session_id: str, limit: int | None = None) -> list[dict]:
        with self._connect() as conn:
            if limit:
                rows = conn.execute(
                    "SELECT * FROM trades WHERE session_id = ? ORDER BY exit_date DESC LIMIT ?",
                    (session_id, limit),
                ).fetchall()
            else:
                rows = conn.execute(
                    "SELECT * FROM trades WHERE session_id = ? ORDER BY exit_date",
                    (session_id,),
                ).fetchall()
        return [dict(r) for r in rows]

    # ------------------------------------------------------------------ #
    # Daily snapshots
    # ------------------------------------------------------------------ #

    def save_daily_snapshot(self, row: DailySnapshotRow) -> None:
        with self._connect() as conn:
            conn.execute(
                """INSERT INTO daily_snapshots
                   (session_id, date, equity, cash, market_value, daily_pnl,
                    total_pnl, drawdown_pct, open_position_count)
                   VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
                   ON CONFLICT(session_id, date) DO UPDATE SET
                     equity=excluded.equity,
                     cash=excluded.cash,
                     market_value=excluded.market_value,
                     daily_pnl=excluded.daily_pnl,
                     total_pnl=excluded.total_pnl,
                     drawdown_pct=excluded.drawdown_pct,
                     open_position_count=excluded.open_position_count""",
                (
                    row.session_id, row.date, row.equity, row.cash, row.market_value,
                    row.daily_pnl, row.total_pnl, row.drawdown_pct, row.open_position_count,
                ),
            )

    def list_snapshots(self, session_id: str) -> list[dict]:
        with self._connect() as conn:
            rows = conn.execute(
                "SELECT * FROM daily_snapshots WHERE session_id = ? ORDER BY date",
                (session_id,),
            ).fetchall()
        return [dict(r) for r in rows]

    def get_peak_equity(self, session_id: str, initial_equity: float) -> float:
        with self._connect() as conn:
            row = conn.execute(
                "SELECT MAX(equity) FROM daily_snapshots WHERE session_id = ?",
                (session_id,),
            ).fetchone()
        if row and row[0] is not None:
            return max(float(row[0]), initial_equity)
        return initial_equity

    # ------------------------------------------------------------------ #
    # Idempotency: processed dates
    # ------------------------------------------------------------------ #

    def is_phase_processed(self, session_id: str, date: dt.date, phase: str) -> bool:
        with self._connect() as conn:
            row = conn.execute(
                "SELECT 1 FROM processed_phases WHERE session_id = ? AND date = ? AND phase = ?",
                (session_id, date.isoformat(), phase),
            ).fetchone()
        return row is not None

    def mark_phase_processed(self, session_id: str, date: dt.date, phase: str) -> None:
        with self._connect() as conn:
            conn.execute(
                "INSERT OR IGNORE INTO processed_phases (session_id, date, phase) VALUES (?, ?, ?)",
                (session_id, date.isoformat(), phase),
            )

    def is_date_processed(self, session_id: str, date: dt.date) -> bool:
        with self._connect() as conn:
            row = conn.execute(
                "SELECT 1 FROM processed_dates WHERE session_id = ? AND date = ?",
                (session_id, date.isoformat()),
            ).fetchone()
        return row is not None

    def mark_date_processed(self, session_id: str, date: dt.date) -> None:
        with self._connect() as conn:
            conn.execute(
                "INSERT OR IGNORE INTO processed_dates (session_id, date) VALUES (?, ?)",
                (session_id, date.isoformat()),
            )

    # ------------------------------------------------------------------ #
    # Cash Parking
    # ------------------------------------------------------------------ #

    def get_parking_state(self, session_id: str) -> dict[str, Any] | None:
        with self._connect() as conn:
            row = conn.execute(
                "SELECT * FROM parking_state WHERE session_id = ? AND status = 'active'",
                (session_id,),
            ).fetchone()
        if row is None:
            return None
        return dict(row)

    def save_parking_state(
        self, session_id: str, symbol: str, entry_date: dt.date,
        qty: int, avg_price: float, entry_value: float,
        peak_price: float = 0, gate_in_sgov: int = 0,
        committed_target: str = "", sgov_entry_value: float = 0,
    ) -> None:
        with self._connect() as conn:
            conn.execute(
                """INSERT OR REPLACE INTO parking_state
                   (session_id, symbol, entry_date, qty, avg_price, entry_value, status,
                    peak_price, gate_in_sgov, committed_target, pending_target, pending_days,
                    sgov_entry_value, sold_today)
                   VALUES (?, ?, ?, ?, ?, ?, 'active', ?, ?, ?, '', 0, ?, 0)""",
                (session_id, symbol, entry_date.isoformat(), qty, avg_price, entry_value,
                 peak_price or avg_price, gate_in_sgov, committed_target or symbol.lower(),
                 sgov_entry_value or entry_value),
            )

    def list_parking_entries(self, session_id: str) -> list[dict]:
        with self._connect() as conn:
            rows = conn.execute(
                "SELECT * FROM parking_state WHERE session_id = ? ORDER BY entry_date",
                (session_id,),
            ).fetchall()
        return [dict(r) for r in rows]

    def close_parking_state(self, session_id: str) -> None:
        with self._connect() as conn:
            conn.execute(
                "UPDATE parking_state SET status = 'closed' WHERE session_id = ? AND status = 'active'",
                (session_id,),
            )

    def update_parking_peak(self, session_id: str, peak_price: float) -> None:
        with self._connect() as conn:
            conn.execute(
                "UPDATE parking_state SET peak_price = ? WHERE session_id = ? AND status = 'active'",
                (peak_price, session_id),
            )

    def update_parking_gate_state(
        self, session_id: str, **kwargs: Any,
    ) -> None:
        """Update gate hysteresis fields. Accepts: gate_in_sgov, committed_target,
        pending_target, pending_days, sold_today, overlay_brake_cooldown, overlay_hold_days."""
        allowed = {"gate_in_sgov", "committed_target", "pending_target", "pending_days", "sold_today",
                   "overlay_brake_cooldown", "overlay_hold_days"}
        updates, params = [], []
        for k, v in kwargs.items():
            if k in allowed:
                updates.append(f"{k} = ?")
                params.append(v)
        if not updates:
            return
        params.append(session_id)
        with self._connect() as conn:
            conn.execute(
                f"UPDATE parking_state SET {', '.join(updates)} WHERE session_id = ? AND status = 'active'",
                params,
            )

    def update_parking_sgov_value(self, session_id: str, new_value: float) -> None:
        with self._connect() as conn:
            conn.execute(
                "UPDATE parking_state SET entry_value = ? WHERE session_id = ? AND status = 'active'",
                (new_value, session_id),
            )
