"""
Stream Event Contract - SSE Events for Streaming Agent Responses.

Derived from: definitions/ai/contracts/stream_event.yaml

This module provides typed Server-Sent Events (SSE) for streaming agent
responses. Each event type has a specific data schema.

Usage:
    from lightwave.schema.pydantic.contracts.ai import StreamEvent, EventType

    # Parse incoming SSE event
    event = StreamEvent.model_validate_json(event_data)

    if event.event_type == EventType.CHUNK:
        print(event.data.content)
    elif event.event_type == EventType.STATUS:
        print(f"Status: {event.data.status}")
"""

from __future__ import annotations

from datetime import datetime
from enum import Enum
from typing import Annotated, Any, Union
from uuid import UUID

from pydantic import Field

from lightwave.schema.pydantic.core.base import LightwaveBaseSchema

from .agent_request import AgentType


class EventType(str, Enum):
    """Types of events in the SSE stream."""

    # Content events
    CHUNK = "chunk"
    CONTENT_COMPLETE = "content_complete"

    # Tool events
    TOOL_CALL_START = "tool_call_start"
    TOOL_CALL_END = "tool_call_end"

    # Status events
    STATUS = "status"
    PROGRESS = "progress"

    # Handoff events
    HANDOFF = "handoff"

    # Error events
    ERROR = "error"

    # Lifecycle events
    START = "start"
    END = "end"


class StreamStatus(str, Enum):
    """Agent status values for status events."""

    THINKING = "thinking"
    EXECUTING = "executing"
    WAITING = "waiting"
    STREAMING = "streaming"
    COMPLETE = "complete"
    ERROR = "error"


# =============================================================================
# Event Data Schemas
# =============================================================================


class ChunkData(LightwaveBaseSchema):
    """Data for text chunk events."""

    content: str = Field(
        ...,
        max_length=4000,
        description="Text content of the chunk",
    )

    is_partial: bool = Field(
        True,
        description="Whether this is a partial chunk",
    )


class ContentCompleteData(LightwaveBaseSchema):
    """Data for content complete events."""

    total_length: int | None = Field(
        None,
        ge=0,
        description="Total character length of content",
    )

    chunk_count: int | None = Field(
        None,
        ge=0,
        description="Number of chunks sent",
    )


class StatusData(LightwaveBaseSchema):
    """Data for status change events."""

    status: StreamStatus = Field(
        ...,
        description="Current agent status",
    )

    message: str | None = Field(
        None,
        max_length=200,
        description="Human-readable status message",
    )


class ToolCallStartData(LightwaveBaseSchema):
    """Data for tool call start events."""

    tool_name: str = Field(
        ...,
        max_length=100,
        description="Name of the tool being called",
    )

    tool_args: dict[str, Any] | None = Field(
        None,
        description="Arguments passed to the tool",
    )

    tool_id: str | None = Field(
        None,
        max_length=50,
        description="Unique identifier for this tool call",
    )


class ToolCallEndData(LightwaveBaseSchema):
    """Data for tool call end events."""

    tool_name: str = Field(
        ...,
        max_length=100,
        description="Name of the tool that completed",
    )

    tool_id: str | None = Field(
        None,
        max_length=50,
        description="Identifier matching the start event",
    )

    success: bool = Field(
        ...,
        description="Whether the tool call succeeded",
    )

    result_summary: str | None = Field(
        None,
        max_length=500,
        description="Brief summary of the result",
    )

    duration_ms: int | None = Field(
        None,
        ge=0,
        description="Tool execution time in milliseconds",
    )


class HandoffData(LightwaveBaseSchema):
    """Data for agent handoff events."""

    from_agent: AgentType = Field(
        ...,
        description="Agent initiating handoff",
    )

    to_agent: AgentType = Field(
        ...,
        description="Agent receiving handoff",
    )

    reason: str = Field(
        ...,
        max_length=500,
        description="Why the handoff is occurring",
    )

    context_summary: str | None = Field(
        None,
        max_length=1000,
        description="Summary of context being passed",
    )


class ProgressData(LightwaveBaseSchema):
    """Data for progress update events."""

    current: int = Field(
        ...,
        ge=0,
        description="Current progress value",
    )

    total: int = Field(
        ...,
        ge=1,
        description="Total expected value",
    )

    message: str | None = Field(
        None,
        max_length=200,
        description="Progress message",
    )

    percentage: float | None = Field(
        None,
        ge=0,
        le=100,
        description="Calculated percentage (0-100)",
    )

    def model_post_init(self, __context: Any) -> None:
        """Calculate percentage if not provided."""
        if self.percentage is None and self.total > 0:
            object.__setattr__(self, "percentage", round((self.current / self.total) * 100, 1))


class ErrorData(LightwaveBaseSchema):
    """Data for error events."""

    error_code: str = Field(
        ...,
        max_length=50,
        description="Error code identifier",
    )

    message: str = Field(
        ...,
        max_length=1000,
        description="Human-readable error message",
    )

    recoverable: bool = Field(
        False,
        description="Whether the error is recoverable",
    )

    retry_after_ms: int | None = Field(
        None,
        ge=0,
        description="Suggested retry delay if recoverable",
    )


class StartData(LightwaveBaseSchema):
    """Data for execution start events."""

    agent_type: AgentType = Field(
        ...,
        description="Agent starting execution",
    )

    estimated_duration_ms: int | None = Field(
        None,
        ge=0,
        description="Estimated execution time",
    )


class EndData(LightwaveBaseSchema):
    """Data for execution end events."""

    agent_type: AgentType = Field(
        ...,
        description="Agent that completed",
    )

    success: bool = Field(
        ...,
        description="Whether execution succeeded",
    )

    duration_ms: int | None = Field(
        None,
        ge=0,
        description="Total execution time in milliseconds",
    )

    token_count: int | None = Field(
        None,
        ge=0,
        description="Total tokens used",
    )


# Union type for all event data
EventData = Annotated[
    Union[
        ChunkData,
        ContentCompleteData,
        StatusData,
        ToolCallStartData,
        ToolCallEndData,
        HandoffData,
        ProgressData,
        ErrorData,
        StartData,
        EndData,
    ],
    Field(discriminator=None),  # No automatic discriminator, use event_type
]


class StreamEvent(LightwaveBaseSchema):
    """
    A single event in the SSE stream.

    StreamEvents provide real-time updates during agent execution. The
    event_type determines which data schema is used.

    SSE Wire Format:
        event: chunk
        data: {"event_type": "chunk", "request_id": "...", "data": {"content": "Hello"}}

    Example:
        # Creating events for streaming
        yield StreamEvent(
            event_type=EventType.CHUNK,
            request_id=request_id,
            data=ChunkData(content="Hello "),
        )

        yield StreamEvent(
            event_type=EventType.STATUS,
            request_id=request_id,
            data=StatusData(status=StreamStatus.COMPLETE),
        )
    """

    event_type: EventType = Field(
        ...,
        description="Type of stream event",
    )

    request_id: UUID = Field(
        ...,
        description="Request ID this event belongs to",
    )

    timestamp: datetime = Field(
        default_factory=datetime.utcnow,
        description="ISO 8601 timestamp of the event",
    )

    sequence: int | None = Field(
        None,
        ge=0,
        description="Sequence number for ordering",
    )

    data: EventData | None = Field(
        None,
        description="Event-specific data payload",
    )

    def to_sse_format(self) -> str:
        """
        Convert to Server-Sent Events wire format.

        Returns:
            SSE-formatted string ready to send
        """
        json_data = self.model_dump_json()
        return f"event: {self.event_type.value}\ndata: {json_data}\n\n"

    @classmethod
    def chunk(
        cls,
        request_id: UUID,
        content: str,
        sequence: int | None = None,
    ) -> "StreamEvent":
        """Create a chunk event."""
        return cls(
            event_type=EventType.CHUNK,
            request_id=request_id,
            sequence=sequence,
            data=ChunkData(content=content),
        )

    @classmethod
    def status(
        cls,
        request_id: UUID,
        status: StreamStatus,
        message: str | None = None,
    ) -> "StreamEvent":
        """Create a status event."""
        return cls(
            event_type=EventType.STATUS,
            request_id=request_id,
            data=StatusData(status=status, message=message),
        )

    @classmethod
    def tool_start(
        cls,
        request_id: UUID,
        tool_name: str,
        tool_args: dict[str, Any] | None = None,
        tool_id: str | None = None,
    ) -> "StreamEvent":
        """Create a tool call start event."""
        return cls(
            event_type=EventType.TOOL_CALL_START,
            request_id=request_id,
            data=ToolCallStartData(
                tool_name=tool_name,
                tool_args=tool_args,
                tool_id=tool_id,
            ),
        )

    @classmethod
    def tool_end(
        cls,
        request_id: UUID,
        tool_name: str,
        success: bool,
        tool_id: str | None = None,
        result_summary: str | None = None,
        duration_ms: int | None = None,
    ) -> "StreamEvent":
        """Create a tool call end event."""
        return cls(
            event_type=EventType.TOOL_CALL_END,
            request_id=request_id,
            data=ToolCallEndData(
                tool_name=tool_name,
                tool_id=tool_id,
                success=success,
                result_summary=result_summary,
                duration_ms=duration_ms,
            ),
        )

    @classmethod
    def handoff(
        cls,
        request_id: UUID,
        from_agent: AgentType,
        to_agent: AgentType,
        reason: str,
    ) -> "StreamEvent":
        """Create a handoff event."""
        return cls(
            event_type=EventType.HANDOFF,
            request_id=request_id,
            data=HandoffData(
                from_agent=from_agent,
                to_agent=to_agent,
                reason=reason,
            ),
        )

    @classmethod
    def error(
        cls,
        request_id: UUID,
        error_code: str,
        message: str,
        recoverable: bool = False,
    ) -> "StreamEvent":
        """Create an error event."""
        return cls(
            event_type=EventType.ERROR,
            request_id=request_id,
            data=ErrorData(
                error_code=error_code,
                message=message,
                recoverable=recoverable,
            ),
        )

    @classmethod
    def start(
        cls,
        request_id: UUID,
        agent_type: AgentType,
        estimated_duration_ms: int | None = None,
    ) -> "StreamEvent":
        """Create a start event."""
        return cls(
            event_type=EventType.START,
            request_id=request_id,
            data=StartData(
                agent_type=agent_type,
                estimated_duration_ms=estimated_duration_ms,
            ),
        )

    @classmethod
    def end(
        cls,
        request_id: UUID,
        agent_type: AgentType,
        success: bool,
        duration_ms: int | None = None,
        token_count: int | None = None,
    ) -> "StreamEvent":
        """Create an end event."""
        return cls(
            event_type=EventType.END,
            request_id=request_id,
            data=EndData(
                agent_type=agent_type,
                success=success,
                duration_ms=duration_ms,
                token_count=token_count,
            ),
        )
