"""
Supreme 2 MAX — MCP State Management

Thread-safe scan state persistence using SQLite metadata + JSON result files.

Architecture
~~~~~~~~~~~~
* **SQLite DB** at ``~/.supreme2l/mcp/scans.db``   — lightweight metadata index.
* **JSON files** at ``~/.supreme2l/mcp/results/{scan_id}.json`` — full scan payloads.

Every public method opens its own ``sqlite3.Connection`` and closes it before
returning, which makes the class safe for concurrent usage from multiple threads
or async tasks without external locking.
"""

from __future__ import annotations

import json
import sqlite3
import uuid
from dataclasses import dataclass
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Dict, List, Optional


# ---------------------------------------------------------------------------
# Dataclass
# ---------------------------------------------------------------------------

@dataclass
class ScanState:
    """Immutable snapshot of a single scan's metadata."""

    scan_id: str
    project_path: str
    timestamp: str
    scanners_used: List[str]
    files_scanned: int
    issues_found: int
    scan_time: float
    results_path: str
    report_path: str | None


# ---------------------------------------------------------------------------
# Paths
# ---------------------------------------------------------------------------

_BASE_DIR = Path.home() / ".supreme2l" / "mcp"
_DB_PATH = _BASE_DIR / "scans.db"
_RESULTS_DIR = _BASE_DIR / "results"


# ---------------------------------------------------------------------------
# Manager
# ---------------------------------------------------------------------------

class ScanStateManager:
    """Thread-safe scan state manager backed by SQLite + JSON files."""

    def __init__(
        self,
        db_path: Path | None = None,
        results_dir: Path | None = None,
    ) -> None:
        self._db_path = db_path or _DB_PATH
        self._results_dir = results_dir or _RESULTS_DIR

        # Ensure directories exist (idempotent).
        self._db_path.parent.mkdir(parents=True, exist_ok=True)
        self._results_dir.mkdir(parents=True, exist_ok=True)

        # Create the table if it doesn't exist yet.
        self._init_db()

    # ------------------------------------------------------------------
    # Internal helpers
    # ------------------------------------------------------------------

    def _connect(self) -> sqlite3.Connection:
        """Open a **new** connection — caller must close it."""
        conn = sqlite3.connect(str(self._db_path), timeout=10)
        conn.row_factory = sqlite3.Row
        conn.execute("PRAGMA journal_mode=WAL")
        return conn

    def _init_db(self) -> None:
        """Create the ``scans`` table if it does not already exist."""
        conn = self._connect()
        try:
            conn.execute(
                """
                CREATE TABLE IF NOT EXISTS scans (
                    scan_id        TEXT PRIMARY KEY,
                    project_path   TEXT    NOT NULL,
                    timestamp      TEXT    NOT NULL,
                    scanners_used  TEXT    NOT NULL,   -- JSON array
                    files_scanned  INTEGER NOT NULL,
                    issues_found   INTEGER NOT NULL,
                    scan_time      REAL    NOT NULL,
                    results_path   TEXT    NOT NULL,
                    report_path    TEXT                -- nullable
                )
                """
            )
            conn.commit()
        finally:
            conn.close()

    # ------------------------------------------------------------------
    # Public API
    # ------------------------------------------------------------------

    def store_scan(self, scan_result: Dict[str, Any]) -> str:
        """Persist a full scan result and return the generated ``scan_id``.

        Parameters
        ----------
        scan_result:
            The raw scan result dict produced by the scanner engine.  Must
            contain at least ``project_path``, ``scanners_used``,
            ``files_scanned``, ``issues_found``, and ``scan_time``.
            All remaining fields are stored verbatim in the JSON file.

        Returns
        -------
        str
            The UUID-based ``scan_id`` assigned to this scan.
        """
        scan_id = uuid.uuid4().hex

        # Persist full JSON payload -----------------------------------------------
        results_path = self._results_dir / f"{scan_id}.json"
        results_path.write_text(json.dumps(scan_result, indent=2, default=str), encoding="utf-8")

        # Extract metadata --------------------------------------------------------
        timestamp = scan_result.get(
            "timestamp",
            datetime.now(timezone.utc).isoformat(),
        )
        scanners_used: List[str] = scan_result.get("scanners_used", [])

        # Count issues if a flat list; otherwise fall back to provided int.
        issues = scan_result.get("issues", [])
        issues_found = scan_result.get("issues_found", len(issues) if isinstance(issues, list) else 0)

        meta = ScanState(
            scan_id=scan_id,
            project_path=str(scan_result.get("project_path", "")),
            timestamp=str(timestamp),
            scanners_used=scanners_used,
            files_scanned=int(scan_result.get("files_scanned", 0)),
            issues_found=int(issues_found),
            scan_time=float(scan_result.get("scan_time", 0.0)),
            results_path=str(results_path),
            report_path=None,
        )

        # Write metadata to SQLite ------------------------------------------------
        conn = self._connect()
        try:
            conn.execute(
                """
                INSERT INTO scans
                    (scan_id, project_path, timestamp, scanners_used,
                     files_scanned, issues_found, scan_time,
                     results_path, report_path)
                VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
                """,
                (
                    meta.scan_id,
                    meta.project_path,
                    meta.timestamp,
                    json.dumps(meta.scanners_used),
                    meta.files_scanned,
                    meta.issues_found,
                    meta.scan_time,
                    meta.results_path,
                    meta.report_path,
                ),
            )
            conn.commit()
        finally:
            conn.close()

        return scan_id

    # ------------------------------------------------------------------

    def get_scan(self, scan_id: str) -> Optional[Dict[str, Any]]:
        """Return the full JSON scan result for *scan_id*.

        Accepts the literal string ``"latest"`` as a convenience shortcut.
        Returns ``None`` when the scan is not found.
        """
        if scan_id == "latest":
            return self.get_latest_scan()

        meta = self.get_scan_metadata(scan_id)
        if meta is None:
            return None

        results_path = Path(meta["results_path"])
        if not results_path.is_file():
            return None

        return json.loads(results_path.read_text(encoding="utf-8"))

    # ------------------------------------------------------------------

    def get_latest_scan(self) -> Optional[Dict[str, Any]]:
        """Return the full JSON result of the most recent scan, or ``None``."""
        conn = self._connect()
        try:
            row = conn.execute(
                "SELECT * FROM scans ORDER BY timestamp DESC LIMIT 1"
            ).fetchone()
        finally:
            conn.close()

        if row is None:
            return None

        results_path = Path(row["results_path"])
        if not results_path.is_file():
            return None

        return json.loads(results_path.read_text(encoding="utf-8"))

    # ------------------------------------------------------------------

    def get_scan_metadata(self, scan_id: str) -> Optional[Dict[str, Any]]:
        """Return the SQLite metadata row as a plain ``dict``, or ``None``."""
        if scan_id == "latest":
            conn = self._connect()
            try:
                row = conn.execute(
                    "SELECT * FROM scans ORDER BY timestamp DESC LIMIT 1"
                ).fetchone()
            finally:
                conn.close()
        else:
            conn = self._connect()
            try:
                row = conn.execute(
                    "SELECT * FROM scans WHERE scan_id = ?", (scan_id,)
                ).fetchone()
            finally:
                conn.close()

        if row is None:
            return None

        data = dict(row)
        # Deserialize JSON-encoded columns.
        data["scanners_used"] = json.loads(data["scanners_used"])
        return data

    # ------------------------------------------------------------------

    def update_scan_report_path(self, scan_id: str, report_path: str) -> None:
        """Set the ``report_path`` column for an existing scan."""
        conn = self._connect()
        try:
            conn.execute(
                "UPDATE scans SET report_path = ? WHERE scan_id = ?",
                (report_path, scan_id),
            )
            conn.commit()
        finally:
            conn.close()

    # ------------------------------------------------------------------

    def list_scans(self, limit: int = 50) -> List[Dict[str, Any]]:
        """Return the *limit* most recent scan metadata rows."""
        conn = self._connect()
        try:
            rows = conn.execute(
                "SELECT * FROM scans ORDER BY timestamp DESC LIMIT ?",
                (limit,),
            ).fetchall()
        finally:
            conn.close()

        result: List[Dict[str, Any]] = []
        for row in rows:
            data = dict(row)
            data["scanners_used"] = json.loads(data["scanners_used"])
            result.append(data)
        return result
