"""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 g_gremlin.paths import gremlin_path

from .models import EvidenceRow, ScoutFinding

DEFAULT_DB_PATH = gremlin_path("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"]
