from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path
import sqlite3
from typing import Iterable


@dataclass(slots=True)
class FileState:
    rel_path: str
    source_hash: str | None
    target_hash: str | None


class StateStore:
    def __init__(self, db_path: Path) -> None:
        self.db_path = db_path
        self.db_path.parent.mkdir(parents=True, exist_ok=True)
        self._conn = sqlite3.connect(str(self.db_path))
        self._conn.row_factory = sqlite3.Row
        self._init_schema()

    def close(self) -> None:
        self._conn.close()

    def _init_schema(self) -> None:
        self._conn.execute(
            """
            CREATE TABLE IF NOT EXISTS file_state (
                rel_path TEXT PRIMARY KEY,
                source_hash TEXT,
                target_hash TEXT,
                updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
            )
            """
        )
        self._conn.execute(
            """
            CREATE TABLE IF NOT EXISTS metadata (
                key TEXT PRIMARY KEY,
                value TEXT NOT NULL
            )
            """
        )
        self._conn.commit()

    def get_file_state(self, rel_path: str) -> FileState | None:
        row = self._conn.execute(
            "SELECT rel_path, source_hash, target_hash FROM file_state WHERE rel_path = ?",
            (rel_path,),
        ).fetchone()
        if row is None:
            return None
        return FileState(
            rel_path=row["rel_path"],
            source_hash=row["source_hash"],
            target_hash=row["target_hash"],
        )

    def upsert_file_states(self, states: Iterable[FileState]) -> None:
        self._conn.executemany(
            """
            INSERT INTO file_state (rel_path, source_hash, target_hash)
            VALUES (?, ?, ?)
            ON CONFLICT(rel_path) DO UPDATE SET
                source_hash = excluded.source_hash,
                target_hash = excluded.target_hash,
                updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
            """,
            [(s.rel_path, s.source_hash, s.target_hash) for s in states],
        )
        self._conn.commit()

    def delete_not_in(self, rel_paths: set[str]) -> None:
        if not rel_paths:
            self._conn.execute("DELETE FROM file_state")
            self._conn.commit()
            return

        placeholders = ",".join("?" for _ in rel_paths)
        self._conn.execute(
            f"DELETE FROM file_state WHERE rel_path NOT IN ({placeholders})",
            tuple(rel_paths),
        )
        self._conn.commit()

    def count_files(self) -> int:
        row = self._conn.execute("SELECT COUNT(*) AS c FROM file_state").fetchone()
        return int(row["c"] if row else 0)

    def get_metadata(self, key: str) -> str | None:
        row = self._conn.execute("SELECT value FROM metadata WHERE key = ?", (key,)).fetchone()
        if row is None:
            return None
        return str(row["value"])

    def set_metadata(self, key: str, value: str) -> None:
        self._conn.execute(
            """
            INSERT INTO metadata (key, value)
            VALUES (?, ?)
            ON CONFLICT(key) DO UPDATE SET value = excluded.value
            """,
            (key, value),
        )
        self._conn.commit()
