"""SQLite-backed event store for MCP tool call logging."""
from __future__ import annotations

import json
import sqlite3
import threading
import time
from datetime import datetime, timezone
from pathlib import Path


class EventStore:
    """Append-only event log backed by SQLite. Thread-safe."""

    def __init__(self, db_path: str | Path):
        self._db_path = str(db_path)
        self._conn: sqlite3.Connection | None = None
        self._lock = threading.Lock()

    def init(self) -> None:
        """Create the database and events table if needed."""
        self._conn = sqlite3.connect(self._db_path, check_same_thread=False)
        self._conn.row_factory = sqlite3.Row
        self._conn.execute("PRAGMA journal_mode=WAL")
        self._conn.execute("""
            CREATE TABLE IF NOT EXISTS events (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                timestamp REAL NOT NULL,
                tool TEXT NOT NULL,
                action TEXT NOT NULL,
                params TEXT NOT NULL DEFAULT '{}',
                result TEXT NOT NULL DEFAULT '',
                duration_ms INTEGER NOT NULL DEFAULT 0,
                status TEXT NOT NULL DEFAULT 'success',
                project TEXT NOT NULL DEFAULT ''
            )
        """)
        # Add columns to existing tables that lack them
        for col, default in [("project", "''"), ("caller", "''")]:
            try:
                self._conn.execute(f"ALTER TABLE events ADD COLUMN {col} TEXT NOT NULL DEFAULT {default}")
            except sqlite3.OperationalError:
                pass  # column already exists
        self._conn.execute("CREATE INDEX IF NOT EXISTS idx_events_tool ON events(tool)")
        self._conn.execute("CREATE INDEX IF NOT EXISTS idx_events_timestamp ON events(timestamp DESC)")
        self._conn.execute("CREATE INDEX IF NOT EXISTS idx_events_project ON events(project)")
        self._conn.execute("CREATE INDEX IF NOT EXISTS idx_events_tool_project ON events(tool, project)")
        # Project registry table
        self._conn.execute("""
            CREATE TABLE IF NOT EXISTS projects (
                name TEXT PRIMARY KEY,
                project_dir TEXT NOT NULL,
                last_seen REAL NOT NULL
            )
        """)
        self._conn.commit()

    def _get_conn(self) -> sqlite3.Connection:
        if self._conn is None:
            self.init()
        assert self._conn is not None
        return self._conn

    def record(self, *, tool: str, action: str, params: dict, result: str,
               duration_ms: int, status: str, project: str = "",
               caller: str = "") -> int:
        """Record a tool call event. Returns the event ID."""
        with self._lock:
            conn = self._get_conn()
            cur = conn.execute(
                "INSERT INTO events (timestamp, tool, action, params, result, duration_ms, status, project, caller) "
                "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
                (time.time(), tool, action, json.dumps(params, default=str),
                 result, duration_ms, status, project, caller),
            )
            conn.commit()
            assert cur.lastrowid is not None
            return cur.lastrowid

    def query(self, *, tool: str | None = None, project: str | None = None,
              limit: int = 500, before_id: int | None = None,
              exclude_transient: bool = False) -> list[dict]:
        """Query events, newest first."""
        with self._lock:
            conn = self._get_conn()
            clauses = []
            values: list = []
            if exclude_transient:
                clauses.append("status NOT IN ('running', 'progress')")
            if tool:
                clauses.append("tool = ?")
                values.append(tool)
            if project:
                clauses.append("project = ?")
                values.append(project)
            if before_id is not None:
                clauses.append("id < ?")
                values.append(before_id)
            where = f"WHERE {' AND '.join(clauses)}" if clauses else ""
            rows = conn.execute(
                f"SELECT id, timestamp, tool, action, params, result, duration_ms, status, project, caller "
                f"FROM events {where} ORDER BY timestamp DESC LIMIT ?",
                [*values, limit],
            ).fetchall()

        results = []
        for r in rows:
            ts = r["timestamp"]
            if isinstance(ts, (int, float)):
                ts = datetime.fromtimestamp(ts, tz=timezone.utc).isoformat()
            params = r["params"]
            if isinstance(params, str):
                try:
                    params = json.loads(params)
                except (json.JSONDecodeError, TypeError):
                    params = {}
            results.append({
                "id": r["id"], "timestamp": ts, "tool": r["tool"], "action": r["action"],
                "params": params, "result": r["result"], "duration_ms": r["duration_ms"],
                "status": r["status"], "project": r["project"],
                "caller": r["caller"] if "caller" in r.keys() else "",
            })
        return results

    def register_project(self, name: str, project_dir: str) -> None:
        """Upsert a project in the registry."""
        with self._lock:
            conn = self._get_conn()
            conn.execute(
                "INSERT INTO projects (name, project_dir, last_seen) VALUES (?, ?, ?) "
                "ON CONFLICT(name) DO UPDATE SET project_dir = excluded.project_dir, last_seen = excluded.last_seen",
                (name, project_dir, time.time()),
            )
            conn.commit()

    def get_project_dir(self, name: str) -> str | None:
        """Look up a project's directory by name."""
        with self._lock:
            conn = self._get_conn()
            row = conn.execute(
                "SELECT project_dir FROM projects WHERE name = ?", (name,)
            ).fetchone()
        return row["project_dir"] if row else None

    def list_projects(self) -> list[str]:
        """Return distinct project names that have events."""
        with self._lock:
            conn = self._get_conn()
            rows = conn.execute(
                "SELECT DISTINCT project FROM events WHERE project != '' ORDER BY project"
            ).fetchall()
        return [r["project"] for r in rows]

    def list_projects_with_dirs(self) -> list[dict]:
        """Return all registered projects with name, dir, and last_seen."""
        with self._lock:
            conn = self._get_conn()
            rows = conn.execute(
                "SELECT name, project_dir, last_seen FROM projects ORDER BY last_seen DESC"
            ).fetchall()
        return [
            {"name": r["name"], "project_dir": r["project_dir"], "last_seen": r["last_seen"]}
            for r in rows
        ]

    def session_stats(self, project: str | None = None) -> dict:
        """Aggregate stats for the current session."""
        with self._lock:
            conn = self._get_conn()
            conditions = ["status NOT IN ('running', 'progress')"]
            params: list = []
            if project:
                conditions.append("project = ?")
                params.append(project)
            where = " AND ".join(conditions)

            total = conn.execute(
                f"SELECT COUNT(*) AS cnt FROM events WHERE {where}",
                params,
            ).fetchone()["cnt"]

            err_conditions = ["status = 'error'"]
            err_params: list = []
            if project:
                err_conditions.append("project = ?")
                err_params.append(project)
            err_where = " AND ".join(err_conditions)

            errors = conn.execute(
                f"SELECT COUNT(*) AS cnt FROM events WHERE {err_where}",
                err_params,
            ).fetchone()["cnt"]

            by_tool_rows = conn.execute(
                f"SELECT tool, COUNT(*) AS cnt FROM events WHERE {where} GROUP BY tool",
                params,
            ).fetchall()
        return {"total_calls": total, "errors": errors, "by_tool": {r["tool"]: r["cnt"] for r in by_tool_rows}}
