"""
Policy Events
=============

Event system for policy decision logging and audit trails.

Events can be:
- Logged to files
- Streamed to external systems
- Used for metrics/monitoring
- Stored for compliance audits
"""

from __future__ import annotations

import json
import logging
from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
from pathlib import Path
from typing import Any, Callable, Dict, List, Optional, TextIO, Union

from hieromem_core.policy.evaluator import PolicyAction, PolicyDecision, PolicyResult

logger = logging.getLogger(__name__)


class PolicyEventType(Enum):
    """Types of policy events."""
    DECISION = "decision"  # Policy evaluated
    POLICY_LOADED = "policy_loaded"  # Policy registered
    POLICY_REMOVED = "policy_removed"  # Policy unregistered
    VIOLATION = "violation"         # Policy denied operation
    WARNING = "warning"             # Policy warned on operation
    CAPACITY_ALERT = "capacity_alert"  # Approaching limits
    DECAY_TRIGGERED = "decay_triggered"  # Memory decayed
    PRUNE_TRIGGERED = "prune_triggered"  # Memory pruned


@dataclass
class PolicyEvent:
    """
    A policy event for logging and audit.

    Events capture policy decisions and system state changes
    for compliance, debugging, and monitoring purposes.
    """
    event_type: PolicyEventType
    timestamp: datetime = field(default_factory=datetime.utcnow)

    # Event source
    policy_name: Optional[str] = None
    namespace: Optional[str] = None
    episode_id: Optional[str] = None

    # Decision details (for DECISION events)
    action: Optional[PolicyAction] = None
    result: Optional[PolicyResult] = None
    reason: Optional[str] = None

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

    # Trace ID for correlating events
    trace_id: Optional[str] = None

    @classmethod
    def from_decision(cls, decision: PolicyDecision, trace_id: Optional[str] = None) -> "PolicyEvent":
        """
        Create an event from a policy decision.

        Args:
            decision: The PolicyDecision to convert.
            trace_id: Optional trace ID for correlation.

        Returns:
            PolicyEvent representing the decision.
        """
        # Determine event type based on result
        if decision.result == PolicyResult.DENY:
            event_type = PolicyEventType.VIOLATION
        elif decision.result == PolicyResult.WARN:
            event_type = PolicyEventType.WARNING
        else:
            event_type = PolicyEventType.DECISION

        return cls(
            event_type=event_type,
            timestamp=decision.timestamp,
            policy_name=decision.policy_name,
            namespace=decision.namespace,
            episode_id=decision.episode_id,
            action=decision.action,
            result=decision.result,
            reason=decision.reason,
            details={
                "modifications": decision.modifications,
                "warnings": decision.warnings,
            },
            trace_id=trace_id,
        )

    def to_dict(self) -> Dict[str, Any]:
        """Convert event to dictionary for serialization."""
        return {
            "event_type": self.event_type.value,
            "timestamp": self.timestamp.isoformat(),
            "policy_name": self.policy_name,
            "namespace": self.namespace,
            "episode_id": self.episode_id,
            "action": self.action.value if self.action else None,
            "result": self.result.value if self.result else None,
            "reason": self.reason,
            "details": self.details,
            "trace_id": self.trace_id,
        }

    def to_json(self) -> str:
        """Convert event to JSON string."""
        return json.dumps(self.to_dict())


class PolicyEventHandler:
    """Base class for policy event handlers."""

    def handle(self, event: PolicyEvent) -> None:
        """
        Handle a policy event.

        Args:
            event: The event to handle.
        """
        raise NotImplementedError


class LoggingEventHandler(PolicyEventHandler):
    """Handler that logs events using Python logging."""

    def __init__(
        self,
        logger_name: str = "hieromem.policy",
        level_map: Optional[Dict[PolicyEventType, int]] = None,
    ):
        """
        Initialize the logging handler.

        Args:
            logger_name: Name of the logger to use.
            level_map: Optional mapping of event types to log levels.
        """
        self._logger = logging.getLogger(logger_name)
        self._level_map = level_map or {
            PolicyEventType.VIOLATION: logging.ERROR,
            PolicyEventType.WARNING: logging.WARNING,
            PolicyEventType.CAPACITY_ALERT: logging.WARNING,
            PolicyEventType.DECISION: logging.DEBUG,
            PolicyEventType.POLICY_LOADED: logging.INFO,
            PolicyEventType.POLICY_REMOVED: logging.INFO,
            PolicyEventType.DECAY_TRIGGERED: logging.DEBUG,
            PolicyEventType.PRUNE_TRIGGERED: logging.INFO,
        }

    def handle(self, event: PolicyEvent) -> None:
        """Log the event at appropriate level."""
        level = self._level_map.get(event.event_type, logging.INFO)
        message = self._format_message(event)
        self._logger.log(level, message)

    def _format_message(self, event: PolicyEvent) -> str:
        """Format event as log message."""
        parts = [f"[{event.event_type.value}]"]

        if event.policy_name:
            parts.append(f"policy={event.policy_name}")
        if event.namespace:
            parts.append(f"ns={event.namespace}")
        if event.action:
            parts.append(f"action={event.action.value}")
        if event.result:
            parts.append(f"result={event.result.value}")
        if event.reason:
            parts.append(f"reason={event.reason}")
        if event.trace_id:
            parts.append(f"trace={event.trace_id}")

        return " ".join(parts)


class FileEventHandler(PolicyEventHandler):
    """Handler that writes events to a file in JSON Lines format."""

    def __init__(
        self,
        path: Union[str, Path],
        append: bool = True,
        flush_every: int = 1,
    ):
        """
        Initialize the file handler.

        Args:
            path: Path to the output file.
            append: If True, append to existing file. If False, overwrite.
            flush_every: Flush after every N events (1 = always flush).
        """
        self._path = Path(path)
        self._append = append
        self._flush_every = flush_every
        self._event_count = 0
        self._file: Optional[TextIO] = None

    def _ensure_open(self) -> TextIO:
        """Ensure file is open for writing."""
        if self._file is None:
            self._path.parent.mkdir(parents=True, exist_ok=True)
            mode = "a" if self._append else "w"
            self._file = open(self._path, mode)
        return self._file

    def handle(self, event: PolicyEvent) -> None:
        """Write event to file."""
        f = self._ensure_open()
        f.write(event.to_json() + "\n")
        self._event_count += 1

        if self._event_count % self._flush_every == 0:
            f.flush()

    def close(self) -> None:
        """Close the file."""
        if self._file:
            self._file.close()
            self._file = None

    def __del__(self):
        self.close()


class CallbackEventHandler(PolicyEventHandler):
    """Handler that calls a user-provided callback function."""

    def __init__(self, callback: Callable[[PolicyEvent], None]):
        """
        Initialize with a callback function.

        Args:
            callback: Function to call for each event.
        """
        self._callback = callback

    def handle(self, event: PolicyEvent) -> None:
        """Call the callback with the event."""
        self._callback(event)


class FilteredEventHandler(PolicyEventHandler):
    """Handler that filters events before passing to another handler."""

    def __init__(
        self,
        inner: PolicyEventHandler,
        event_types: Optional[set[PolicyEventType]] = None,
        namespaces: Optional[set[str]] = None,
        min_severity: Optional[PolicyResult] = None,
    ):
        """
        Initialize with filtering criteria.

        Args:
            inner: Handler to pass filtered events to.
            event_types: Only handle these event types (None = all).
            namespaces: Only handle events from these namespaces (None = all).
            min_severity: Only handle events with this severity or higher.
        """
        self._inner = inner
        self._event_types = event_types
        self._namespaces = namespaces
        self._min_severity = min_severity

        # Severity ordering (highest to lowest)
        self._severity_order = [PolicyResult.DENY, PolicyResult.WARN, PolicyResult.ALLOW]

    def handle(self, event: PolicyEvent) -> None:
        """Filter and potentially forward the event."""
        # Check event type
        if self._event_types and event.event_type not in self._event_types:
            return

        # Check namespace
        if self._namespaces and event.namespace not in self._namespaces:
            return

        # Check severity
        if self._min_severity and event.result:
            event_idx = self._severity_order.index(event.result) if event.result in self._severity_order else 2
            min_idx = self._severity_order.index(self._min_severity)
            if event_idx > min_idx:
                return

        # Passed all filters
        self._inner.handle(event)


class PolicyEventEmitter:
    """
    Central event emitter for policy events.

    Manages multiple handlers and provides convenient emit methods.
    """

    def __init__(self):
        """Initialize the event emitter."""
        self._handlers: List[PolicyEventHandler] = []

    def add_handler(self, handler: PolicyEventHandler) -> None:
        """
        Add an event handler.

        Args:
            handler: Handler to add.
        """
        self._handlers.append(handler)

    def remove_handler(self, handler: PolicyEventHandler) -> bool:
        """
        Remove an event handler.

        Args:
            handler: Handler to remove.

        Returns:
            True if handler was found and removed.
        """
        try:
            self._handlers.remove(handler)
            return True
        except ValueError:
            return False

    def emit(self, event: PolicyEvent) -> None:
        """
        Emit an event to all handlers.

        Args:
            event: Event to emit.
        """
        for handler in self._handlers:
            try:
                handler.handle(event)
            except Exception as e:
                logger.warning(f"Event handler error: {e}")

    def emit_decision(self, decision: PolicyDecision, trace_id: Optional[str] = None) -> None:
        """
        Emit a decision event.

        Args:
            decision: Policy decision to emit.
            trace_id: Optional trace ID.
        """
        event = PolicyEvent.from_decision(decision, trace_id)
        self.emit(event)

    def emit_policy_loaded(self, policy_name: str, namespace: str) -> None:
        """Emit a policy loaded event."""
        self.emit(PolicyEvent(
            event_type=PolicyEventType.POLICY_LOADED,
            policy_name=policy_name,
            namespace=namespace,
        ))

    def emit_policy_removed(self, policy_name: str) -> None:
        """Emit a policy removed event."""
        self.emit(PolicyEvent(
            event_type=PolicyEventType.POLICY_REMOVED,
            policy_name=policy_name,
        ))

    def emit_capacity_alert(
        self,
        namespace: str,
        current: int,
        maximum: int,
        policy_name: Optional[str] = None,
    ) -> None:
        """Emit a capacity alert event."""
        self.emit(PolicyEvent(
            event_type=PolicyEventType.CAPACITY_ALERT,
            policy_name=policy_name,
            namespace=namespace,
            details={
                "current": current,
                "maximum": maximum,
                "usage_pct": current / maximum if maximum > 0 else 0,
            },
        ))

    def emit_decay(
        self,
        namespace: str,
        episode_id: str,
        old_trust: float,
        new_trust: float,
        policy_name: Optional[str] = None,
    ) -> None:
        """Emit a decay triggered event."""
        self.emit(PolicyEvent(
            event_type=PolicyEventType.DECAY_TRIGGERED,
            policy_name=policy_name,
            namespace=namespace,
            episode_id=episode_id,
            details={
                "old_trust": old_trust,
                "new_trust": new_trust,
            },
        ))

    def emit_prune(
        self,
        namespace: str,
        episode_id: str,
        reason: str,
        policy_name: Optional[str] = None,
    ) -> None:
        """Emit a prune triggered event."""
        self.emit(PolicyEvent(
            event_type=PolicyEventType.PRUNE_TRIGGERED,
            policy_name=policy_name,
            namespace=namespace,
            episode_id=episode_id,
            reason=reason,
        ))


# Default global emitter
_default_emitter: Optional[PolicyEventEmitter] = None


def get_default_emitter() -> PolicyEventEmitter:
    """Get or create the default global event emitter."""
    global _default_emitter
    if _default_emitter is None:
        _default_emitter = PolicyEventEmitter()
    return _default_emitter


def setup_default_logging(
    log_level: int = logging.INFO,
    file_path: Optional[Union[str, Path]] = None,
) -> PolicyEventEmitter:
    """
    Set up default logging for policy events.

    Args:
        log_level: Logging level for the logging handler.
        file_path: Optional path for JSON Lines file output.

    Returns:
        Configured PolicyEventEmitter.
    """
    emitter = get_default_emitter()

    # Add logging handler
    log_handler = LoggingEventHandler()
    emitter.add_handler(log_handler)

    # Optionally add file handler
    if file_path:
        file_handler = FileEventHandler(file_path)
        emitter.add_handler(file_handler)

    return emitter
