from __future__ import annotations

import json
import sqlite3
import threading
from pathlib import Path
from typing import Any, Dict, List, Optional

from mockmesh.core.logger import get_logger
from mockmesh.storage.base import BaseStorage
from mockmesh.storage.utils import sanitize_name, utc_now

_log = get_logger("storage.sql")
_write_lock = threading.Lock()


class SQLStorage(BaseStorage):

    backend_type = "sql"

    def __init__(self, root: Path) -> None:
        super().__init__(root)
        self._db_path = root / "sql" / "standalone" / "mockmesh.db"
        self._conn: Optional[sqlite3.Connection] = None

    # ------------------------------------------------------------------
    # Lifecycle
    # ------------------------------------------------------------------

    def initialize(self) -> None:
        self._db_path.parent.mkdir(parents=True, exist_ok=True)
        self._conn = sqlite3.connect(str(self._db_path), check_same_thread=False)
        self._conn.row_factory = sqlite3.Row
        self._conn.execute("PRAGMA journal_mode=WAL")
        self._conn.execute("PRAGMA foreign_keys=ON")
        self._ready = True
        _log.info("SQLStorage ready", extra={"mm_db": str(self._db_path)})

    def close(self) -> None:
        if self._conn:
            self._conn.close()
            self._conn = None
        self._ready = False

    # ------------------------------------------------------------------
    # Schema
    # ------------------------------------------------------------------

    def _ensure_table(self, collection: str) -> None:
        sql = f"""
            CREATE TABLE IF NOT EXISTS "{sanitize_name(collection)}" (
                key        TEXT PRIMARY KEY,
                data       TEXT NOT NULL,
                updated_at TEXT NOT NULL
            )
        """
        with _write_lock:
            self._conn.execute(sql)
            self._conn.commit()

    # ------------------------------------------------------------------
    # CRUD
    # ------------------------------------------------------------------

    def put(self, collection: str, key: str, value: Dict[str, Any]) -> None:
        self._ensure_table(collection)
        sql = f"""
            INSERT INTO "{sanitize_name(collection)}" (key, data, updated_at)
            VALUES (?, ?, ?)
            ON CONFLICT(key) DO UPDATE SET data=excluded.data, updated_at=excluded.updated_at
        """
        with _write_lock:
            self._conn.execute(sql, (key, json.dumps(value), utc_now()))
            self._conn.commit()
        _log.debug("SQL put", extra={"mm_collection": collection, "mm_key": key})

    def get(self, collection: str, key: str) -> Optional[Dict[str, Any]]:
        self._ensure_table(collection)
        cur = self._conn.execute(
            f'SELECT data FROM "{sanitize_name(collection)}" WHERE key = ?', (key,)
        )
        row = cur.fetchone()
        if row is None:
            return None
        return json.loads(row["data"])

    def delete(self, collection: str, key: str) -> bool:
        self._ensure_table(collection)
        with _write_lock:
            cur = self._conn.execute(
                f'DELETE FROM "{sanitize_name(collection)}" WHERE key = ?', (key,)
            )
            self._conn.commit()
        return cur.rowcount > 0

    def list(self, collection: str, prefix: str = "") -> List[Dict[str, Any]]:
        self._ensure_table(collection)
        if prefix:
            cur = self._conn.execute(
                f'SELECT key, data FROM "{sanitize_name(collection)}" WHERE key LIKE ?',
                (f"{prefix}%",),
            )
        else:
            cur = self._conn.execute(
                f'SELECT key, data FROM "{sanitize_name(collection)}"'
            )
        return [{"key": row["key"], "value": json.loads(row["data"])} for row in cur.fetchall()]

    def query(self, collection: str, **filters: Any) -> List[Dict[str, Any]]:
        all_records = self.list(collection)
        if not filters:
            return all_records
        result = []
        for record in all_records:
            v = record["value"]
            if all(v.get(k) == val for k, val in filters.items()):
                result.append(record)
        return result
