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

import os
import sqlite3
import uuid
from datetime import datetime, timezone
from pathlib import Path
from typing import Any

from apps.orb_trader.models import (
    CREATE_TABLES_SQL,
    ORBCandidateRow,
    ORBDailySnapshotRow,
    ORBDailyStateRow,
    ORBPositionRow,
    ORBSessionRow,
    ORBTradeRow,
)

_DEFAULT_DB = "data/paper/orb.db"


class ORBStateManager:
    """SQLite CRUD layer for ORB paper trading state."""

    def __init__(self, db_path: str | None = None) -> None:
        self._db_path = db_path or os.environ.get("ORB_TRADER_DB", _DEFAULT_DB)
        Path(self._db_path).parent.mkdir(parents=True, exist_ok=True)
        self._init_db()

    def _connect(self) -> sqlite3.Connection:
        conn = sqlite3.connect(self._db_path)
        conn.row_factory = sqlite3.Row
        conn.execute("PRAGMA journal_mode=WAL")
        conn.execute("PRAGMA foreign_keys=ON")
        return conn

    def _init_db(self) -> None:
        with self._connect() as conn:
            conn.executescript(CREATE_TABLES_SQL)

    # ── Sessions ──────────────────────────────────────────────────────────────

    def create_session(
        self,
        name: str,
        config_path: str,
        initial_equity: float,
    ) -> str:
        session_id = str(uuid.uuid4())[:8]
        now = datetime.now(timezone.utc).isoformat()
        with self._connect() as conn:
            conn.execute(
                """INSERT INTO sessions (session_id, session_name, config_path, initial_equity, created_at)
                   VALUES (?, ?, ?, ?, ?)""",
                (session_id, name, config_path, initial_equity, now),
            )
        return session_id

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

    def list_sessions(self) -> list[ORBSessionRow]:
        with self._connect() as conn:
            rows = conn.execute(
                "SELECT * FROM sessions ORDER BY created_at DESC"
            ).fetchall()
        return [ORBSessionRow(**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:
        with self._connect() as conn:
            conn.execute("DELETE FROM orb_candidates WHERE session_id=?", (session_id,))
            conn.execute("DELETE FROM orb_positions WHERE session_id=?", (session_id,))
            conn.execute("DELETE FROM trades WHERE session_id=?", (session_id,))
            conn.execute("DELETE FROM daily_snapshots WHERE session_id=?", (session_id,))
            conn.execute("DELETE FROM daily_state WHERE session_id=?", (session_id,))
            conn.execute("DELETE FROM sessions WHERE session_id=?", (session_id,))

    # ── Positions ─────────────────────────────────────────────────────────────

    def save_position(self, pos: ORBPositionRow) -> None:
        with self._connect() as conn:
            conn.execute(
                """INSERT OR REPLACE INTO orb_positions
                   (session_id, date, ticker, direction, entry_price, entry_time,
                    shares, orb_high, orb_low, atr_at_entry, stop_distance, current_stop,
                    peak_price, trailing_active, rvol, composite_score, order_id, status)
                   VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
                (
                    pos.session_id, pos.date, pos.ticker, pos.direction,
                    pos.entry_price, pos.entry_time, pos.shares,
                    pos.orb_high, pos.orb_low, pos.atr_at_entry,
                    pos.stop_distance, pos.current_stop, pos.peak_price,
                    int(pos.trailing_active), pos.rvol, pos.composite_score,
                    pos.order_id, pos.status,
                ),
            )

    def get_open_positions(self, session_id: str, date: str) -> list[ORBPositionRow]:
        with self._connect() as conn:
            rows = conn.execute(
                "SELECT * FROM orb_positions WHERE session_id=? AND date=? AND status='open'",
                (session_id, date),
            ).fetchall()
        return [_row_to_position(r) for r in rows]

    def get_all_open_positions(self, session_id: str) -> list[ORBPositionRow]:
        """Get all open positions across all dates (for reconciliation)."""
        with self._connect() as conn:
            rows = conn.execute(
                "SELECT * FROM orb_positions WHERE session_id=? AND status='open'",
                (session_id,),
            ).fetchall()
        return [_row_to_position(r) for r in rows]

    def update_position_stop(
        self,
        session_id: str,
        date: str,
        ticker: str,
        current_stop: float,
        peak_price: float,
        trailing_active: bool,
    ) -> None:
        with self._connect() as conn:
            conn.execute(
                """UPDATE orb_positions
                   SET current_stop=?, peak_price=?, trailing_active=?
                   WHERE session_id=? AND date=? AND ticker=? AND status='open'""",
                (current_stop, peak_price, int(trailing_active), session_id, date, ticker),
            )

    def close_position_record(self, session_id: str, date: str, ticker: str) -> None:
        with self._connect() as conn:
            conn.execute(
                """UPDATE orb_positions SET status='closed'
                   WHERE session_id=? AND date=? AND ticker=?""",
                (session_id, date, ticker),
            )

    # ── Trades ────────────────────────────────────────────────────────────────

    def save_trade(self, trade: ORBTradeRow) -> None:
        with self._connect() as conn:
            conn.execute(
                """INSERT OR REPLACE INTO trades
                   (trade_id, session_id, date, ticker, direction, entry_price, exit_price,
                    entry_time, exit_time, shares, pnl, r_multiple, exit_reason,
                    atr_at_entry, rvol, composite_score)
                   VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
                (
                    trade.trade_id, trade.session_id, trade.date, trade.ticker,
                    trade.direction, trade.entry_price, trade.exit_price,
                    trade.entry_time, trade.exit_time, trade.shares,
                    trade.pnl, trade.r_multiple, trade.exit_reason,
                    trade.atr_at_entry, trade.rvol, trade.composite_score,
                ),
            )

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

    # ── Daily Snapshots ───────────────────────────────────────────────────────

    def save_daily_snapshot(self, snap: ORBDailySnapshotRow) -> None:
        with self._connect() as conn:
            conn.execute(
                """INSERT OR REPLACE INTO daily_snapshots
                   (session_id, date, equity, daily_pnl, total_pnl, trades_taken, stops_hit, drawdown_pct)
                   VALUES (?, ?, ?, ?, ?, ?, ?, ?)""",
                (
                    snap.session_id, snap.date, snap.equity, snap.daily_pnl,
                    snap.total_pnl, snap.trades_taken, snap.stops_hit, snap.drawdown_pct,
                ),
            )

    def list_snapshots(self, session_id: str) -> list[dict[str, Any]]:
        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_equity(self, session_id: str) -> float | None:
        """Return the equity from the latest daily snapshot, or None if no snapshots exist."""
        with self._connect() as conn:
            row = conn.execute(
                "SELECT equity FROM daily_snapshots WHERE session_id=? ORDER BY date DESC LIMIT 1",
                (session_id,),
            ).fetchone()
        return float(row[0]) if row else None

    def get_peak_equity(self, session_id: str, initial_equity: float) -> float:
        """Return the peak equity across all snapshots (for drawdown calculation)."""
        with self._connect() as conn:
            row = conn.execute(
                "SELECT MAX(equity) FROM daily_snapshots WHERE session_id=?",
                (session_id,),
            ).fetchone()
        return float(row[0]) if row and row[0] is not None else initial_equity

    # ── Daily State ───────────────────────────────────────────────────────────

    def get_daily_state(self, session_id: str, date: str) -> ORBDailyStateRow:
        with self._connect() as conn:
            row = conn.execute(
                "SELECT * FROM daily_state WHERE session_id=? AND date=?",
                (session_id, date),
            ).fetchone()
        if row is None:
            return ORBDailyStateRow(session_id=session_id, date=date)
        return ORBDailyStateRow(
            session_id=row["session_id"],
            date=row["date"],
            cumulative_loss=row["cumulative_loss"],
            stops_hit=row["stops_hit"],
            kill_switch=bool(row["kill_switch"]),
            phase=row["phase"],
        )

    def update_daily_state(self, session_id: str, date: str, **kwargs: Any) -> None:
        state = self.get_daily_state(session_id, date)
        for k, v in kwargs.items():
            setattr(state, k, v)
        with self._connect() as conn:
            conn.execute(
                """INSERT OR REPLACE INTO daily_state
                   (session_id, date, cumulative_loss, stops_hit, kill_switch, phase)
                   VALUES (?, ?, ?, ?, ?, ?)""",
                (
                    state.session_id, state.date, state.cumulative_loss,
                    state.stops_hit, int(state.kill_switch), state.phase,
                ),
            )

    # ── Candidates ────────────────────────────────────────────────────────────

    def save_candidate(self, cand: ORBCandidateRow) -> None:
        with self._connect() as conn:
            conn.execute(
                """INSERT OR REPLACE INTO orb_candidates
                   (session_id, date, ticker, direction, orb_high, orb_low, breakout_level,
                    atr, rvol, gap_pct, composite_score, status)
                   VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
                (
                    cand.session_id, cand.date, cand.ticker, cand.direction,
                    cand.orb_high, cand.orb_low, cand.breakout_level,
                    cand.atr, cand.rvol, cand.gap_pct, cand.composite_score, cand.status,
                ),
            )

    def update_candidate_status(
        self, session_id: str, date: str, ticker: str, status: str
    ) -> None:
        with self._connect() as conn:
            conn.execute(
                """UPDATE orb_candidates SET status=?
                   WHERE session_id=? AND date=? AND ticker=?""",
                (status, session_id, date, ticker),
            )

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


# ── Helpers ───────────────────────────────────────────────────────────────────

def _row_to_position(row: sqlite3.Row) -> ORBPositionRow:
    d = dict(row)
    d["trailing_active"] = bool(d.get("trailing_active", 0))
    return ORBPositionRow(**d)
