"""SQLite-backed audit log for platform integration events."""

from __future__ import annotations

import json
import sqlite3
import uuid
from contextlib import contextmanager
from datetime import datetime, timedelta, timezone
from pathlib import Path
from typing import Any, Iterator

_AUDIT_SCHEMA_VERSION = 1
_VERSION_KEY = "audit_schema_version"
_ALLOWED_SEVERITIES = {"info", "warning", "error"}


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


def _coerce_iso(value: str | None) -> str | None:
    if not value:
        return None
    try:
        parsed = datetime.fromisoformat(value.replace("Z", "+00:00"))
    except Exception:
        return None
    if parsed.tzinfo is None:
        parsed = parsed.replace(tzinfo=timezone.utc)
    return parsed.astimezone(timezone.utc).isoformat()


class AuditLog:
    def __init__(self, db_path: Path):
        self.db_path = Path(db_path)
        self.db_path.parent.mkdir(parents=True, exist_ok=True)
        self._init_db()

    @contextmanager
    def _connect(self) -> Iterator[sqlite3.Connection]:
        conn = sqlite3.connect(self.db_path)
        conn.row_factory = sqlite3.Row
        conn.execute("PRAGMA journal_mode=WAL")
        conn.execute("PRAGMA busy_timeout=5000")
        try:
            yield conn
            conn.commit()
        except Exception:
            conn.rollback()
            raise
        finally:
            conn.close()

    def _init_db(self) -> None:
        with self._connect() as conn:
            conn.execute(
                """
                CREATE TABLE IF NOT EXISTS _meta (
                    key TEXT PRIMARY KEY,
                    value TEXT NOT NULL,
                    updated_at TEXT NOT NULL
                )
                """
            )
            current = self._read_version(conn)
            while current < _AUDIT_SCHEMA_VERSION:
                if current == 0:
                    self._migrate_v1(conn)
                    current = 1
                else:
                    raise ValueError(f"Unsupported audit schema version: {current}")
            self._write_version(conn, current)

    def _read_version(self, conn: sqlite3.Connection) -> int:
        row = conn.execute(
            "SELECT value FROM _meta WHERE key = ?",
            (_VERSION_KEY,),
        ).fetchone()
        if not row:
            return 0
        try:
            return int(row["value"])
        except Exception:
            return 0

    def _write_version(self, conn: sqlite3.Connection, version: int) -> None:
        now = _utc_now().isoformat()
        conn.execute(
            """
            INSERT INTO _meta(key, value, updated_at)
            VALUES (?, ?, ?)
            ON CONFLICT(key) DO UPDATE SET
                value = excluded.value,
                updated_at = excluded.updated_at
            """,
            (_VERSION_KEY, str(version), now),
        )

    def _migrate_v1(self, conn: sqlite3.Connection) -> None:
        conn.execute(
            """
            CREATE TABLE IF NOT EXISTS audit_events (
                event_id TEXT PRIMARY KEY,
                event_type TEXT NOT NULL,
                actor TEXT NOT NULL,
                summary TEXT NOT NULL,
                integration TEXT,
                module_id TEXT,
                scout_name TEXT,
                record_ids_json TEXT,
                detail_json TEXT,
                severity TEXT NOT NULL DEFAULT 'info',
                receipt_id TEXT,
                run_id TEXT,
                created_at TEXT NOT NULL
            )
            """
        )
        conn.execute(
            "CREATE INDEX IF NOT EXISTS idx_audit_events_created_at ON audit_events(created_at DESC)"
        )
        conn.execute(
            "CREATE INDEX IF NOT EXISTS idx_audit_events_type ON audit_events(event_type, created_at DESC)"
        )
        conn.execute(
            "CREATE INDEX IF NOT EXISTS idx_audit_events_integration ON audit_events(integration, created_at DESC)"
        )

    def log(
        self,
        event_type: str,
        actor: str,
        summary: str,
        *,
        integration: str | None = None,
        module_id: str | None = None,
        scout_name: str | None = None,
        record_ids: list[str] | None = None,
        detail: dict[str, Any] | None = None,
        severity: str = "info",
        receipt_id: str | None = None,
        run_id: str | None = None,
    ) -> str:
        normalized_severity = str(severity or "").strip().lower() or "info"
        if normalized_severity not in _ALLOWED_SEVERITIES:
            raise ValueError(f"Invalid severity '{severity}'. Allowed values: {sorted(_ALLOWED_SEVERITIES)}")

        event_id = str(uuid.uuid4())
        created_at = _utc_now().isoformat()
        with self._connect() as conn:
            conn.execute(
                """
                INSERT INTO audit_events (
                    event_id, event_type, actor, summary, integration, module_id, scout_name,
                    record_ids_json, detail_json, severity, receipt_id, run_id, created_at
                )
                VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                """,
                (
                    event_id,
                    event_type,
                    actor,
                    summary,
                    integration,
                    module_id,
                    scout_name,
                    json.dumps(record_ids or []),
                    json.dumps(detail or {}),
                    normalized_severity,
                    receipt_id,
                    run_id,
                    created_at,
                ),
            )
        return event_id

    def query(
        self,
        *,
        event_type: str | None = None,
        integration: str | None = None,
        since: str | None = None,
        limit: int = 100,
    ) -> list[dict[str, Any]]:
        filters: list[str] = []
        args: list[Any] = []

        if event_type:
            filters.append("event_type = ?")
            args.append(event_type)
        if integration:
            filters.append("integration = ?")
            args.append(integration)
        since_iso = _coerce_iso(since)
        if since_iso:
            filters.append("created_at >= ?")
            args.append(since_iso)

        where_clause = f"WHERE {' AND '.join(filters)}" if filters else ""
        bounded_limit = min(max(int(limit), 1), 1000)
        args.append(bounded_limit)

        with self._connect() as conn:
            rows = conn.execute(
                f"""
                SELECT
                    event_id, event_type, actor, summary, integration, module_id, scout_name,
                    record_ids_json, detail_json, severity, receipt_id, run_id, created_at
                FROM audit_events
                {where_clause}
                ORDER BY created_at DESC
                LIMIT ?
                """,
                args,
            ).fetchall()

        result: list[dict[str, Any]] = []
        for row in rows:
            try:
                record_ids = json.loads(row["record_ids_json"] or "[]")
            except Exception:
                record_ids = []
            try:
                detail = json.loads(row["detail_json"] or "{}")
            except Exception:
                detail = {}
            result.append(
                {
                    "event_id": row["event_id"],
                    "event_type": row["event_type"],
                    "actor": row["actor"],
                    "summary": row["summary"],
                    "integration": row["integration"],
                    "module_id": row["module_id"],
                    "scout_name": row["scout_name"],
                    "record_ids": record_ids if isinstance(record_ids, list) else [],
                    "detail": detail if isinstance(detail, dict) else {},
                    "severity": row["severity"],
                    "receipt_id": row["receipt_id"],
                    "run_id": row["run_id"],
                    "created_at": row["created_at"],
                }
            )
        return result

    def purge(self, older_than_days: int) -> int:
        days = max(int(older_than_days), 1)
        cutoff = (_utc_now() - timedelta(days=days)).isoformat()
        with self._connect() as conn:
            cursor = conn.execute(
                "DELETE FROM audit_events WHERE created_at < ?",
                (cutoff,),
            )
            return int(cursor.rowcount or 0)


__all__ = ["AuditLog"]
