from __future__ import annotations

import sqlite3
import time
from collections.abc import Iterator
from contextlib import contextmanager
from dataclasses import dataclass
from pathlib import Path
from typing import Any

from ocr_pipeline.utils.logging import get_logger

logger = get_logger(__name__)


@dataclass(frozen=True)
class ProcessedFile:
    """Legacy value object retained for import compatibility."""

    path: str
    hash: str
    timestamp: float
    run_id: int | None = None
    size: int = 0
    mtime: float = 0.0


class Database:
    def __init__(self, db_path: Path) -> None:
        self.db_path = db_path.expanduser().resolve()
        self.db_path.parent.mkdir(parents=True, exist_ok=True)
        self._init_db()

    @contextmanager
    def _conn(self) -> Iterator[sqlite3.Connection]:
        conn = sqlite3.connect(self.db_path)
        conn.row_factory = sqlite3.Row
        try:
            yield conn
            conn.commit()
        finally:
            conn.close()

    def _init_db(self) -> None:
        with self._conn() as conn:
            conn.executescript("""
                CREATE TABLE IF NOT EXISTS processed_files (
                    hash TEXT PRIMARY KEY,
                    path TEXT NOT NULL,
                    timestamp TEXT NOT NULL,
                    ocr_engine TEXT,
                    confidence REAL,
                    chunks_created INTEGER DEFAULT 0,
                    run_id INTEGER,
                    created_at REAL DEFAULT (strftime('%s', 'now'))
                );
                CREATE TABLE IF NOT EXISTS runs (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    date TEXT NOT NULL,
                    run_number INTEGER NOT NULL,
                    total_images INTEGER DEFAULT 0,
                    successful INTEGER DEFAULT 0,
                    failed INTEGER DEFAULT 0,
                    chunks_created INTEGER DEFAULT 0,
                    started_at REAL DEFAULT (strftime('%s', 'now')),
                    completed_at REAL,
                    UNIQUE(date, run_number)
                );
                CREATE INDEX IF NOT EXISTS idx_processed_hash ON processed_files(hash);
                CREATE INDEX IF NOT EXISTS idx_runs_date ON runs(date);
            """)
            columns = {row[1] for row in conn.execute("PRAGMA table_info(processed_files)")}
            if "run_id" not in columns:
                conn.execute("ALTER TABLE processed_files ADD COLUMN run_id INTEGER")

    def is_processed(self, file_hash: str, path: Path | str | None = None) -> bool:
        with self._conn() as conn:
            if path is None:
                row = conn.execute(
                    "SELECT 1 FROM processed_files WHERE hash = ?", (file_hash,)
                ).fetchone()
            else:
                row = conn.execute(
                    "SELECT 1 FROM processed_files WHERE hash = ? AND path = ?",
                    (file_hash, str(path)),
                ).fetchone()
            return row is not None

    def get_processed(self, file_hash: str) -> dict[str, Any] | None:
        with self._conn() as conn:
            row = conn.execute(
                "SELECT * FROM processed_files WHERE hash = ?", (file_hash,)
            ).fetchone()
            return dict(row) if row else None

    def mark_processed(
        self,
        *,
        file_hash: str,
        path: str,
        timestamp: float | str,
        engine: str = "",
        confidence: float = 0.0,
        chunks: int = 0,
        run_id: int | None = None,
    ) -> None:
        with self._conn() as conn:
            conn.execute(
                """INSERT OR REPLACE INTO processed_files
                (hash, path, timestamp, ocr_engine, confidence, chunks_created, run_id)
                VALUES (?, ?, ?, ?, ?, ?, ?)""",
                (file_hash, path, str(timestamp), engine, confidence, chunks, run_id),
            )

    def start_run(self, date: str | None = None) -> tuple[int, int]:
        date = date or time.strftime("%Y-%m-%d")
        with self._conn() as conn:
            run_number = conn.execute(
                "SELECT COALESCE(MAX(run_number), 0) + 1 FROM runs WHERE date = ?", (date,)
            ).fetchone()[0]
            cursor = conn.execute(
                "INSERT INTO runs (date, run_number) VALUES (?, ?)", (date, run_number)
            )
            return int(cursor.lastrowid), int(run_number)

    def complete_run(self, run_id: int, stats: dict[str, int]) -> None:
        with self._conn() as conn:
            conn.execute(
                """UPDATE runs SET total_images = ?, successful = ?, failed = ?, chunks_created = ?, completed_at = ? WHERE id = ?""",
                (
                    stats.get("total", 0),
                    stats.get("successful", 0),
                    stats.get("failed", 0),
                    stats.get("chunks", 0),
                    time.time(),
                    run_id,
                ),
            )

    def get_stats(self, days: int = 30) -> dict[str, Any]:
        cutoff = time.time() - days * 86400
        with self._conn() as conn:
            files = conn.execute(
                "SELECT COUNT(*) AS total_processed, COALESCE(SUM(chunks_created), 0) AS total_chunks, AVG(confidence) AS avg_confidence FROM processed_files WHERE created_at >= ?",
                (cutoff,),
            ).fetchone()
            runs = conn.execute(
                "SELECT COUNT(*) AS total_runs, MAX(completed_at) AS latest_run FROM runs WHERE started_at >= ?",
                (cutoff,),
            ).fetchone()
            return {**dict(files), **dict(runs)}


_db_instance: Database | None = None


def get_db() -> Database:
    global _db_instance
    if _db_instance is None:
        from ocr_pipeline.config import settings

        _db_instance = Database(settings.watch_db_path)
    return _db_instance
