# Familiar - Self-Hosted AI Companion Platform
# Copyright (c) 2026 George Scott Foley
# ORCID: 0009-0006-4957-0540
# Email: Georgescottfoley@proton.me
# Licensed under the MIT License - see LICENSE file for details

"""
Audit Log Module - Phase 4

Comprehensive audit logging for compliance (SOC2, HIPAA, board requirements).

Features:
- Event capture for all sensitive actions
- Tamper-evident log chain (hash linking)
- Multiple export formats (CSV, JSON, SIEM)
- Retention policies
- Search and filtering
- Scheduled exports

Events tracked:
- Authentication (login, logout, failed attempts)
- User management (create, update, delete, role changes)
- Data access (view, export, delete)
- Configuration changes
- Agent interactions
- System events

Configuration:
    audit:
      enabled: true
      retention_days: 365
      hash_chain: true
      export_schedule: weekly
      notify_email: compliance@org.org
"""

from __future__ import annotations

import csv
import hashlib
import io
import json
import logging
import sqlite3
from contextlib import contextmanager
from dataclasses import asdict, dataclass, field
from datetime import datetime, timedelta, timezone
from enum import Enum
from pathlib import Path
from typing import Any, Dict, List, Optional

logger = logging.getLogger(__name__)


class AuditAction(str, Enum):
    """Audit event actions."""

    # Authentication
    LOGIN = "login"
    LOGOUT = "logout"
    LOGIN_FAILED = "login_failed"
    SESSION_EXPIRED = "session_expired"
    TOKEN_REFRESH = "token_refresh"
    SSO_LOGIN = "sso_login"

    # User management
    USER_CREATE = "user_create"
    USER_UPDATE = "user_update"
    USER_DELETE = "user_delete"
    USER_DEACTIVATE = "user_deactivate"
    USER_REACTIVATE = "user_reactivate"
    ROLE_CHANGE = "role_change"
    PASSWORD_CHANGE = "password_change"

    # Data access
    DATA_VIEW = "data_view"
    DATA_EXPORT = "data_export"
    DATA_DELETE = "data_delete"
    HISTORY_ACCESS = "history_access"

    # Agent/Chat
    MESSAGE_SENT = "message_sent"
    TOOL_EXECUTED = "tool_executed"
    SKILL_INVOKED = "skill_invoked"

    # Configuration
    CONFIG_CHANGE = "config_change"
    SETTING_UPDATE = "setting_update"
    CHANNEL_LINK = "channel_link"
    CHANNEL_UNLINK = "channel_unlink"

    # System
    SYSTEM_START = "system_start"
    SYSTEM_STOP = "system_stop"
    BACKUP_CREATE = "backup_create"
    BACKUP_RESTORE = "backup_restore"
    AUDIT_EXPORT = "audit_export"

    # Security
    PERMISSION_DENIED = "permission_denied"
    RATE_LIMIT_HIT = "rate_limit_hit"
    SUSPICIOUS_ACTIVITY = "suspicious_activity"


class AuditSeverity(str, Enum):
    """Audit event severity levels."""

    DEBUG = "debug"
    INFO = "info"
    WARNING = "warning"
    ERROR = "error"
    CRITICAL = "critical"


@dataclass
class AuditEvent:
    """
    Single audit event.

    The hash_chain field links events together for tamper detection.
    """

    id: str = None
    timestamp: datetime = None

    # Who
    actor_id: str = None  # User ID or "system"
    actor_email: str = None  # User email if available
    actor_role: str = None  # User role

    # What
    action: AuditAction = None
    severity: AuditSeverity = AuditSeverity.INFO

    # Where
    resource_type: str = None  # user, message, config, etc.
    resource_id: str = None  # ID of affected resource
    channel: str = None  # Where action originated

    # Context
    ip_address: str = None
    user_agent: str = None
    session_id: str = None

    # Details
    details: Dict[str, Any] = field(default_factory=dict)

    # Integrity
    hash_chain: str = None  # Hash of previous event
    event_hash: str = None  # Hash of this event

    def __post_init__(self):
        if self.timestamp is None:
            self.timestamp = datetime.now(timezone.utc)
        if self.id is None:
            import secrets

            self.id = secrets.token_urlsafe(16)

    def to_dict(self) -> Dict:
        d = asdict(self)
        d["timestamp"] = self.timestamp.isoformat()
        d["action"] = self.action.value if self.action else None
        d["severity"] = self.severity.value if self.severity else None
        return d

    @classmethod
    def from_dict(cls, d: Dict) -> "AuditEvent":
        d = d.copy()
        if d.get("timestamp"):
            d["timestamp"] = datetime.fromisoformat(d["timestamp"])
        if d.get("action"):
            d["action"] = AuditAction(d["action"])
        if d.get("severity"):
            d["severity"] = AuditSeverity(d["severity"])
        return cls(**d)

    def compute_hash(self) -> str:
        """Compute hash of event for integrity verification."""
        content = json.dumps(
            {
                "id": self.id,
                "timestamp": self.timestamp.isoformat(),
                "actor_id": self.actor_id,
                "action": self.action.value if self.action else None,
                "resource_id": self.resource_id,
                "details": self.details,
                "hash_chain": self.hash_chain,
            },
            sort_keys=True,
        )
        return hashlib.sha256(content.encode()).hexdigest()

    def to_siem_format(self) -> Dict:
        """
        Convert to SIEM format (CEF-like for Splunk/ELK).

        Common Event Format (CEF):
        CEF:Version|Device Vendor|Device Product|Device Version|Signature ID|Name|Severity|Extension
        """
        severity_map = {
            AuditSeverity.DEBUG: 0,
            AuditSeverity.INFO: 3,
            AuditSeverity.WARNING: 6,
            AuditSeverity.ERROR: 8,
            AuditSeverity.CRITICAL: 10,
        }

        return {
            "cef_version": "0",
            "device_vendor": "Familiar",
            "device_product": "AI Assistant",
            "device_version": "2.0",
            "signature_id": self.action.value if self.action else "unknown",
            "name": self.action.value if self.action else "unknown",
            "severity": severity_map.get(self.severity, 3),
            "extension": {
                "rt": int(self.timestamp.timestamp() * 1000),
                "src": self.ip_address or "",
                "suser": self.actor_email or self.actor_id or "",
                "cs1": self.resource_type or "",
                "cs2": self.resource_id or "",
                "cs3": self.channel or "",
                "msg": json.dumps(self.details),
            },
        }


@dataclass
class AuditConfig:
    """Audit system configuration."""

    enabled: bool = True

    # Storage
    db_path: Path = None

    # Retention
    retention_days: int = 365
    archive_after_days: int = 90

    # Integrity
    hash_chain: bool = True

    # Export
    export_schedule: str = "weekly"  # daily, weekly, monthly, none
    export_format: str = "csv"  # csv, json, siem
    notify_email: str = None

    def __post_init__(self):
        if self.db_path is None:
            from ..core.paths import DATA_DIR

            self.db_path = Path(DATA_DIR) / "audit.db"


class AuditStore:
    """
    SQLite-based audit log storage.

    Usage:
        store = AuditStore()

        # Log an event
        store.log(AuditEvent(
            actor_id="user123",
            action=AuditAction.LOGIN,
            ip_address="192.168.1.1"
        ))

        # Query events
        events = store.query(
            start=datetime.now(timezone.utc) - timedelta(days=7),
            action=AuditAction.LOGIN
        )

        # Export
        csv_data = store.export_csv(start, end)
    """

    def __init__(self, config: AuditConfig = None, db_path: Path = None, encrypted_storage=None):
        self.config = config or AuditConfig()
        self._encrypted_storage = encrypted_storage

        if db_path:
            self.db_path = Path(db_path)
        else:
            self.db_path = self.config.db_path

        self.db_path.parent.mkdir(parents=True, exist_ok=True)
        self._init_db()

        # Cache last hash for chain
        self._last_hash: str = None

    def _init_db(self):
        """Initialize database schema."""
        with self._get_conn() as conn:
            conn.executescript("""
                CREATE TABLE IF NOT EXISTS audit_events (
                    id TEXT PRIMARY KEY,
                    timestamp TEXT NOT NULL,
                    actor_id TEXT,
                    actor_email TEXT,
                    actor_role TEXT,
                    action TEXT NOT NULL,
                    severity TEXT DEFAULT 'info',
                    resource_type TEXT,
                    resource_id TEXT,
                    channel TEXT,
                    ip_address TEXT,
                    user_agent TEXT,
                    session_id TEXT,
                    details TEXT,
                    hash_chain TEXT,
                    event_hash TEXT,
                    created_at TEXT DEFAULT CURRENT_TIMESTAMP
                );

                CREATE INDEX IF NOT EXISTS idx_audit_timestamp
                    ON audit_events(timestamp);
                CREATE INDEX IF NOT EXISTS idx_audit_actor
                    ON audit_events(actor_id);
                CREATE INDEX IF NOT EXISTS idx_audit_action
                    ON audit_events(action);
                CREATE INDEX IF NOT EXISTS idx_audit_resource
                    ON audit_events(resource_type, resource_id);

                -- Archived events table
                CREATE TABLE IF NOT EXISTS audit_events_archive (
                    id TEXT PRIMARY KEY,
                    timestamp TEXT NOT NULL,
                    actor_id TEXT,
                    actor_email TEXT,
                    actor_role TEXT,
                    action TEXT NOT NULL,
                    severity TEXT DEFAULT 'info',
                    resource_type TEXT,
                    resource_id TEXT,
                    channel TEXT,
                    ip_address TEXT,
                    user_agent TEXT,
                    session_id TEXT,
                    details TEXT,
                    hash_chain TEXT,
                    event_hash TEXT,
                    archived_at TEXT DEFAULT CURRENT_TIMESTAMP
                );

                -- Export history
                CREATE TABLE IF NOT EXISTS audit_exports (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    export_time TEXT NOT NULL,
                    start_time TEXT NOT NULL,
                    end_time TEXT NOT NULL,
                    format TEXT NOT NULL,
                    event_count INTEGER,
                    file_hash TEXT,
                    exported_by TEXT
                );
            """)

    @contextmanager
    def _get_conn(self):
        """Get database connection."""
        conn = sqlite3.connect(str(self.db_path))
        conn.row_factory = sqlite3.Row
        try:
            yield conn
            conn.commit()
        finally:
            conn.close()

    def log(self, event: AuditEvent) -> AuditEvent:
        """
        Log an audit event.

        Args:
            event: The audit event to log

        Returns:
            The event with hash computed
        """
        if not self.config.enabled:
            return event

        # Add hash chain
        if self.config.hash_chain:
            if self._last_hash is None:
                self._last_hash = self._get_last_hash()
            event.hash_chain = self._last_hash
            event.event_hash = event.compute_hash()
            self._last_hash = event.event_hash

        # Serialize details, optionally encrypting
        details_str = json.dumps(event.details) if event.details else None
        if details_str and self._encrypted_storage:
            try:
                details_str = self._encrypted_storage.encrypt(details_str)
            except Exception as e:
                logger.warning(f"Failed to encrypt audit details, storing plaintext: {e}")

        with self._get_conn() as conn:
            conn.execute(
                """
                INSERT INTO audit_events
                (id, timestamp, actor_id, actor_email, actor_role, action,
                 severity, resource_type, resource_id, channel, ip_address,
                 user_agent, session_id, details, hash_chain, event_hash)
                VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
            """,
                (
                    event.id,
                    event.timestamp.isoformat(),
                    event.actor_id,
                    event.actor_email,
                    event.actor_role,
                    event.action.value if event.action else None,
                    event.severity.value if event.severity else "info",
                    event.resource_type,
                    event.resource_id,
                    event.channel,
                    event.ip_address,
                    event.user_agent,
                    event.session_id,
                    details_str,
                    event.hash_chain,
                    event.event_hash,
                ),
            )

        logger.debug(
            f"Audit: {event.action.value if event.action else 'unknown'} by {event.actor_id}"
        )
        return event

    def _get_last_hash(self) -> Optional[str]:
        """Get hash of last event for chain."""
        with self._get_conn() as conn:
            row = conn.execute("""
                SELECT event_hash FROM audit_events
                ORDER BY timestamp DESC, id DESC LIMIT 1
            """).fetchone()
            return row["event_hash"] if row else "genesis"

    def query(
        self,
        start: datetime = None,
        end: datetime = None,
        actor_id: str = None,
        action: AuditAction = None,
        resource_type: str = None,
        resource_id: str = None,
        severity: AuditSeverity = None,
        channel: str = None,
        ip_address: str = None,
        limit: int = 1000,
        offset: int = 0,
        include_archived: bool = False,
    ) -> List[AuditEvent]:
        """
        Query audit events with filters.

        Args:
            start: Start of time range
            end: End of time range
            actor_id: Filter by actor
            action: Filter by action type
            resource_type: Filter by resource type
            resource_id: Filter by specific resource
            severity: Minimum severity
            channel: Filter by channel
            ip_address: Filter by IP
            limit: Max results
            offset: Pagination offset
            include_archived: Include archived events

        Returns:
            List of matching events
        """
        conditions = []
        params = []

        if start:
            conditions.append("timestamp >= ?")
            params.append(start.isoformat())

        if end:
            conditions.append("timestamp <= ?")
            params.append(end.isoformat())

        if actor_id:
            conditions.append("actor_id = ?")
            params.append(actor_id)

        if action:
            conditions.append("action = ?")
            params.append(action.value)

        if resource_type:
            conditions.append("resource_type = ?")
            params.append(resource_type)

        if resource_id:
            conditions.append("resource_id = ?")
            params.append(resource_id)

        if severity:
            severity_order = ["debug", "info", "warning", "error", "critical"]
            min_idx = severity_order.index(severity.value)
            allowed = severity_order[min_idx:]
            placeholders = ",".join("?" * len(allowed))
            conditions.append(f"severity IN ({placeholders})")
            params.extend(allowed)

        if channel:
            conditions.append("channel = ?")
            params.append(channel)

        if ip_address:
            conditions.append("ip_address = ?")
            params.append(ip_address)

        where_clause = " AND ".join(conditions) if conditions else "1=1"

        # Build query
        if include_archived:
            query = f"""
                SELECT * FROM (
                    SELECT * FROM audit_events WHERE {where_clause}
                    UNION ALL
                    SELECT id, timestamp, actor_id, actor_email, actor_role, action,
                           severity, resource_type, resource_id, channel, ip_address,
                           user_agent, session_id, details, hash_chain, event_hash,
                           archived_at as created_at
                    FROM audit_events_archive WHERE {where_clause}
                ) ORDER BY timestamp DESC LIMIT ? OFFSET ?
            """
            params = params + params  # Double params for UNION
        else:
            query = f"""
                SELECT * FROM audit_events
                WHERE {where_clause}
                ORDER BY timestamp DESC
                LIMIT ? OFFSET ?
            """

        params.extend([limit, offset])

        events = []
        with self._get_conn() as conn:
            for row in conn.execute(query, params):
                events.append(self._row_to_event(row))

        return events

    def _row_to_event(self, row: sqlite3.Row) -> AuditEvent:
        """Convert database row to AuditEvent, decrypting details if needed."""
        details_raw = row["details"]
        details = {}
        if details_raw:
            # Try JSON parse first (plaintext or legacy)
            try:
                details = json.loads(details_raw)
            except (json.JSONDecodeError, ValueError):
                # Attempt decryption
                if self._encrypted_storage:
                    try:
                        decrypted = self._encrypted_storage.decrypt(details_raw)
                        details = json.loads(decrypted)
                    except Exception:
                        details = {"_encrypted": True, "_raw": details_raw[:50]}
                else:
                    details = {"_encrypted": True, "_raw": details_raw[:50]}

        return AuditEvent(
            id=row["id"],
            timestamp=datetime.fromisoformat(row["timestamp"]),
            actor_id=row["actor_id"],
            actor_email=row["actor_email"],
            actor_role=row["actor_role"],
            action=AuditAction(row["action"]) if row["action"] else None,
            severity=AuditSeverity(row["severity"]) if row["severity"] else AuditSeverity.INFO,
            resource_type=row["resource_type"],
            resource_id=row["resource_id"],
            channel=row["channel"],
            ip_address=row["ip_address"],
            user_agent=row["user_agent"],
            session_id=row["session_id"],
            details=details,
            hash_chain=row["hash_chain"],
            event_hash=row["event_hash"],
        )

    def get_stats(
        self,
        start: datetime = None,
        end: datetime = None,
    ) -> Dict[str, Any]:
        """
        Get audit log statistics.

        Returns:
            Dict with counts by action, severity, actor, etc.
        """
        if start is None:
            start = datetime.now(timezone.utc) - timedelta(days=30)
        if end is None:
            end = datetime.now(timezone.utc)

        stats = {
            "total_events": 0,
            "by_action": {},
            "by_severity": {},
            "by_actor": {},
            "by_channel": {},
            "security_events": 0,
        }

        with self._get_conn() as conn:
            # Total
            row = conn.execute(
                """
                SELECT COUNT(*) as cnt FROM audit_events
                WHERE timestamp BETWEEN ? AND ?
            """,
                (start.isoformat(), end.isoformat()),
            ).fetchone()
            stats["total_events"] = row["cnt"]

            # By action
            for row in conn.execute(
                """
                SELECT action, COUNT(*) as cnt FROM audit_events
                WHERE timestamp BETWEEN ? AND ?
                GROUP BY action ORDER BY cnt DESC LIMIT 20
            """,
                (start.isoformat(), end.isoformat()),
            ):
                stats["by_action"][row["action"]] = row["cnt"]

            # By severity
            for row in conn.execute(
                """
                SELECT severity, COUNT(*) as cnt FROM audit_events
                WHERE timestamp BETWEEN ? AND ?
                GROUP BY severity
            """,
                (start.isoformat(), end.isoformat()),
            ):
                stats["by_severity"][row["severity"]] = row["cnt"]

            # By actor (top 10)
            for row in conn.execute(
                """
                SELECT actor_email, COUNT(*) as cnt FROM audit_events
                WHERE timestamp BETWEEN ? AND ? AND actor_email IS NOT NULL
                GROUP BY actor_email ORDER BY cnt DESC LIMIT 10
            """,
                (start.isoformat(), end.isoformat()),
            ):
                stats["by_actor"][row["actor_email"]] = row["cnt"]

            # Security events
            security_actions = [
                AuditAction.LOGIN_FAILED.value,
                AuditAction.PERMISSION_DENIED.value,
                AuditAction.RATE_LIMIT_HIT.value,
                AuditAction.SUSPICIOUS_ACTIVITY.value,
            ]
            placeholders = ",".join("?" * len(security_actions))
            row = conn.execute(
                f"""
                SELECT COUNT(*) as cnt FROM audit_events
                WHERE timestamp BETWEEN ? AND ?
                AND action IN ({placeholders})
            """,
                [start.isoformat(), end.isoformat()] + security_actions,
            ).fetchone()
            stats["security_events"] = row["cnt"]

        return stats

    def verify_chain(self, start: datetime = None, end: datetime = None) -> Dict:
        """
        Verify hash chain integrity.

        Returns:
            Dict with verification result and any broken links
        """
        events = self.query(start=start, end=end, limit=100000)
        events.sort(key=lambda e: (e.timestamp, e.id))

        result = {
            "verified": True,
            "events_checked": len(events),
            "broken_links": [],
            "missing_hashes": [],
        }

        prev_hash = "genesis"

        for event in events:
            if not event.event_hash:
                result["missing_hashes"].append(event.id)
                continue

            # Check chain link
            if event.hash_chain != prev_hash:
                result["broken_links"].append(
                    {
                        "event_id": event.id,
                        "expected": prev_hash,
                        "found": event.hash_chain,
                    }
                )
                result["verified"] = False

            # Verify event hash
            computed = event.compute_hash()
            if computed != event.event_hash:
                result["broken_links"].append(
                    {
                        "event_id": event.id,
                        "error": "event_hash_mismatch",
                        "expected": computed,
                        "found": event.event_hash,
                    }
                )
                result["verified"] = False

            prev_hash = event.event_hash

        return result

    # ============================================================
    # EXPORT FUNCTIONS
    # ============================================================

    def export_csv(self, start: datetime = None, end: datetime = None, **filters) -> str:
        """Export events to CSV format."""
        events = self.query(start=start, end=end, limit=100000, **filters)

        output = io.StringIO()
        writer = csv.writer(output)

        # Header
        writer.writerow(
            [
                "timestamp",
                "actor_id",
                "actor_email",
                "action",
                "severity",
                "resource_type",
                "resource_id",
                "channel",
                "ip_address",
                "details",
                "event_hash",
            ]
        )

        # Data
        for event in events:
            writer.writerow(
                [
                    event.timestamp.isoformat(),
                    event.actor_id or "",
                    event.actor_email or "",
                    event.action.value if event.action else "",
                    event.severity.value if event.severity else "",
                    event.resource_type or "",
                    event.resource_id or "",
                    event.channel or "",
                    event.ip_address or "",
                    json.dumps(event.details) if event.details else "",
                    event.event_hash or "",
                ]
            )

        # Record export
        self._record_export(start, end, "csv", len(events))

        return output.getvalue()

    def export_json(self, start: datetime = None, end: datetime = None, **filters) -> str:
        """Export events to JSON format."""
        events = self.query(start=start, end=end, limit=100000, **filters)

        data = {
            "export_time": datetime.now(timezone.utc).isoformat(),
            "start": start.isoformat() if start else None,
            "end": end.isoformat() if end else None,
            "event_count": len(events),
            "events": [e.to_dict() for e in events],
        }

        self._record_export(start, end, "json", len(events))

        return json.dumps(data, indent=2)

    def export_siem(self, start: datetime = None, end: datetime = None, **filters) -> str:
        """Export events to SIEM format (newline-delimited JSON)."""
        events = self.query(start=start, end=end, limit=100000, **filters)

        lines = []
        for event in events:
            lines.append(json.dumps(event.to_siem_format()))

        self._record_export(start, end, "siem", len(events))

        return "\n".join(lines)

    def _record_export(
        self, start: datetime, end: datetime, format: str, count: int, exported_by: str = None
    ):
        """Record export in history."""
        # Provide defaults if not specified
        if start is None:
            start = datetime.now(timezone.utc) - timedelta(days=30)
        if end is None:
            end = datetime.now(timezone.utc)

        with self._get_conn() as conn:
            conn.execute(
                """
                INSERT INTO audit_exports
                (export_time, start_time, end_time, format, event_count, exported_by)
                VALUES (?, ?, ?, ?, ?, ?)
            """,
                (
                    datetime.now(timezone.utc).isoformat(),
                    start.isoformat() if start else None,
                    end.isoformat() if end else None,
                    format,
                    count,
                    exported_by,
                ),
            )

    def get_export_history(self, limit: int = 20) -> List[Dict]:
        """Get history of exports."""
        with self._get_conn() as conn:
            rows = conn.execute(
                """
                SELECT * FROM audit_exports
                ORDER BY export_time DESC LIMIT ?
            """,
                (limit,),
            ).fetchall()

            return [dict(row) for row in rows]

    # ============================================================
    # RETENTION & ARCHIVING
    # ============================================================

    def archive_old_events(self) -> int:
        """
        Move old events to archive table.

        Returns:
            Number of events archived
        """
        cutoff = datetime.now(timezone.utc) - timedelta(days=self.config.archive_after_days)

        with self._get_conn() as conn:
            # Move to archive
            conn.execute(
                """
                INSERT INTO audit_events_archive
                SELECT * FROM audit_events
                WHERE timestamp < ?
            """,
                (cutoff.isoformat(),),
            )

            archived = conn.execute("""
                SELECT changes() as cnt
            """).fetchone()["cnt"]

            # Delete from main table
            conn.execute(
                """
                DELETE FROM audit_events
                WHERE timestamp < ?
            """,
                (cutoff.isoformat(),),
            )

            logger.info(f"Archived {archived} audit events older than {cutoff}")
            return archived

    def purge_expired(self) -> int:
        """
        Delete events past retention period.

        Returns:
            Number of events deleted
        """
        cutoff = datetime.now(timezone.utc) - timedelta(days=self.config.retention_days)

        with self._get_conn() as conn:
            # Delete from archive
            conn.execute(
                """
                DELETE FROM audit_events_archive
                WHERE timestamp < ?
            """,
                (cutoff.isoformat(),),
            )

            deleted = conn.execute("""
                SELECT changes() as cnt
            """).fetchone()["cnt"]

            logger.info(f"Purged {deleted} expired audit events older than {cutoff}")
            return deleted


# ============================================================
# CONVENIENCE FUNCTIONS
# ============================================================

_audit_store: Optional[AuditStore] = None


def get_audit_store(encrypted_storage=None) -> AuditStore:
    """Get or create global audit store."""
    global _audit_store
    if _audit_store is None:
        _audit_store = AuditStore(encrypted_storage=encrypted_storage)
    return _audit_store


def audit_log(
    action: AuditAction,
    actor_id: str = None,
    actor_email: str = None,
    resource_type: str = None,
    resource_id: str = None,
    channel: str = None,
    ip_address: str = None,
    details: Dict = None,
    severity: AuditSeverity = AuditSeverity.INFO,
) -> AuditEvent:
    """
    Log an audit event (convenience function).

    Usage:
        from familiar.core.audit import audit_log, AuditAction

        audit_log(
            action=AuditAction.LOGIN,
            actor_email="user@example.org",
            ip_address=request.remote_addr
        )
    """
    store = get_audit_store()

    event = AuditEvent(
        actor_id=actor_id,
        actor_email=actor_email,
        action=action,
        severity=severity,
        resource_type=resource_type,
        resource_id=resource_id,
        channel=channel,
        ip_address=ip_address,
        details=details or {},
    )

    return store.log(event)


# ============================================================
# FLASK INTEGRATION
# ============================================================


def setup_audit_routes(app, audit_store: AuditStore = None):
    """
    Add audit routes to Flask admin app.

    Routes:
        GET /admin/audit - Audit log viewer
        GET /admin/audit/export - Export audit logs
        GET /admin/audit/stats - Audit statistics
        POST /admin/audit/verify - Verify hash chain
    """
    from flask import g, jsonify, render_template, request

    if audit_store is None:
        audit_store = get_audit_store()

    @app.route("/admin/audit")
    def audit_view():
        """Audit log viewer."""
        # Get filters from query params
        days = int(request.args.get("days", 7))
        action_filter = request.args.get("action")
        actor_filter = request.args.get("actor")

        start = datetime.now(timezone.utc) - timedelta(days=days)

        events = audit_store.query(
            start=start,
            action=AuditAction(action_filter) if action_filter else None,
            actor_email=actor_filter if actor_filter else None,
            limit=500,
        )

        stats = audit_store.get_stats(start=start)

        return render_template(
            "audit.html",
            user=g.user,
            events=events,
            stats=stats,
            days=days,
            actions=[a.value for a in AuditAction],
        )

    @app.route("/admin/audit/export")
    def audit_export():
        """Export audit logs."""
        format = request.args.get("format", "csv")
        days = int(request.args.get("days", 30))

        start = datetime.now(timezone.utc) - timedelta(days=days)
        end = datetime.now(timezone.utc)

        # Log the export
        audit_log(
            action=AuditAction.AUDIT_EXPORT,
            actor_id=g.user.id if g.user else None,
            actor_email=g.user.email if g.user else None,
            details={"format": format, "days": days},
        )

        if format == "json":
            data = audit_store.export_json(start=start, end=end)
            mimetype = "application/json"
            filename = f"audit_export_{datetime.now(timezone.utc).strftime('%Y%m%d')}.json"
        elif format == "siem":
            data = audit_store.export_siem(start=start, end=end)
            mimetype = "application/x-ndjson"
            filename = f"audit_export_{datetime.now(timezone.utc).strftime('%Y%m%d')}.ndjson"
        else:
            data = audit_store.export_csv(start=start, end=end)
            mimetype = "text/csv"
            filename = f"audit_export_{datetime.now(timezone.utc).strftime('%Y%m%d')}.csv"

        return app.response_class(
            data,
            mimetype=mimetype,
            headers={"Content-Disposition": f"attachment; filename={filename}"},
        )

    @app.route("/admin/audit/stats")
    def audit_stats():
        """Get audit statistics."""
        days = int(request.args.get("days", 30))
        start = datetime.now(timezone.utc) - timedelta(days=days)

        stats = audit_store.get_stats(start=start)
        return jsonify(stats)

    @app.route("/admin/audit/verify", methods=["POST"])
    def audit_verify():
        """Verify hash chain integrity."""
        result = audit_store.verify_chain()
        return jsonify(result)

    return app
