"""Audit logging — SQLite-backed security event log."""
from __future__ import annotations

import logging
import sqlite3
import threading
import time
from datetime import datetime, timedelta, timezone
from pathlib import Path
from typing import Any, Dict, List, Optional

logger = logging.getLogger(__name__)


class AuditAction:
    """Constants for audit action types."""
    AUTH_SUCCESS = "auth_success"
    AUTH_FAILURE = "auth_failure"
    TOKEN_CREATED = "token_created"
    TOKEN_REVOKED = "token_revoked"
    NODE_REGISTERED = "node_registered"
    NODE_REMOVED = "node_removed"
    NODE_ARCHIVED = "node_archived"
    JOB_SUBMITTED = "job_submitted"
    JOB_COMPLETED = "job_completed"
    JOB_FAILED = "job_failed"
    CONFIG_CHANGED = "config_changed"
    COMMAND_BLOCKED = "command_blocked"
    RATE_LIMITED = "rate_limited"
    SSRF_BLOCKED = "ssrf_blocked"
    UPLOAD_REJECTED = "upload_rejected"


class AuditLog:
    """SQLite-backed audit log for security events.

    Thread-safe. Events are logged with timestamp, action, actor, result, and details.
    Supports retention policies to prevent unbounded growth.
    """

    def __init__(
        self,
        db_path: str = "data/mesh_audit.db",
        retention_days: int = 90,
    ):
        self.db_path = Path(db_path)
        self.db_path.parent.mkdir(parents=True, exist_ok=True)
        self.retention_days = retention_days
        self._write_lock = threading.Lock()
        self._conn = sqlite3.connect(str(self.db_path), check_same_thread=False)
        self._conn.row_factory = sqlite3.Row
        self._conn.execute("PRAGMA journal_mode=WAL")
        self._conn.execute("PRAGMA busy_timeout=5000")
        self._init_db()

    def _init_db(self):
        with self._write_lock:
            self._conn.executescript("""
                CREATE TABLE IF NOT EXISTS audit_log (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    timestamp TEXT NOT NULL,
                    action TEXT NOT NULL,
                    actor TEXT NOT NULL DEFAULT '',
                    actor_ip TEXT NOT NULL DEFAULT '',
                    result TEXT NOT NULL DEFAULT 'success',
                    details TEXT DEFAULT '',
                    resource TEXT DEFAULT ''
                );

                CREATE INDEX IF NOT EXISTS idx_audit_timestamp
                    ON audit_log(timestamp);
                CREATE INDEX IF NOT EXISTS idx_audit_action
                    ON audit_log(action);
                CREATE INDEX IF NOT EXISTS idx_audit_actor
                    ON audit_log(actor);
            """)
            self._conn.commit()

    def log(
        self,
        action: str,
        actor: str = "",
        actor_ip: str = "",
        result: str = "success",
        details: str = "",
        resource: str = "",
    ) -> None:
        """Log an audit event.

        Args:
            action: Event type (use AuditAction constants).
            actor: Who performed the action (node_id, username, etc.).
            actor_ip: IP address of the actor.
            result: "success" or "failure".
            details: Additional context (free-form text).
            resource: The resource acted upon (node_id, job_id, etc.).
        """
        timestamp = datetime.now(timezone.utc).isoformat()
        try:
            with self._write_lock:
                self._conn.execute(
                    """INSERT INTO audit_log
                       (timestamp, action, actor, actor_ip, result, details, resource)
                       VALUES (?, ?, ?, ?, ?, ?, ?)""",
                    (timestamp, action, actor, actor_ip, result, details, resource),
                )
                self._conn.commit()
        except Exception as e:
            logger.error("Failed to write audit log: %s", e)

    def query(
        self,
        action: Optional[str] = None,
        actor: Optional[str] = None,
        since: Optional[str] = None,
        limit: int = 100,
    ) -> List[Dict[str, Any]]:
        """Query audit log entries.

        Args:
            action: Filter by action type.
            actor: Filter by actor.
            since: ISO timestamp — return only events after this time.
            limit: Maximum number of results.

        Returns:
            List of audit event dicts, newest first.
        """
        conditions = []
        params = []

        if action:
            conditions.append("action = ?")
            params.append(action)
        if actor:
            conditions.append("actor = ?")
            params.append(actor)
        if since:
            conditions.append("timestamp > ?")
            params.append(since)

        where = ""
        if conditions:
            where = "WHERE " + " AND ".join(conditions)

        params.append(limit)
        rows = self._conn.execute(
            f"SELECT * FROM audit_log {where} ORDER BY timestamp DESC LIMIT ?",
            params,
        ).fetchall()
        return [dict(row) for row in rows]

    def prune(self, max_age_days: Optional[int] = None) -> int:
        """Remove audit entries older than retention period.

        Args:
            max_age_days: Override retention_days for this call.

        Returns:
            Number of entries deleted.
        """
        days = max_age_days or self.retention_days
        cutoff = (datetime.now(timezone.utc) - timedelta(days=days)).isoformat()
        try:
            with self._write_lock:
                cursor = self._conn.execute(
                    "DELETE FROM audit_log WHERE timestamp < ?", (cutoff,)
                )
                self._conn.commit()
                deleted = cursor.rowcount
                if deleted > 0:
                    logger.info("Pruned %d audit entries older than %d days", deleted, days)
                return deleted
        except Exception as e:
            logger.error("Failed to prune audit log: %s", e)
            return 0

    def count(self, action: Optional[str] = None, since: Optional[str] = None) -> int:
        """Count audit entries matching criteria."""
        conditions = []
        params = []
        if action:
            conditions.append("action = ?")
            params.append(action)
        if since:
            conditions.append("timestamp > ?")
            params.append(since)
        where = ""
        if conditions:
            where = "WHERE " + " AND ".join(conditions)
        row = self._conn.execute(
            f"SELECT COUNT(*) FROM audit_log {where}", params
        ).fetchone()
        return row[0] if row else 0

    def close(self):
        """Close the audit log database."""
        try:
            self._conn.execute("PRAGMA wal_checkpoint(TRUNCATE)")
            self._conn.close()
        except Exception as e:
            logger.warning("AuditLog close error: %s", e)


# Module-level singleton — lazy-initialized
_audit_log: Optional[AuditLog] = None


def get_audit_log(db_path: str = "data/mesh_audit.db") -> AuditLog:
    """Get or create the global audit log instance."""
    global _audit_log
    if _audit_log is None:
        _audit_log = AuditLog(db_path=db_path)
    return _audit_log


def audit(action: str, **kwargs) -> None:
    """Convenience function to log an audit event to the global audit log."""
    log = get_audit_log()
    log.log(action=action, **kwargs)
