"""
Agent observability module.

Provides structured logging and event tracking for agent execution.
Uses the observer pattern to decouple observability from agent logic.
"""

from dataclasses import dataclass, field
from datetime import UTC, datetime
from typing import Any, Protocol

from persuade.logging import get_logger


@dataclass
class AgentEvent:
    """Structured event from agent execution.

    Attributes:
        event_type: Type of event (e.g., "phase.started", "phase.completed")
        agent_type: Class name of the agent
        run_id: Unique identifier for this run
        timestamp: ISO format timestamp
        phase: Current phase ("plan" or "execute")
        duration_ms: Duration in milliseconds (for completed events)
        data: Additional event data
        error: Error message if applicable
    """

    event_type: str
    agent_type: str
    run_id: str
    timestamp: str = field(default_factory=lambda: datetime.now(UTC).isoformat())
    phase: str | None = None
    duration_ms: float | None = None
    data: dict[str, Any] | None = None
    error: str | None = None


class AgentObserver(Protocol):
    """Protocol for observing agent execution.

    Implement this protocol to receive notifications about agent lifecycle events.
    The protocol is intentionally minimal to allow flexible implementations.
    """

    def on_phase_start(self, agent_type: str, run_id: str, phase: str) -> None:
        """Called when an agent phase starts.

        Args:
            agent_type: Class name of the agent
            run_id: Unique identifier for this run
            phase: Phase name ("plan" or "execute")
        """
        ...

    def on_phase_complete(
        self,
        agent_type: str,
        run_id: str,
        phase: str,
        duration_ms: float,
        success: bool,
        error: str | None = None,
    ) -> None:
        """Called when an agent phase completes.

        Args:
            agent_type: Class name of the agent
            run_id: Unique identifier for this run
            phase: Phase name ("plan" or "execute")
            duration_ms: Duration of the phase in milliseconds
            success: Whether the phase succeeded
            error: Error message if success is False
        """
        ...


class LoggingObserver:
    """Observer that logs structured events using the configured logger.

    Uses structlog when available, falls back to stdlib logging.
    Events are logged with structured fields for easy parsing.

    Example:
        runner = AgentRunner(observer=LoggingObserver())
        result = runner.run_sync(agent)
        # Logs: "Agent phase started agent_type=IteratePRAgent run_id=abc123 phase=plan"
    """

    def __init__(self) -> None:
        """Initialize with the configured logger."""
        self.logger = get_logger()

    def on_phase_start(self, agent_type: str, run_id: str, phase: str) -> None:
        """Log phase start event."""
        self.logger.info(
            "Agent phase started",
            agent_type=agent_type,
            run_id=run_id,
            phase=phase,
        )

    def on_phase_complete(
        self,
        agent_type: str,
        run_id: str,
        phase: str,
        duration_ms: float,
        success: bool,
        error: str | None = None,
    ) -> None:
        """Log phase completion event."""
        self.logger.info(
            "Agent phase completed",
            agent_type=agent_type,
            run_id=run_id,
            phase=phase,
            duration_ms=round(duration_ms, 2),
            success=success,
            error=error,
        )


class NullObserver:
    """No-op observer for when observability is disabled.

    Use this as the default when no observer is provided.
    All methods are no-ops with minimal overhead.
    """

    def on_phase_start(self, agent_type: str, run_id: str, phase: str) -> None:
        """No-op: do nothing on phase start."""
        pass

    def on_phase_complete(
        self,
        agent_type: str,
        run_id: str,
        phase: str,
        duration_ms: float,
        success: bool,
        error: str | None = None,
    ) -> None:
        """No-op: do nothing on phase complete."""
        pass


class CompositeObserver:
    """Observer that forwards events to multiple observers.

    Useful when you need to log events AND collect metrics.

    Example:
        observer = CompositeObserver([
            LoggingObserver(),
            MetricsObserver(),
        ])
    """

    def __init__(self, observers: list[AgentObserver]) -> None:
        """Initialize with a list of observers to forward to."""
        self.observers = observers

    def on_phase_start(self, agent_type: str, run_id: str, phase: str) -> None:
        """Forward phase start to all observers."""
        for observer in self.observers:
            observer.on_phase_start(agent_type, run_id, phase)

    def on_phase_complete(
        self,
        agent_type: str,
        run_id: str,
        phase: str,
        duration_ms: float,
        success: bool,
        error: str | None = None,
    ) -> None:
        """Forward phase completion to all observers."""
        for observer in self.observers:
            observer.on_phase_complete(agent_type, run_id, phase, duration_ms, success, error)
