"""Defines runtime execution event types (the "History" dimension).

This module implements the "Event Sourcing" pattern using Strict Polymorphism.
Instead of a generic JSON payload, each event type is a distinct class schema.
This guarantees that if the event type is 'run_failed', the consumer KNOWS
there is an 'error_type' field available.

The event ledger is the single source of truth for:
- Auditing and compliance
- Debugging and observability
- Replay and time-travel debugging
- Usage tracking and cost attribution

Events are immutable and append-only. They capture WHAT HAPPENED, not what
should happen next (that's the Kernel's job).
"""
import uuid
from datetime import datetime, timezone
from typing import Any, Dict, List, Literal, Optional, Union
from typing_extensions import Annotated
from enum import Enum
from pydantic import Field, field_validator

from xulcan.core.primitives import (
    CanonicalRecord, 
    MachineID,
    HumanLabel,
    SemanticText,
    ExternalID,
    FinitePositiveFloat,
    SafePayload,
    SemanticVersion
)
from xulcan.core.economics import UsageStats
from xulcan.protocol.message import UnifiedMessage
from xulcan.protocol.tools import ToolCall


# 1. Definimos el Catálogo de Eventos (La fuente de la verdad)
class EventType(str, Enum):
    # Lifecycle
    RUN_CREATED = "run_created"
    RUN_COMPLETED = "run_completed"
    RUN_FAILED = "run_failed"
    
    # Flow
    STEP_STARTED = "step_started"
    
    # Inference
    MODEL_REQUEST = "model_request"
    MODEL_RESPONSE = "model_response"
    
    # Tooling
    TOOL_EXECUTION = "tool_execution"
    TOOL_OUTPUT = "tool_output"
    
    # Governance
    POLICY_VIOLATION = "policy_violation"
    HUMAN_INTERVENTION_REQUIRED = "human_intervention_required"
    HUMAN_INTERVENTION_RESULT = "human_intervention_result"

class StepType(str, Enum):
    INFERENCE = "inference"
    TOOL_EXECUTION = "tool_execution"
    LIFECYCLE = "lifecycle"

class RunStatus(str, Enum):
    """High-level status for the Ledger Projection (RunSnapshot).
    Differs from KernelState (FSM) which is granular and ephemeral.
    """
    CREATED = "created"
    RUNNING = "running"
    PAUSED = "paused"    # Awaiting human intervention
    COMPLETED = "completed"
    FAILED = "failed"


# ═══════════════════════════════════════════════════════════════════════════
# BASE EVENT (ABSTRACT)
# ═══════════════════════════════════════════════════════════════════════════

class BaseRunEvent(CanonicalRecord):
    """Abstract base class for all execution events.
    
    Contains the shared metadata required for auditing, sequencing, and tracing.
    Does not contain payload data (defined in subclasses).
    
    Design Philosophy:
        - Events are immutable facts about the past
        - Timestamps are always UTC and timezone-aware
        - step_index provides a logical clock for ordering
        - metadata enables distributed tracing
    
    Attributes:
        id: Unique event identifier (UUID v4).
        run_id: Trace ID linking this event to a specific execution run.
        timestamp: When this event occurred (always UTC, timezone-aware).
        step_index: Logical clock / sequence number within the run.
        metadata: Arbitrary tracing info (user_id, correlation_id, app_version).
    """
    type: EventType

    id: MachineID = Field(
        default_factory=lambda: str(uuid.uuid4()),
        description="Unique event identifier (UUID v4)"
    )
    
    run_id: MachineID = Field(
        description="Trace ID linking this event to a specific execution run"
    )
    
    timestamp: datetime = Field(
        default_factory=lambda: datetime.now(timezone.utc),
        description="When this event occurred (always UTC)"
    )

    sequence_number: int = Field(
        ge=1,
        description="Absolute monotonic counter for this event within the run (1, 2, 3...). Vital for deterministic replay."
    )
    
    step_index: int = Field(
        ge=0,
        description="Logical clock / Sequence number within the run"
    )
    
    metadata: SafePayload = Field(
        default_factory=dict,
        description="Tracing info (user_id, correlation_id, app_version, etc.)"
    )

    @field_validator('timestamp')
    @classmethod
    def validate_timestamp_is_utc(cls, v: datetime) -> datetime:
        """Ensure timestamp is timezone-aware and normalized to UTC."""
        if v.tzinfo is None:
            raise ValueError("Timestamp must be timezone-aware")
        if v.tzinfo != timezone.utc:
            v = v.astimezone(timezone.utc)
        return v


# ═══════════════════════════════════════════════════════════════════════════
# LIFECYCLE EVENTS (Run Boundary)
# ═══════════════════════════════════════════════════════════════════════════

class RunCreated(BaseRunEvent):
    """The Big Bang. Fired when a new agent run is initialized.
    
    This is the first event in every run's ledger. It captures the initial
    state and user intent before any processing begins.
    
    Occurs at: Node START in the Kernel flow diagram.
    
    Attributes:
        agent_id: Which agent configuration is being executed.
        agent_version: Semantic version of the blueprint.
        user_input: The initial messages/prompts from the user.
        initial_budget: Optional budget constraints (tokens, cost, time).
    """
    type: Literal[EventType.RUN_CREATED] = EventType.RUN_CREATED
    
    agent_id: MachineID = Field(
        description="Identifier of the agent being executed"
    )
    
    agent_version: SemanticVersion = Field(
        description="Semantic version of the agent blueprint"
    )
    
    user_input: List[UnifiedMessage] = Field(
        min_length=1,
        description="Initial user messages that triggered this run"
    )
    
    initial_budget: Optional[SafePayload] = Field(
        default=None,
        description="Budget constraints (max_tokens, max_cost, timeout)"
    )


class RunCompleted(BaseRunEvent):
    """Fired when the run finishes successfully.
    
    This is the terminal success state. After this event, no more events
    should be appended to the run's ledger.
    
    Occurs at: Node SUCCESS in the Kernel flow diagram.
    
    Attributes:
        final_response: The agent's final output to the user.
        total_usage: Cumulative resource usage across all steps.
    """
    type: Literal[EventType.RUN_COMPLETED] = EventType.RUN_COMPLETED
    
    final_response: Optional[UnifiedMessage] = Field(
        default=None,
        description="The agent's final output (may be None for no-response runs)"
    )
    
    total_usage: UsageStats = Field(
        description="Cumulative tokens, cost, and latency across all LLM calls"
    )


class RunFailed(BaseRunEvent):
    """Fired when the run crashes or encounters a fatal error.
    
    This is the terminal failure state. Contains diagnostic information
    for debugging and alerting.
    
    Occurs at: Node ERROR in the Kernel flow diagram.
    
    Attributes:
        error_type: Classification of the error (timeout, exception, budget).
        error_message: Human-readable error description.
        traceback: Optional Python traceback for debugging.
    """
    type: Literal[EventType.RUN_FAILED] = EventType.RUN_FAILED
    
    error_type: MachineID = Field(
        description="Error classification (timeout, validation_error, etc.)"
    )
    
    error_message: SemanticText = Field(
        min_length=1,
        description="Human-readable error description"
    )
    
    traceback: Optional[SemanticText] = Field(
        default=None,
        description="Python traceback for debugging (omitted in production)"
    )


# ═══════════════════════════════════════════════════════════════════════════
# FLOW EVENTS (Kernel State Transitions)
# ═══════════════════════════════════════════════════════════════════════════

class StepStarted(BaseRunEvent):
    """Marks the beginning of a processing cycle in the agent loop.
    
    This event is critical for the "Fold" operation (H2 in Trinity) to know
    when to clear temporary contexts and reset state for the next iteration.
    
    Occurs at: Node B (Process Step) in the Kernel flow diagram.
    
    Attributes:
        step_type: What kind of step is starting (inference, tool_execution, lifecycle).
    """
    type: Literal[EventType.STEP_STARTED] = EventType.STEP_STARTED
    
    step_type: StepType = Field(
        description="Classification of this processing step"
    )


# ═══════════════════════════════════════════════════════════════════════════
# INFERENCE EVENTS (LLM Interactions)
# ═══════════════════════════════════════════════════════════════════════════

class ModelRequest(BaseRunEvent):
    """Fired just before sending the payload to the LLM provider.
    
    Captures the exact input that was sent, enabling perfect reproducibility
    and debugging of model behavior.
    
    Occurs at: Node C (LLM Request) in the Kernel flow diagram.
    
    Attributes:
        provider: LLM provider name (openai, anthropic, google).
        model: Specific model identifier (gpt-4, claude-sonnet-4).
        prompt_messages: The conversation history sent to the model.
        parameters: Sampling parameters (temperature, top_p, max_tokens).
    """
    type: Literal[EventType.MODEL_REQUEST] = EventType.MODEL_REQUEST
    
    provider: ExternalID = Field(
        description="LLM provider name (openai, anthropic, google)"
    )
    
    model: ExternalID = Field(
        description="Specific model identifier (e.g., gpt-4, claude-sonnet-4)"
    )
    
    prompt_messages: List[UnifiedMessage] = Field(
        min_length=1,
        description="The conversation history sent to the model"
    )
    
    parameters: SafePayload = Field(
        default_factory=dict,
        description="Sampling parameters (temperature, top_p, max_tokens, etc.)"
    )


class ModelResponse(BaseRunEvent):
    """Fired when the LLM responds successfully.
    
    Captures the model's output, token usage, and performance metrics.
    
    Occurs at: Node D (LLM Response) in the Kernel flow diagram.
    
    Attributes:
        message: The model's output message (may contain tool calls).
        usage: Token counts and cost for this specific request.
        latency_ms: Time elapsed for this LLM call in milliseconds.
        provider_id: Optional provider-specific request ID for support queries.
    """
    type: Literal[EventType.MODEL_RESPONSE] = EventType.MODEL_RESPONSE
    
    message: UnifiedMessage = Field(
        description="The model's output message (may contain tool calls)"
    )
    
    usage: UsageStats = Field(
        description="Token counts and cost for this specific request"
    )
    
    latency_ms: FinitePositiveFloat = Field(
        description="Time elapsed for this LLM call in milliseconds"
    )
    
    provider_id: Optional[ExternalID] = Field(
        default=None,
        description="Provider-specific request ID for support queries"
    )


# ═══════════════════════════════════════════════════════════════════════════
# TOOL EVENTS (Function Execution)
# ═══════════════════════════════════════════════════════════════════════════

class ToolExecution(BaseRunEvent):
    """Fired when the Kernel decides to execute a tool.
    
    Captures the decision point before actual execution. If the tool crashes,
    there will be a ToolExecution but no matching ToolOutput.
    
    Occurs at: Node E (Tool Execution) in the Kernel flow diagram.
    
    Attributes:
        tool_call: The parsed tool call from the model's response.
        tool_input: The actual arguments passed to the tool function.
    """
    type: Literal[EventType.TOOL_EXECUTION] = EventType.TOOL_EXECUTION
    
    tool_call: ToolCall = Field(
        description="The parsed tool call from the model's response"
    )
    
    tool_input: SafePayload = Field(
        default_factory=dict,
        description="The actual arguments passed to the tool function"
    )


class ToolOutput(BaseRunEvent):
    """Fired when a tool finishes execution (success or failure).
    
    Contains the raw output from the tool. The Kernel decides how to
    serialize this into a message for the model.
    
    Occurs at: Node F (Tool Result) in the Kernel flow diagram.
    
    Attributes:
        tool_call_id: Links this output to the corresponding ToolExecution.
        tool_name: Which tool produced this output.
        output: Serialized result (JSON, text, or error message).
        is_error: Whether this output represents a tool execution failure.
    """
    type: Literal[EventType.TOOL_OUTPUT] = EventType.TOOL_OUTPUT
    
    tool_call_id: MachineID = Field(
        description="Links this output to the corresponding ToolExecution"
    )
    
    tool_name: MachineID = Field(
        description="Which tool produced this output"
    )
    
    output: SemanticText = Field(
        description="Serialized result (JSON, text, or error message)"
    )
    
    is_error: bool = Field(
        default=False,
        description="Whether this output represents a tool execution failure"
    )


# ═══════════════════════════════════════════════════════════════════════════
# GOVERNANCE & SAFETY EVENTS (Policy Enforcement)
# ═══════════════════════════════════════════════════════════════════════════

class PolicyViolation(BaseRunEvent):
    """Fired when the Kernel blocks an action due to policy constraints.
    
    This is the enforcement point for safety guardrails. The model is NOT
    allowed to execute the violating tool, and this event explains why.
    
    Occurs at: Node EC3 (Policy Violation) in the Kernel flow diagram.
    
    Attributes:
        violating_tool: Which tool call triggered the policy violation.
        reason: Human-readable explanation of why this was blocked.
        policy_id: Optional identifier of the specific policy that was violated.
    """
    type: Literal[EventType.POLICY_VIOLATION] = EventType.POLICY_VIOLATION
    
    violating_tool: MachineID = Field(
        description="Which tool call triggered the policy violation"
    )
    
    reason: SemanticText = Field(
        min_length=1,
        description="Human-readable explanation of why this was blocked"
    )
    
    policy_id: Optional[MachineID] = Field(
        default=None,
        description="Identifier of the specific policy that was violated"
    )


class HumanInterventionRequired(BaseRunEvent):
    """Fired when the Kernel pauses execution and requests human approval.
    
    The run enters a suspended state until a human operator reviews the
    context and provides a decision via HumanInterventionResult.
    
    Occurs at: Node AA (Human Gate) in the Kernel flow diagram.
    
    Attributes:
        reason: Why human approval is needed (destructive_action, sensitive_data).
        data_context: Context data for the human to review before deciding.
    """
    type: Literal[EventType.HUMAN_INTERVENTION_REQUIRED] = EventType.HUMAN_INTERVENTION_REQUIRED
    
    reason: SemanticText = Field(
        min_length=1,
        description="Why human approval is needed (destructive_action, etc.)"
    )
    
    data_context: SafePayload = Field(
        default_factory=dict,
        description="Context data for the human to review before deciding"
    )


class HumanInterventionResult(BaseRunEvent):
    """Fired when a human operator responds to a HumanInterventionRequired event.
    
    Contains the human's decision (approve/reject) and optional feedback that
    gets injected back into the agent's context.
    
    Occurs at: Node AB (Human Decision) in the Kernel flow diagram.
    
    Attributes:
        approved: Whether the human approved the pending action.
        feedback: Optional explanation or guidance from the human operator.
    """
    type: Literal[EventType.HUMAN_INTERVENTION_RESULT] = EventType.HUMAN_INTERVENTION_RESULT
    
    approved: bool = Field(
        description="Whether the human approved the pending action"
    )
    
    feedback: Optional[SemanticText] = Field(
        default=None,
        description="Optional explanation or guidance from the human operator"
    )


# ═══════════════════════════════════════════════════════════════════════════
# THE UNION (POLYMORPHIC CONTAINER)
# ═══════════════════════════════════════════════════════════════════════════

RunEvent = Annotated[
    Union[
        # Lifecycle Events (Run Boundaries)
        RunCreated,
        RunCompleted,
        RunFailed,
        
        # Flow Events (Kernel State)
        StepStarted,
        
        # Inference Events (LLM)
        ModelRequest,
        ModelResponse,
        
        # Tool Events (Function Execution)
        ToolExecution,
        ToolOutput,
        
        # Governance Events (Policy & Safety)
        PolicyViolation,
        HumanInterventionRequired,
        HumanInterventionResult,
    ],
    Field(discriminator='type')
]


# ═══════════════════════════════════════════════════════════════════════════
# READ MODELS (PROJECTIONS)
# ═══════════════════════════════════════════════════════════════════════════

class RunSummary(CanonicalRecord):
    """A read-only projection of the agent's current state (The 'Fold' result).
    
    This replaces the abstract 'StateView'. It is a concrete DTO that
    can be serialized to JSON and sent over APIs.
    """
    run_id: MachineID
    
    status: RunStatus = Field(
        description="High-level lifecycle status (for UI/API)"
    )
    
    step_count: int = Field(
        ge=0,
        description="Current logical clock / sequence number"
    )
    
    created_at: datetime = Field(
        description="When the run started"
    )
    
    updated_at: datetime = Field(
        description="Timestamp of the last event"
    )
    
    last_event: Optional[RunEvent] = Field(
        default=None,
        description="The most recent event appended to the ledger"
    )
    
    @property
    def is_terminal(self) -> bool:
        """True if the run has reached a final state."""
        return self.status in (RunStatus.COMPLETED, RunStatus.FAILED)

"""
═══════════════════════════════════════════════════════════════════════════
USAGE EXAMPLE: Pattern Matching with Strict Polymorphism
═══════════════════════════════════════════════════════════════════════════

    def process_event(event: RunEvent) -> None:
        # Python 3.10+ Pattern Matching works perfectly!
        match event:
            case RunFailed(error_type=err, error_message=msg):
                log.error(f"Run failed: {err} - {msg}")
                alert_ops_team(event)
            
            case ModelResponse(usage=stats, latency_ms=lat):
                metrics.record_llm_call(stats, lat)
                log.info(f"LLM call: {stats.total_tokens} tokens in {lat}ms")
            
            case PolicyViolation(violating_tool=tool, reason=reason):
                log.warning(f"Blocked {tool}: {reason}")
                notify_security_team(event)
            
            case HumanInterventionRequired(reason=reason):
                log.info(f"Human approval needed: {reason}")
                enqueue_for_review(event)
            
            case ToolOutput(tool_name=name, is_error=True):
                log.error(f"Tool {name} failed")
                retry_with_fallback(event)
            
            case _:
                log.debug(f"Event: {event.type}")

═══════════════════════════════════════════════════════════════════════════
DESIGN GUARANTEES
═══════════════════════════════════════════════════════════════════════════

1. Type Safety: If `event.type == "run_failed"`, TypeScript/Python KNOW
   there's an `error_type` field. No runtime `KeyError` surprises.

2. Exhaustiveness: Pattern matching can detect missing cases at compile time.

3. Evolvability: Adding new event types doesn't break existing consumers
   (they can use `case _` for unknown events).

4. Auditability: Every event is immutable and contains full context.

5. Replayability: Given a sequence of events, you can reconstruct the
   exact state of the system at any point in time.
"""