"""SQLite-backed suppression store for Autopilot snooze."""

from __future__ import annotations

import hashlib
import sqlite3
from contextlib import contextmanager
from dataclasses import replace
from datetime import datetime, timedelta, timezone
from pathlib import Path
from typing import Iterable, List

from g_gremlin.scout.models import EvidenceRow, ScoutFinding


def _utc_now() -> datetime:
    return datetime.now(timezone.utc)


def _utc_now_iso() -> str:
    return _utc_now().isoformat()


def _parse_iso(value: str | None) -> datetime | None:
    if not value:
        return None
    try:
        return datetime.fromisoformat(value.replace("Z", "+00:00"))
    except Exception:
        return None


def _finding_hash(scout_name: str, record_id: str, reason_code: str) -> str:
    base = f"{scout_name}:{record_id}:{reason_code}"
    return hashlib.sha256(base.encode("utf-8")).hexdigest()


def _chunks(items: List[str], size: int = 500) -> Iterable[List[str]]:
    for i in range(0, len(items), size):
        yield items[i : i + size]


class SuppressStore:
    def __init__(self, db_path: Path) -> None:
        self.db_path = Path(db_path)
        self.db_path.parent.mkdir(parents=True, exist_ok=True)
        self._ensure_schema()

    @contextmanager
    def _connect(self) -> Iterable[sqlite3.Connection]:
        conn = sqlite3.connect(self.db_path)
        conn.row_factory = sqlite3.Row
        try:
            yield conn
            conn.commit()
        except Exception:
            conn.rollback()
            raise
        finally:
            conn.close()

    def _ensure_schema(self) -> None:
        with self._connect() as conn:
            conn.execute(
                """
                CREATE TABLE IF NOT EXISTS suppressed_findings (
                    finding_hash TEXT PRIMARY KEY,
                    scout_name TEXT,
                    record_id TEXT,
                    reason_code TEXT,
                    created_at TEXT,
                    expires_at TEXT,
                    comment TEXT
                )
                """
            )
            conn.execute(
                """
                CREATE TABLE IF NOT EXISTS suppressed_runs (
                    run_id TEXT PRIMARY KEY,
                    created_at TEXT,
                    expires_at TEXT,
                    comment TEXT
                )
                """
            )

    def suppress_findings(
        self,
        findings: Iterable[ScoutFinding],
        *,
        days: int = 7,
        comment: str = "",
    ) -> int:
        expires_at = _utc_now() + timedelta(days=days)
        now = _utc_now_iso()
        count = 0
        with self._connect() as conn:
            for finding in findings:
                for row in finding.evidence:
                    if not row.record_id:
                        continue
                    hash_value = _finding_hash(finding.scout_name, row.record_id, row.reason_code)
                    conn.execute(
                        """
                        INSERT INTO suppressed_findings (
                            finding_hash, scout_name, record_id, reason_code,
                            created_at, expires_at, comment
                        )
                        VALUES (?, ?, ?, ?, ?, ?, ?)
                        ON CONFLICT(finding_hash) DO UPDATE SET
                          expires_at = excluded.expires_at,
                          comment = excluded.comment
                        """,
                        (
                            hash_value,
                            finding.scout_name,
                            row.record_id,
                            row.reason_code,
                            now,
                            expires_at.isoformat(),
                            comment,
                        ),
                    )
                    count += 1
        return count

    def suppress_run(self, run_id: str, *, days: int = 7, comment: str = "") -> None:
        now = _utc_now()
        expires_at = now + timedelta(days=days)
        with self._connect() as conn:
            conn.execute(
                """
                INSERT INTO suppressed_runs (run_id, created_at, expires_at, comment)
                VALUES (?, ?, ?, ?)
                ON CONFLICT(run_id) DO UPDATE SET
                  expires_at = excluded.expires_at,
                  comment = excluded.comment
                """,
                (run_id, now.isoformat(), expires_at.isoformat(), comment),
            )

    def filter_suppressed(self, findings: Iterable[ScoutFinding]) -> List[ScoutFinding]:
        now = _utc_now()
        filtered: List[ScoutFinding] = []

        for finding in findings:
            if not finding.evidence:
                filtered.append(finding)
                continue

            hashes = [
                _finding_hash(finding.scout_name, row.record_id, row.reason_code)
                for row in finding.evidence
                if row.record_id
            ]

            suppressed: set[str] = set()
            with self._connect() as conn:
                for chunk in _chunks(hashes):
                    placeholders = ",".join("?" for _ in chunk)
                    query = (
                        "SELECT finding_hash, expires_at FROM suppressed_findings "
                        f"WHERE finding_hash IN ({placeholders})"
                    )
                    for row in conn.execute(query, chunk):
                        expires_at = _parse_iso(row["expires_at"])
                        if expires_at and expires_at > now:
                            suppressed.add(row["finding_hash"])

            new_rows: List[EvidenceRow] = []
            for row in finding.evidence:
                if not row.record_id:
                    continue
                hash_value = _finding_hash(finding.scout_name, row.record_id, row.reason_code)
                if hash_value in suppressed:
                    continue
                new_rows.append(row)

            filtered.append(
                replace(
                    finding,
                    evidence=new_rows,
                    findings_count=len(new_rows),
                    status="findings" if new_rows else "ok",
                )
            )

        return filtered

    def cleanup(self) -> int:
        now = _utc_now().isoformat()
        with self._connect() as conn:
            cursor = conn.execute(
                "DELETE FROM suppressed_findings WHERE expires_at <= ?",
                (now,),
            )
            conn.execute(
                "DELETE FROM suppressed_runs WHERE expires_at <= ?",
                (now,),
            )
            return cursor.rowcount or 0


__all__ = ["SuppressStore"]
