"""Local SQLite dedupe store for Scout findings."""

from __future__ import annotations

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

from .models import EvidenceRow, ScoutFinding

DEFAULT_DB_PATH = Path.home() / ".g_gremlin" / "state" / "scout_seen.sqlite"


def _utc_now_iso() -> str:
    return datetime.now(timezone.utc).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 _chunks(items: List[str], size: int = 500) -> Iterable[List[str]]:
    for i in range(0, len(items), size):
        yield items[i : i + size]


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()


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

    def _connect(self) -> sqlite3.Connection:
        conn = sqlite3.connect(self.db_path)
        conn.row_factory = sqlite3.Row
        return conn

    def _ensure_schema(self) -> None:
        with self._connect() as conn:
            conn.execute(
                """
                CREATE TABLE IF NOT EXISTS seen_findings (
                    finding_hash TEXT PRIMARY KEY,
                    scout_name TEXT,
                    record_id TEXT,
                    first_seen TEXT,
                    last_seen TEXT
                )
                """
            )

    def mark_seen(self, findings: Iterable[ScoutFinding]) -> None:
        now = _utc_now_iso()
        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 seen_findings (finding_hash, scout_name, record_id, first_seen, last_seen)
                        VALUES (?, ?, ?, ?, ?)
                        ON CONFLICT(finding_hash) DO UPDATE SET last_seen = excluded.last_seen
                        """,
                        (hash_value, finding.scout_name, row.record_id, now, now),
                    )

    def filter_new(self, findings: Iterable[ScoutFinding], window_days: int = 7) -> List[ScoutFinding]:
        cutoff = datetime.now(timezone.utc) - timedelta(days=window_days)
        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
            ]

            seen_map: dict[str, datetime] = {}
            with self._connect() as conn:
                for chunk in _chunks(hashes):
                    placeholders = ",".join("?" for _ in chunk)
                    query = f"SELECT finding_hash, last_seen FROM seen_findings WHERE finding_hash IN ({placeholders})"
                    for row in conn.execute(query, chunk):
                        seen_map[row["finding_hash"]] = _parse_iso(row["last_seen"]) or datetime.min.replace(tzinfo=timezone.utc)

            new_rows: List[EvidenceRow] = []
            for row in finding.evidence:
                hash_value = _finding_hash(finding.scout_name, row.record_id, row.reason_code)
                last_seen = seen_map.get(hash_value)
                if not last_seen or last_seen < cutoff:
                    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, older_than_days: int = 90) -> int:
        cutoff = datetime.now(timezone.utc) - timedelta(days=older_than_days)
        with self._connect() as conn:
            cursor = conn.execute(
                "DELETE FROM seen_findings WHERE last_seen < ?",
                (cutoff.isoformat(),),
            )
            return cursor.rowcount or 0


__all__ = ["SeenStore", "DEFAULT_DB_PATH"]
