"""Persistent error logger backed by SQLite.

Stores errors in a queryable table with structured metadata.
Designed for post-mortem debugging and the error log UI page.
"""

from __future__ import annotations

import json
import logging
import traceback
from datetime import UTC, datetime
from typing import Any

import aiosqlite

log = logging.getLogger(__name__)


class ErrorLogger:
    """SQLite-backed error log for persistent error history."""

    def __init__(self, db: aiosqlite.Connection) -> None:
        self._db = db

    async def init_schema(self) -> None:
        """Create error_logs table if it doesn't exist."""
        await self._db.executescript("""
            CREATE TABLE IF NOT EXISTS error_logs (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                timestamp TEXT NOT NULL,
                level TEXT NOT NULL DEFAULT 'ERROR',
                source TEXT NOT NULL DEFAULT '',
                message TEXT NOT NULL,
                error_type TEXT DEFAULT '',
                traceback TEXT DEFAULT '',
                context TEXT DEFAULT '{}',
                request_method TEXT DEFAULT '',
                request_path TEXT DEFAULT '',
                status_code INTEGER DEFAULT 0,
                resolved INTEGER NOT NULL DEFAULT 0,
                created_at TEXT NOT NULL
            );

            CREATE INDEX IF NOT EXISTS idx_error_logs_ts
                ON error_logs(timestamp DESC);
            CREATE INDEX IF NOT EXISTS idx_error_logs_source
                ON error_logs(source);
            CREATE INDEX IF NOT EXISTS idx_error_logs_level
                ON error_logs(level);
        """)
        await self._db.commit()

    async def log_error(
        self,
        message: str,
        *,
        source: str = "",
        error_type: str = "",
        tb: str = "",
        context: dict[str, Any] | None = None,
        request_method: str = "",
        request_path: str = "",
        status_code: int = 0,
        level: str = "ERROR",
    ) -> int:
        """Write an error to the persistent log. Returns the row ID."""
        now = datetime.now(UTC).isoformat()
        ctx_json = json.dumps(context or {}, default=str)
        try:
            cursor = await self._db.execute(
                """INSERT INTO error_logs
                   (timestamp, level, source, message, error_type, traceback,
                    context, request_method, request_path, status_code, created_at)
                   VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
                (now, level, source, message, error_type, tb,
                 ctx_json, request_method, request_path, status_code, now),
            )
            await self._db.commit()
            return cursor.lastrowid or 0
        except Exception:
            log.debug("Failed to write error log entry", exc_info=True)
            return 0

    async def log_exception(
        self,
        exc: Exception,
        *,
        source: str = "",
        context: dict[str, Any] | None = None,
        request_method: str = "",
        request_path: str = "",
        status_code: int = 500,
    ) -> int:
        """Convenience: log an exception with its traceback."""
        tb = "".join(traceback.format_exception(type(exc), exc, exc.__traceback__))
        return await self.log_error(
            message=str(exc),
            source=source,
            error_type=type(exc).__name__,
            tb=tb,
            context=context,
            request_method=request_method,
            request_path=request_path,
            status_code=status_code,
        )

    async def get_errors(
        self,
        *,
        limit: int = 100,
        offset: int = 0,
        level: str = "",
        source: str = "",
        resolved: bool | None = None,
        since: str = "",
    ) -> list[dict]:
        """Query error logs with filters."""
        clauses: list[str] = []
        params: list[Any] = []

        if level:
            clauses.append("level = ?")
            params.append(level.upper())
        if source:
            clauses.append("source LIKE ?")
            params.append(f"%{source}%")
        if resolved is not None:
            clauses.append("resolved = ?")
            params.append(1 if resolved else 0)
        if since:
            clauses.append("timestamp >= ?")
            params.append(since)

        where = (" WHERE " + " AND ".join(clauses)) if clauses else ""
        query = f"SELECT * FROM error_logs{where} ORDER BY timestamp DESC LIMIT ? OFFSET ?"
        params.extend([limit, offset])

        rows = await self._db.execute_fetchall(query, params)
        return [dict(row) for row in rows]

    async def get_error(self, error_id: int) -> dict | None:
        """Get a single error by ID."""
        cursor = await self._db.execute(
            "SELECT * FROM error_logs WHERE id = ?", (error_id,),
        )
        row = await cursor.fetchone()
        return dict(row) if row else None

    async def resolve_error(self, error_id: int) -> bool:
        """Mark an error as resolved."""
        cursor = await self._db.execute(
            "UPDATE error_logs SET resolved = 1 WHERE id = ?", (error_id,),
        )
        await self._db.commit()
        return (cursor.rowcount or 0) > 0

    async def resolve_all(self) -> int:
        """Mark all unresolved errors as resolved."""
        cursor = await self._db.execute(
            "UPDATE error_logs SET resolved = 1 WHERE resolved = 0",
        )
        await self._db.commit()
        return cursor.rowcount or 0

    async def delete_old(self, older_than_days: int = 30) -> int:
        """Remove resolved errors older than N days."""
        cursor = await self._db.execute(
            """DELETE FROM error_logs
               WHERE resolved = 1
               AND timestamp < datetime('now', ? || ' days')""",
            (f"-{older_than_days}",),
        )
        await self._db.commit()
        return cursor.rowcount or 0

    async def get_stats(self) -> dict:
        """Summary statistics for the error log."""
        rows = await self._db.execute_fetchall(
            """SELECT
                 COUNT(*) as total,
                 SUM(CASE WHEN resolved = 0 THEN 1 ELSE 0 END) as unresolved,
                 SUM(CASE WHEN level = 'ERROR' THEN 1 ELSE 0 END) as errors,
                 SUM(CASE WHEN level = 'WARNING' THEN 1 ELSE 0 END) as warnings,
                 SUM(CASE WHEN level = 'CRITICAL' THEN 1 ELSE 0 END) as critical
               FROM error_logs""",
        )
        row = rows[0] if rows else None
        if not row:
            return {"total": 0, "unresolved": 0, "errors": 0, "warnings": 0, "critical": 0}

        # Top error sources
        source_rows = await self._db.execute_fetchall(
            """SELECT source, COUNT(*) as count
               FROM error_logs WHERE resolved = 0
               GROUP BY source ORDER BY count DESC LIMIT 10""",
        )
        top_sources = [{"source": r["source"], "count": r["count"]} for r in source_rows]

        # Recent rate (last hour)
        rate_rows = await self._db.execute_fetchall(
            "SELECT COUNT(*) as count FROM error_logs WHERE timestamp >= datetime('now', '-1 hour')",
        )
        hourly_rate = rate_rows[0]["count"] if rate_rows else 0

        return {
            "total": row["total"],
            "unresolved": row["unresolved"],
            "errors": row["errors"],
            "warnings": row["warnings"],
            "critical": row["critical"],
            "top_sources": top_sources,
            "hourly_rate": hourly_rate,
        }
