from __future__ import annotations

import sqlite3
import threading
import time
from pathlib import Path
from typing import Any

from ..config import Settings, get_settings

_SCHEMA = """
CREATE TABLE IF NOT EXISTS settings (
  key TEXT PRIMARY KEY,
  value TEXT NOT NULL
);

CREATE TABLE IF NOT EXISTS ledger_meta (
  id INTEGER PRIMARY KEY CHECK (id = 1),
  equity REAL NOT NULL,
  available REAL NOT NULL,
  reserved REAL NOT NULL DEFAULT 0,
  updated_at_ms INTEGER NOT NULL
);

CREATE TABLE IF NOT EXISTS groups (
  group_id TEXT PRIMARY KEY,
  status TEXT NOT NULL,
  bias TEXT,
  option_side TEXT,
  perp_side TEXT,
  option_inst_id TEXT,
  perp_inst_id TEXT,
  strike REAL,
  expiry_ymd TEXT,
  entry_index_px REAL,
  initial_premium REAL DEFAULT 0,
  exit_target_usdt REAL,
  open_at_ms INTEGER,
  close_at_ms INTEGER,
  close_reason TEXT,
  realized_pnl REAL DEFAULT 0,
  fees REAL DEFAULT 0,
  slip_cost REAL DEFAULT 0,
  note TEXT,
  exec_mode TEXT,
  funding_usdt REAL,
  settle_index_px REAL,
  perp_margin_mode TEXT
);

CREATE TABLE IF NOT EXISTS fills (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  group_id TEXT NOT NULL,
  leg TEXT NOT NULL,
  action TEXT NOT NULL,
  side TEXT NOT NULL,
  inst_id TEXT NOT NULL,
  qty_eth REAL NOT NULL,
  qty_contracts REAL,
  base_px REAL,
  fill_px REAL NOT NULL,
  fee REAL NOT NULL,
  slip REAL NOT NULL,
  notional REAL NOT NULL,
  ts_ms INTEGER NOT NULL,
  exec_mode TEXT,
  FOREIGN KEY(group_id) REFERENCES groups(group_id)
);

CREATE TABLE IF NOT EXISTS positions (
  id INTEGER PRIMARY KEY CHECK (id = 1),
  group_id TEXT,
  perp_side TEXT,
  perp_qty_eth REAL DEFAULT 0,
  perp_entry_px REAL,
  option_inst_id TEXT,
  option_side TEXT,
  option_qty_eth REAL DEFAULT 0,
  option_qty_contracts REAL DEFAULT 0,
  option_entry_px REAL,
  entry_index_px REAL,
  initial_premium REAL DEFAULT 0,
  exit_target_usdt REAL,
  status TEXT
);

CREATE TABLE IF NOT EXISTS ledger_entries (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  group_id TEXT,
  kind TEXT NOT NULL,
  amount REAL NOT NULL,
  balance_after REAL NOT NULL,
  note TEXT,
  ts_ms INTEGER NOT NULL
);

CREATE TABLE IF NOT EXISTS strategy_state (
  id INTEGER PRIMARY KEY CHECK (id = 1),
  running INTEGER NOT NULL DEFAULT 0,
  phase TEXT NOT NULL DEFAULT 'idle',
  rounds_done INTEGER NOT NULL DEFAULT 0,
  window_key TEXT,
  rest_until_ms INTEGER,
  last_error TEXT,
  updated_at_ms INTEGER NOT NULL
);

CREATE TABLE IF NOT EXISTS residual_options (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  group_id TEXT NOT NULL UNIQUE,
  option_inst_id TEXT NOT NULL,
  option_side TEXT NOT NULL,
  option_qty_eth REAL NOT NULL,
  option_qty_contracts REAL,
  option_entry_px REAL NOT NULL,
  strike REAL,
  expiry_ymd TEXT,
  expiry_ms INTEGER,
  entry_index_px REAL,
  initial_premium REAL DEFAULT 0,
  status TEXT NOT NULL DEFAULT 'pending',
  created_at_ms INTEGER NOT NULL,
  settled_at_ms INTEGER,
  settle_px REAL,
  settle_pnl REAL,
  note TEXT,
  FOREIGN KEY(group_id) REFERENCES groups(group_id)
);
CREATE TABLE IF NOT EXISTS funds_wallets (
  id INTEGER PRIMARY KEY CHECK (id = 1),
  funding_usdt REAL NOT NULL DEFAULT 0,
  trading_usdt REAL NOT NULL DEFAULT 0,
  options_funding_usdc REAL NOT NULL DEFAULT 0,
  options_trading_usdc REAL NOT NULL DEFAULT 0,
  options_funding_usdt REAL NOT NULL DEFAULT 0,
  options_trading_usdt REAL NOT NULL DEFAULT 0,
  updated_at_ms INTEGER NOT NULL
);
"""


def default_db_path(settings: Settings | None = None) -> Path:
    s = settings or get_settings()
    if s.db_path:
        return Path(s.db_path)
    root = Path(__file__).resolve().parents[2]  # backend/
    return root / "data" / "hedge.db"


class Database:
    def __init__(self, path: Path | None = None) -> None:
        self.path = path or default_db_path()
        self.path.parent.mkdir(parents=True, exist_ok=True)
        self._lock = threading.RLock()
        self._conn = sqlite3.connect(str(self.path), check_same_thread=False)
        self._conn.row_factory = sqlite3.Row
        self._conn.execute("PRAGMA journal_mode=WAL;")
        self._conn.executescript(_SCHEMA)
        self._conn.commit()
        self._migrate_columns()
        self._ensure_seed()

    def _migrate_columns(self) -> None:
        """幂等补列：exec_mode。"""
        with self._lock:
            for table, col, decl in (
                ("groups", "exec_mode", "TEXT"),
                ("groups", "funding_usdt", "REAL"),
                ("groups", "settle_index_px", "REAL"),
                ("groups", "perp_margin_mode", "TEXT"),
                ("groups", "exit_target_usdt", "REAL"),
                ("positions", "exit_target_usdt", "REAL"),
                ("fills", "exec_mode", "TEXT"),
                ("fills", "fee_ccy", "TEXT"),
            ):
                cols = {
                    str(r[1])
                    for r in self._conn.execute(f"PRAGMA table_info({table})").fetchall()
                }
                if col not in cols:
                    self._conn.execute(
                        f"ALTER TABLE {table} ADD COLUMN {col} {decl}"
                    )
            self._conn.commit()

    def close(self) -> None:
        with self._lock:
            self._conn.close()

    def _ensure_seed(self) -> None:
        s = get_settings()
        now = int(time.time() * 1000)
        with self._lock:
            row = self._conn.execute("SELECT id FROM ledger_meta WHERE id=1").fetchone()
            if row is None:
                self._conn.execute(
                    "INSERT INTO ledger_meta(id, equity, available, reserved, updated_at_ms) VALUES (1,?,?,0,?)",
                    (s.initial_equity, s.initial_equity, now),
                )
            pos = self._conn.execute("SELECT id FROM positions WHERE id=1").fetchone()
            if pos is None:
                self._conn.execute(
                    "INSERT INTO positions(id, status) VALUES (1, 'flat')"
                )
            st = self._conn.execute("SELECT id FROM strategy_state WHERE id=1").fetchone()
            if st is None:
                self._conn.execute(
                    "INSERT INTO strategy_state(id, running, phase, rounds_done, updated_at_ms) VALUES (1,0,'idle',0,?)",
                    (now,),
                )
            fw = self._conn.execute("SELECT id FROM funds_wallets WHERE id=1").fetchone()
            if fw is None:
                led = self._conn.execute(
                    "SELECT equity FROM ledger_meta WHERE id=1"
                ).fetchone()
                eq = float(led["equity"]) if led else float(s.initial_equity)
                self._conn.execute(
                    """INSERT INTO funds_wallets(
                        id, funding_usdt, trading_usdt, options_funding_usdc, options_trading_usdc,
                        options_funding_usdt, options_trading_usdt, updated_at_ms
                    ) VALUES (1,?,0,0,0,0,0,?)""",
                    (eq, now),
                )
            defaults = {
                "fee_rate": str(s.fee_rate),
                "initial_equity": str(s.initial_equity),
                "exit_move_points": str(s.exit_move_points),
                "exit_move_pct": str(s.exit_move_pct),
                "exit_mode": str(s.exit_mode),
                "net_profit_target": str(s.net_profit_target),
                "premium_exit_multiple": str(s.premium_exit_multiple),
                "rest_seconds": str(s.rest_seconds),
                "live_order_interval_sec": str(s.live_order_interval_sec),
                "skip_weekends": str(s.skip_weekends),
                "one_expiry_per_day": str(s.one_expiry_per_day),
                "max_rounds": str(s.max_rounds),
                "leverage": str(s.leverage),
                "perp_margin_mode": str(s.perp_margin_mode),
                "min_option_hours": str(s.min_option_hours),
                "min_option_leverage": str(s.min_option_leverage),
                "risk_leverage_basis": str(s.risk_leverage_basis),
                "atm_open_offset_enabled": str(s.atm_open_offset_enabled),
                "max_atm_open_offset": str(s.max_atm_open_offset),
                "fixed_direction_enabled": str(s.fixed_direction_enabled),
                "fixed_perp_side": str(s.fixed_perp_side),
                "close_bid_mark_max_pct": str(s.close_bid_mark_max_pct),
                "perp_qty_eth": str(s.perp_qty_eth),
                "option_qty_eth": str(s.option_qty_eth),
                "exchange": str(s.exchange),
                "perp_inst_id": str(s.perp_inst_id),
                "option_inst_family": str(s.option_inst_family),
                "index_inst_id": str(s.index_inst_id),
                "option_ct_mult_default": str(s.option_ct_mult_default),
            }
            for k, v in defaults.items():
                exists = self._conn.execute(
                    "SELECT key FROM settings WHERE key=?", (k,)
                ).fetchone()
                if exists is None:
                    self._conn.execute(
                        "INSERT INTO settings(key, value) VALUES (?,?)", (k, v)
                    )
            self._conn.commit()

    def execute(self, sql: str, params: tuple[Any, ...] | list[Any] = ()) -> sqlite3.Cursor:
        with self._lock:
            cur = self._conn.execute(sql, params)
            self._conn.commit()
            return cur

    def executemany(self, sql: str, seq: list[tuple[Any, ...]]) -> None:
        with self._lock:
            self._conn.executemany(sql, seq)
            self._conn.commit()

    def fetchone(self, sql: str, params: tuple[Any, ...] | list[Any] = ()) -> sqlite3.Row | None:
        with self._lock:
            return self._conn.execute(sql, params).fetchone()

    def fetchall(self, sql: str, params: tuple[Any, ...] | list[Any] = ()) -> list[sqlite3.Row]:
        with self._lock:
            return list(self._conn.execute(sql, params).fetchall())

    def get_setting(self, key: str, default: str | None = None) -> str | None:
        row = self.fetchone("SELECT value FROM settings WHERE key=?", (key,))
        if row is None:
            return default
        return str(row["value"])

    def set_setting(self, key: str, value: str) -> None:
        self.execute(
            "INSERT INTO settings(key, value) VALUES(?,?) ON CONFLICT(key) DO UPDATE SET value=excluded.value",
            (key, value),
        )


_db: Database | None = None


def get_db() -> Database:
    global _db
    if _db is None:
        _db = Database()
    return _db


def set_db(db: Database | None) -> None:
    global _db
    _db = db
