from __future__ import annotations

import json
import os
import sqlite3
import time
from pathlib import Path
from typing import Any

import structlog

SQLITE_HEADER = b"SQLite format 3\x00"
_data_dir = os.environ.get("PROXYM_DATA_DIR")
DEFAULT_SQLITE_PATH = (
    Path(_data_dir).expanduser() / "proxym.sqlite"
    if _data_dir
    else Path.home() / ".local" / "share" / "proxym" / "proxym.sqlite"
)
logger = structlog.get_logger()


def is_sqlite_database(path: Path) -> bool:
    try:
        with path.open("rb") as f:
            return f.read(len(SQLITE_HEADER)) == SQLITE_HEADER
    except OSError:
        return False


class SQLiteNamespaceStore:
    def __init__(self, path: str | Path | None = None) -> None:
        self.path = Path(path).expanduser() if path is not None else DEFAULT_SQLITE_PATH

    def load_namespace(self, namespace: str) -> dict[str, dict[str, Any]]:
        if not self.path.exists() or not is_sqlite_database(self.path):
            return {}

        try:
            with self._connect() as conn:
                rows = conn.execute(
                    "SELECT item_id, payload FROM namespace_records "
                    "WHERE namespace = ? ORDER BY item_id",
                    (namespace,),
                ).fetchall()

            data: dict[str, dict[str, Any]] = {}
            for row in rows:
                try:
                    data[row["item_id"]] = json.loads(row["payload"])
                except json.JSONDecodeError:
                    continue
            return data
        except (OSError, sqlite3.Error) as exc:
            logger.warning(
                "sqlite_namespace_load_failed",
                namespace=namespace,
                path=str(self.path),
                error=str(exc),
            )
            return {}

    def save_namespace(self, namespace: str, items: dict[str, dict[str, Any]]) -> None:
        try:
            with self._connect() as conn:
                conn.execute("DELETE FROM namespace_records WHERE namespace = ?", (namespace,))
                now = time.time()
                conn.executemany(
                    "INSERT INTO namespace_records(namespace, item_id, payload, updated_at) VALUES (?, ?, ?, ?)",
                    (
                        (
                            namespace,
                            item_id,
                            json.dumps(payload, ensure_ascii=False, default=str),
                            now,
                        )
                        for item_id, payload in items.items()
                    ),
                )
        except (OSError, sqlite3.Error) as exc:
            logger.warning(
                "sqlite_namespace_save_failed",
                namespace=namespace,
                path=str(self.path),
                error=str(exc),
            )

    def save_namespaces(self, payloads: dict[str, dict[str, dict[str, Any]]]) -> None:
        try:
            with self._connect() as conn:
                now = time.time()
                for namespace, items in payloads.items():
                    conn.execute("DELETE FROM namespace_records WHERE namespace = ?", (namespace,))
                    conn.executemany(
                        "INSERT INTO namespace_records(namespace, item_id, payload, updated_at) VALUES (?, ?, ?, ?)",
                        (
                            (
                                namespace,
                                item_id,
                                json.dumps(payload, ensure_ascii=False, default=str),
                                now,
                            )
                            for item_id, payload in items.items()
                        ),
                    )
        except (OSError, sqlite3.Error) as exc:
            logger.warning(
                "sqlite_namespace_save_failed",
                path=str(self.path),
                namespaces=sorted(payloads),
                error=str(exc),
            )

    def _connect(self) -> sqlite3.Connection:
        if self.path.exists() and not is_sqlite_database(self.path):
            self.path.unlink()
        self.path.parent.mkdir(parents=True, exist_ok=True)
        conn = sqlite3.connect(self.path, timeout=5)
        conn.row_factory = sqlite3.Row
        conn.execute("PRAGMA foreign_keys = ON")
        conn.execute("PRAGMA journal_mode = WAL")
        conn.execute("PRAGMA synchronous = NORMAL")
        conn.execute("PRAGMA busy_timeout = 5000")
        conn.execute(
            "CREATE TABLE IF NOT EXISTS namespace_records ("
            "    namespace TEXT NOT NULL,"
            "    item_id TEXT NOT NULL,"
            "    payload TEXT NOT NULL,"
            "    updated_at REAL NOT NULL,"
            "    PRIMARY KEY (namespace, item_id)"
            ")"
        )
        conn.execute(
            "CREATE INDEX IF NOT EXISTS idx_namespace_records_namespace "
            "ON namespace_records(namespace)"
        )
        return conn
