"""ReviewHistoryService — persistent record of all review actions.

Tracks every approve/reject/unapprove/restore action across all four
review surfaces (memory, reflection, dream, relationship) in a SQLite
table.  Complements ActivityLogger (in-memory, ephemeral feedback
counters) with a durable, queryable audit trail.

Storage: ``review_history`` table in the vault-scoped ``agntspace.db``.
"""

from __future__ import annotations

import asyncio
import json
import logging
from datetime import UTC, datetime
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    import aiosqlite

log = logging.getLogger(__name__)

# Valid surfaces and actions — reject anything else at insert time.
VALID_SURFACES = frozenset({"memory", "reflection", "dream", "relationship"})
VALID_ACTIONS = frozenset({"approved", "rejected", "unapproved", "restored", "batch_approved", "batch_confirmed"})


class ReviewHistoryService:
    """SQLite-backed store for review action history.

    Follows the same patterns as ``DecisionLogger``: async lock on writes,
    idempotent migrations, non-fatal on failure, optional in-memory
    fallback when ``db=None`` (tests).
    """

    _SCHEMA = """
    CREATE TABLE IF NOT EXISTS review_history (
        id           INTEGER PRIMARY KEY AUTOINCREMENT,
        ts           TEXT    NOT NULL,
        surface      TEXT    NOT NULL,
        item_id      TEXT    NOT NULL,
        action       TEXT    NOT NULL,
        actor        TEXT    DEFAULT 'user',
        reason       TEXT    DEFAULT '',
        details      TEXT    DEFAULT '{}'
    );
    CREATE INDEX IF NOT EXISTS idx_review_ts      ON review_history(ts);
    CREATE INDEX IF NOT EXISTS idx_review_surface  ON review_history(surface);
    CREATE INDEX IF NOT EXISTS idx_review_action   ON review_history(action);
    """

    def __init__(self, db: aiosqlite.Connection | None = None) -> None:
        self._db = db
        self._lock = asyncio.Lock()
        self._initialized = False
        self._fallback: list[dict] = []

    async def initialize(self) -> None:
        """Create the table. Safe to call multiple times."""
        if self._initialized or self._db is None:
            self._initialized = True
            return
        async with self._lock:
            try:
                await self._db.executescript(self._SCHEMA)
                await self._db.commit()
                self._initialized = True
            except Exception:
                log.exception("Failed to initialize review_history table")

    async def record(
        self,
        surface: str,
        item_id: str,
        action: str,
        *,
        reason: str = "",
        details: dict | None = None,
        actor: str = "user",
    ) -> None:
        """Record a review action. Never raises."""
        ts = datetime.now(UTC).strftime("%Y-%m-%dT%H:%M:%SZ")
        row = {
            "ts": ts,
            "surface": surface,
            "item_id": str(item_id),
            "action": action,
            "actor": actor,
            "reason": reason,
            "details": details or {},
        }

        if self._db is None:
            self._fallback.append(row)
            return

        if not self._initialized:
            await self.initialize()

        try:
            async with self._lock:
                await self._db.execute(
                    "INSERT INTO review_history "
                    "(ts, surface, item_id, action, actor, reason, details) "
                    "VALUES (?, ?, ?, ?, ?, ?, ?)",
                    (
                        ts,
                        surface,
                        str(item_id),
                        action,
                        actor,
                        reason,
                        json.dumps(details or {}, default=str),
                    ),
                )
                await self._db.commit()
        except Exception:
            log.debug("ReviewHistoryService.record failed", exc_info=True)

    async def query(
        self,
        *,
        surface: str | None = None,
        action: str | None = None,
        since: str | None = None,
        limit: int = 50,
    ) -> list[dict]:
        """Return history entries, newest first."""
        if self._db is None:
            # In-memory fallback
            items = list(self._fallback)
            if surface:
                items = [i for i in items if i["surface"] == surface]
            if action:
                items = [i for i in items if i["action"] == action]
            if since:
                items = [i for i in items if i["ts"] >= since]
            items.sort(key=lambda x: x["ts"], reverse=True)
            return items[:limit]

        if not self._initialized:
            await self.initialize()

        clauses: list[str] = []
        params: list[str | int] = []
        if surface:
            clauses.append("surface = ?")
            params.append(surface)
        if action:
            clauses.append("action = ?")
            params.append(action)
        if since:
            clauses.append("ts >= ?")
            params.append(since)

        where = (" WHERE " + " AND ".join(clauses)) if clauses else ""
        sql = f"SELECT * FROM review_history{where} ORDER BY ts DESC LIMIT ?"
        params.append(limit)

        try:
            async with self._lock:
                cursor = await self._db.execute(sql, params)
                rows = await cursor.fetchall()
                cols = [d[0] for d in cursor.description]
            result = []
            for row in rows:
                entry = dict(zip(cols, row, strict=False))
                # Parse details JSON
                try:
                    entry["details"] = json.loads(entry.get("details", "{}"))
                except (json.JSONDecodeError, TypeError):
                    entry["details"] = {}
                result.append(entry)
            return result
        except Exception:
            log.debug("ReviewHistoryService.query failed", exc_info=True)
            return []

    async def stats(self) -> dict:
        """Return counts by surface and action."""
        if self._db is None:
            counts: dict[str, dict[str, int]] = {}
            for item in self._fallback:
                s = item["surface"]
                a = item["action"]
                counts.setdefault(s, {})
                counts[s][a] = counts[s].get(a, 0) + 1
            return {"by_surface": counts, "total": len(self._fallback)}

        if not self._initialized:
            await self.initialize()

        try:
            async with self._lock:
                cursor = await self._db.execute(
                    "SELECT surface, action, COUNT(*) as cnt "
                    "FROM review_history GROUP BY surface, action"
                )
                rows = await cursor.fetchall()

                total_cursor = await self._db.execute(
                    "SELECT COUNT(*) FROM review_history"
                )
                total_row = await total_cursor.fetchone()

            counts = {}
            for surface, action, cnt in rows:
                counts.setdefault(surface, {})[action] = cnt

            return {"by_surface": counts, "total": total_row[0] if total_row else 0}
        except Exception:
            log.debug("ReviewHistoryService.stats failed", exc_info=True)
            return {"by_surface": {}, "total": 0}
