"""SQLite-based storage backend with auto table creation."""

from __future__ import annotations

import sqlite3
from contextlib import contextmanager
from pathlib import Path
from typing import Any, Dict, List, Optional

from sd_api.exceptions import StorageError
from sd_api.logging import get_logger
from sd_api.models.kline import KlineBar
from sd_api.models.quote import Quote
from sd_api.storage.base import BaseStore

logger = get_logger("storage.sqlite")


class SQLiteStore(BaseStore):
    """Lightweight SQLite storage with auto schema creation.

    Stores quotes and kline bars in separate tables within a single .db file.

    Usage::

        store = SQLiteStore("./data/market.db")
        store.save_quote(quote)
        store.save_kline(bars, symbol="XAUUSD", tf="5min")
        quotes = store.load_quotes("XAUUSD")
        bars = store.load_kline("XAUUSD", tf="5min")
    """

    _CREATE_QUOTE_TABLE = """
    CREATE TABLE IF NOT EXISTS quotes (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        symbol TEXT NOT NULL,
        bid REAL NOT NULL,
        ask REAL NOT NULL,
        spread REAL,
        mid REAL NOT NULL,
        received_at TEXT,
        quote_ts_ms INTEGER,
        quote_time_shanghai TEXT,
        server_ts_ms INTEGER,
        server_time_shanghai TEXT,
        latency_ms INTEGER,
        is_stale INTEGER,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP
    );
    CREATE INDEX IF NOT EXISTS idx_quotes_symbol ON quotes(symbol);
    CREATE INDEX IF NOT EXISTS idx_quotes_received_at ON quotes(received_at);
    """

    _CREATE_KLINE_TABLE = """
    CREATE TABLE IF NOT EXISTS klines (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        symbol TEXT NOT NULL,
        tf TEXT NOT NULL,
        ts INTEGER NOT NULL,
        open REAL NOT NULL,
        high REAL NOT NULL,
        low REAL NOT NULL,
        close REAL NOT NULL,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        UNIQUE(symbol, tf, ts)
    );
    CREATE INDEX IF NOT EXISTS idx_klines_symbol_tf ON klines(symbol, tf);
    """

    def __init__(self, db_path: str = "./sd_api_data/market.db") -> None:
        self.db_path = Path(db_path)
        self.db_path.parent.mkdir(parents=True, exist_ok=True)
        self._init_schema()

    @contextmanager
    def _conn(self):
        conn = sqlite3.connect(str(self.db_path))
        conn.row_factory = sqlite3.Row
        try:
            yield conn
        finally:
            conn.close()

    def _init_schema(self) -> None:
        try:
            with self._conn() as conn:
                conn.executescript(self._CREATE_QUOTE_TABLE)
                conn.executescript(self._CREATE_KLINE_TABLE)
                conn.commit()
            logger.debug(f"SQLite schema initialised at {self.db_path}")
        except sqlite3.Error as e:
            raise StorageError(f"Failed to init SQLite schema: {e}") from e

    # ── quote I/O ──────────────────────────────────────────────────────────

    def save_quote(self, quote: Quote) -> None:
        self.save_quotes([quote])

    def save_quotes(self, quotes: List[Quote]) -> None:
        if not quotes:
            return
        try:
            with self._conn() as conn:
                conn.executemany(
                    """
                    INSERT OR REPLACE INTO quotes
                    (symbol, bid, ask, spread, mid, received_at,
                     quote_ts_ms, quote_time_shanghai, server_ts_ms,
                     server_time_shanghai, latency_ms, is_stale)
                    VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                    """,
                    [
                        (
                            q.symbol, q.bid, q.ask, q.spread, q.mid,
                            q.received_at, q.quote_ts_ms, q.quote_time_shanghai,
                            q.server_ts_ms, q.server_time_shanghai,
                            q.latency_ms,
                            1 if q.is_stale else (0 if q.is_stale is not None else None),
                        )
                        for q in quotes
                    ],
                )
                conn.commit()
            logger.debug(f"Saved {len(quotes)} quotes to SQLite")
        except sqlite3.Error as e:
            raise StorageError(f"Failed to save quotes: {e}") from e

    def load_quotes(self, symbol: str, *, limit: int = 1000) -> List[Quote]:
        try:
            with self._conn() as conn:
                rows = conn.execute(
                    """
                    SELECT symbol, bid, ask, spread,
                           mid, received_at, quote_ts_ms, quote_time_shanghai,
                           server_ts_ms, server_time_shanghai, latency_ms, is_stale
                    FROM quotes
                    WHERE symbol = ?
                    ORDER BY received_at DESC
                    LIMIT ?
                    """,
                    (symbol.upper().strip(), limit),
                ).fetchall()
            quotes = []
            for r in reversed(rows):  # chronological order
                is_stale = None
                if r["is_stale"] is not None:
                    is_stale = bool(r["is_stale"])
                quotes.append(
                    Quote(
                        symbol=r["symbol"],
                        bid=r["bid"],
                        ask=r["ask"],
                        spread=r["spread"],
                        mid=r["mid"],
                        received_at=r["received_at"] or "",
                        quote_ts_ms=r["quote_ts_ms"],
                        quote_time_shanghai=r["quote_time_shanghai"],
                        server_ts_ms=r["server_ts_ms"],
                        server_time_shanghai=r["server_time_shanghai"],
                        latency_ms=r["latency_ms"],
                        is_stale=is_stale,
                    )
                )
            return quotes
        except sqlite3.Error as e:
            raise StorageError(f"Failed to load quotes: {e}") from e

    # ── kline I/O ─────────────────────────────────────────────────────────

    def save_kline(self, bars: List[KlineBar], symbol: str = "", tf: str = "") -> None:
        if not bars:
            return
        if not symbol or not tf:
            raise StorageError("symbol and tf are required for save_kline")
        try:
            with self._conn() as conn:
                conn.executemany(
                    """
                    INSERT OR REPLACE INTO klines
                    (symbol, tf, ts, open, high, low, close)
                    VALUES (?, ?, ?, ?, ?, ?, ?)
                    """,
                    [(symbol.upper().strip(), tf, b.ts, b.open, b.high, b.low, b.close) for b in bars],
                )
                conn.commit()
            logger.debug(f"Saved {len(bars)} kline bars to SQLite")
        except sqlite3.Error as e:
            raise StorageError(f"Failed to save klines: {e}") from e

    def load_kline(
        self,
        symbol: str,
        *,
        tf: str = "5min",
        limit: int = 500,
    ) -> List[KlineBar]:
        try:
            with self._conn() as conn:
                rows = conn.execute(
                    """
                    SELECT ts, open, high, low, close
                    FROM klines
                    WHERE symbol = ? AND tf = ?
                    ORDER BY ts ASC
                    LIMIT ?
                    """,
                    (symbol.upper().strip(), tf, limit),
                ).fetchall()
            return [KlineBar(ts=r["ts"], open=r["open"], high=r["high"], low=r["low"], close=r["close"]) for r in rows]
        except sqlite3.Error as e:
            raise StorageError(f"Failed to load klines: {e}") from e
