"""
SQLite database for strategy execution state, trade logs, and backtest history.

All data is stored under DATA_DIR/dexorder.db.
Uses aiosqlite for async compatibility with the MCP server's event loop.
"""
from __future__ import annotations

import json
import logging
import time
from pathlib import Path
from typing import Any, Optional

log = logging.getLogger(__name__)

_SCHEMA = """
CREATE TABLE IF NOT EXISTS strategies (
    name TEXT PRIMARY KEY,
    status TEXT NOT NULL DEFAULT 'stopped',
    git_rev TEXT,
    worktree_path TEXT,
    started_at REAL,
    stopped_at REAL,
    allocation REAL NOT NULL DEFAULT 0,
    paper INTEGER NOT NULL DEFAULT 1,
    feeds_json TEXT,
    config_json TEXT
);

CREATE TABLE IF NOT EXISTS strategy_state (
    name TEXT PRIMARY KEY,
    realized_pnl REAL NOT NULL DEFAULT 0,
    unrealized_pnl REAL NOT NULL DEFAULT 0,
    trade_count INTEGER NOT NULL DEFAULT 0,
    positions_json TEXT,
    updated_at REAL NOT NULL DEFAULT 0
);

CREATE TABLE IF NOT EXISTS trades (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    strategy_name TEXT NOT NULL,
    instrument TEXT NOT NULL,
    side TEXT NOT NULL,
    quantity REAL NOT NULL,
    entry_price REAL,
    exit_price REAL NOT NULL,
    entry_time REAL,
    exit_time REAL NOT NULL,
    pnl REAL NOT NULL,
    recorded_at REAL NOT NULL DEFAULT (unixepoch())
);

CREATE TABLE IF NOT EXISTS backtest_runs (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    strategy_name TEXT NOT NULL,
    ran_at REAL NOT NULL DEFAULT (unixepoch()),
    from_time REAL,
    to_time REAL,
    initial_capital REAL,
    feeds_json TEXT,
    summary_json TEXT,
    statistics_json TEXT,
    trades_json TEXT,
    equity_curve_json TEXT
);

CREATE TABLE IF NOT EXISTS strategy_events (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    strategy_name TEXT NOT NULL,
    event_type TEXT NOT NULL,
    payload_json TEXT,
    recorded_at REAL NOT NULL DEFAULT (unixepoch())
);

CREATE INDEX IF NOT EXISTS idx_trades_strategy ON trades(strategy_name);
CREATE INDEX IF NOT EXISTS idx_backtest_strategy ON backtest_runs(strategy_name);
CREATE INDEX IF NOT EXISTS idx_events_strategy ON strategy_events(strategy_name);
"""


class StrategyDB:
    """Async SQLite interface for strategy persistence."""

    def __init__(self, db_path: Path):
        self.db_path = db_path

    async def initialize(self) -> None:
        """Create tables if they don't exist."""
        import aiosqlite
        self.db_path.parent.mkdir(parents=True, exist_ok=True)
        async with aiosqlite.connect(self.db_path) as db:
            await db.executescript(_SCHEMA)
            await db.commit()
        log.info("StrategyDB initialized at %s", self.db_path)

    # ------------------------------------------------------------------
    # Strategy lifecycle
    # ------------------------------------------------------------------

    async def upsert_strategy(
        self,
        name: str,
        status: str,
        allocation: float,
        paper: bool,
        feeds: list[dict],
        git_rev: Optional[str] = None,
        worktree_path: Optional[str] = None,
        config: Optional[dict] = None,
    ) -> None:
        import aiosqlite
        now = time.time()
        async with aiosqlite.connect(self.db_path) as db:
            await db.execute("""
                INSERT INTO strategies
                    (name, status, git_rev, worktree_path, started_at, allocation, paper, feeds_json, config_json)
                VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
                ON CONFLICT(name) DO UPDATE SET
                    status=excluded.status,
                    git_rev=excluded.git_rev,
                    worktree_path=excluded.worktree_path,
                    started_at=excluded.started_at,
                    allocation=excluded.allocation,
                    paper=excluded.paper,
                    feeds_json=excluded.feeds_json,
                    config_json=excluded.config_json
            """, (
                name, status, git_rev, worktree_path, now,
                allocation, int(paper),
                json.dumps(feeds),
                json.dumps(config or {}),
            ))
            await db.commit()

    async def update_strategy_status(self, name: str, status: str) -> None:
        import aiosqlite
        async with aiosqlite.connect(self.db_path) as db:
            if status == "stopped":
                await db.execute(
                    "UPDATE strategies SET status=?, stopped_at=? WHERE name=?",
                    (status, time.time(), name)
                )
            else:
                await db.execute("UPDATE strategies SET status=? WHERE name=?", (status, name))
            await db.commit()

    async def get_strategy(self, name: str) -> Optional[dict]:
        import aiosqlite
        async with aiosqlite.connect(self.db_path) as db:
            db.row_factory = aiosqlite.Row
            async with db.execute("SELECT * FROM strategies WHERE name=?", (name,)) as cur:
                row = await cur.fetchone()
                return dict(row) if row else None

    async def get_all_strategies(self) -> list[dict]:
        import aiosqlite
        async with aiosqlite.connect(self.db_path) as db:
            db.row_factory = aiosqlite.Row
            async with db.execute("SELECT * FROM strategies ORDER BY started_at DESC") as cur:
                rows = await cur.fetchall()
                return [dict(r) for r in rows]

    async def get_running_strategies(self) -> list[dict]:
        import aiosqlite
        async with aiosqlite.connect(self.db_path) as db:
            db.row_factory = aiosqlite.Row
            async with db.execute(
                "SELECT * FROM strategies WHERE status='running' OR status='starting'",
            ) as cur:
                rows = await cur.fetchall()
                return [dict(r) for r in rows]

    # ------------------------------------------------------------------
    # PnL state
    # ------------------------------------------------------------------

    async def update_pnl_state(
        self,
        name: str,
        realized_pnl: float,
        unrealized_pnl: float,
        trade_count: int,
        positions: Optional[dict] = None,
    ) -> None:
        import aiosqlite
        async with aiosqlite.connect(self.db_path) as db:
            await db.execute("""
                INSERT INTO strategy_state
                    (name, realized_pnl, unrealized_pnl, trade_count, positions_json, updated_at)
                VALUES (?, ?, ?, ?, ?, ?)
                ON CONFLICT(name) DO UPDATE SET
                    realized_pnl=excluded.realized_pnl,
                    unrealized_pnl=excluded.unrealized_pnl,
                    trade_count=excluded.trade_count,
                    positions_json=excluded.positions_json,
                    updated_at=excluded.updated_at
            """, (name, realized_pnl, unrealized_pnl, trade_count,
                  json.dumps(positions or {}), time.time()))
            await db.commit()

    async def get_pnl_state(self, name: str) -> Optional[dict]:
        import aiosqlite
        async with aiosqlite.connect(self.db_path) as db:
            db.row_factory = aiosqlite.Row
            async with db.execute("SELECT * FROM strategy_state WHERE name=?", (name,)) as cur:
                row = await cur.fetchone()
                return dict(row) if row else None

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

    async def insert_trade(self, strategy_name: str, trade: dict) -> None:
        import aiosqlite
        async with aiosqlite.connect(self.db_path) as db:
            await db.execute("""
                INSERT INTO trades
                    (strategy_name, instrument, side, quantity, entry_price,
                     exit_price, entry_time, exit_time, pnl)
                VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
            """, (
                strategy_name,
                trade.get("instrument", ""),
                trade.get("side", ""),
                trade.get("quantity", 0),
                trade.get("entry_price"),
                trade.get("exit_price", 0),
                trade.get("entry_time"),
                trade.get("exit_time", time.time()),
                trade.get("pnl", 0),
            ))
            await db.commit()

    async def get_trades(self, strategy_name: str, limit: int = 200) -> list[dict]:
        import aiosqlite
        async with aiosqlite.connect(self.db_path) as db:
            db.row_factory = aiosqlite.Row
            async with db.execute(
                "SELECT * FROM trades WHERE strategy_name=? ORDER BY exit_time DESC LIMIT ?",
                (strategy_name, limit),
            ) as cur:
                rows = await cur.fetchall()
                return [dict(r) for r in rows]

    # ------------------------------------------------------------------
    # Backtest runs
    # ------------------------------------------------------------------

    async def insert_backtest(
        self,
        strategy_name: str,
        from_time: Any,
        to_time: Any,
        initial_capital: float,
        feeds: list[dict],
        summary: dict,
        statistics: dict,
        trades: list[dict],
        equity_curve: list[dict],
    ) -> int:
        import aiosqlite
        async with aiosqlite.connect(self.db_path) as db:
            cur = await db.execute("""
                INSERT INTO backtest_runs
                    (strategy_name, from_time, to_time, initial_capital, feeds_json,
                     summary_json, statistics_json, trades_json, equity_curve_json)
                VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
            """, (
                strategy_name,
                float(from_time) if from_time else None,
                float(to_time) if to_time else None,
                initial_capital,
                json.dumps(feeds),
                json.dumps(summary),
                json.dumps(statistics),
                json.dumps(trades[:500]),  # cap
                json.dumps(equity_curve),
            ))
            await db.commit()
            return cur.lastrowid

    async def get_backtests(self, strategy_name: str, limit: int = 10) -> list[dict]:
        import aiosqlite
        async with aiosqlite.connect(self.db_path) as db:
            db.row_factory = aiosqlite.Row
            async with db.execute(
                "SELECT * FROM backtest_runs WHERE strategy_name=? ORDER BY ran_at DESC LIMIT ?",
                (strategy_name, limit),
            ) as cur:
                rows = await cur.fetchall()
                result = []
                for r in rows:
                    d = dict(r)
                    for key in ("feeds_json", "summary_json", "statistics_json",
                                "trades_json", "equity_curve_json"):
                        if d.get(key):
                            plain = key.replace("_json", "")
                            d[plain] = json.loads(d.pop(key))
                        else:
                            d.pop(key, None)
                    result.append(d)
                return result

    # ------------------------------------------------------------------
    # Events
    # ------------------------------------------------------------------

    async def insert_event(self, strategy_name: str, event_type: str, payload: dict) -> None:
        import aiosqlite
        async with aiosqlite.connect(self.db_path) as db:
            await db.execute(
                "INSERT INTO strategy_events (strategy_name, event_type, payload_json) VALUES (?, ?, ?)",
                (strategy_name, event_type, json.dumps(payload)),
            )
            await db.commit()

    async def get_events(
        self,
        strategy_name: str,
        event_type: Optional[str] = None,
        limit: int = 100,
    ) -> list[dict]:
        import aiosqlite
        async with aiosqlite.connect(self.db_path) as db:
            db.row_factory = aiosqlite.Row
            if event_type:
                async with db.execute(
                    "SELECT * FROM strategy_events WHERE strategy_name=? AND event_type=? "
                    "ORDER BY recorded_at DESC LIMIT ?",
                    (strategy_name, event_type, limit),
                ) as cur:
                    rows = await cur.fetchall()
            else:
                async with db.execute(
                    "SELECT * FROM strategy_events WHERE strategy_name=? "
                    "ORDER BY recorded_at DESC LIMIT ?",
                    (strategy_name, limit),
                ) as cur:
                    rows = await cur.fetchall()
            result = []
            for r in rows:
                d = dict(r)
                if d.get("payload_json"):
                    d["payload"] = json.loads(d.pop("payload_json"))
                result.append(d)
            return result


# Singleton
_db: Optional[StrategyDB] = None


def get_strategy_db(data_dir: Optional[Path] = None) -> StrategyDB:
    global _db
    if _db is None:
        if data_dir is None:
            import os
            data_dir = Path(os.environ.get("DATA_DIR", "/app/data"))
        _db = StrategyDB(data_dir / "dexorder.db")
    return _db
