"""
Agent-specific types used by the agents package.
"""

from datetime import datetime
from typing import Any, Dict, List, Literal, Optional, Sequence, Union

from pydantic import BaseModel, ConfigDict, Field

from agentbyte.context import AgentContext, ToolApprovalRequest
from agentbyte.messages import Message, Usage
from agentbyte.types import ToolResult


class BaseEvent(BaseModel):
    """Base streaming/debug event emitted during agent execution."""

    model_config = ConfigDict(frozen=True)

    event_type: str = Field(..., description="Event type")
    source: str = Field(..., description="Event source")
    timestamp: datetime = Field(default_factory=datetime.now)
    details: Dict[str, Any] = Field(default_factory=dict)

    def __str__(self) -> str:
        """Returns a user-friendly string representation."""
        time_str = self.timestamp.strftime("%H:%M:%S")
        return f"[{self.source}] {time_str} | {self.event_type}"

    def __repr__(self) -> str:
        """Returns an unambiguous, developer-friendly representation."""
        class_name = self.__class__.__name__
        return f"{class_name}(event_type='{self.event_type}', source='{self.source}', timestamp='{self.timestamp}')"


class TaskStartEvent(BaseEvent):
    """Event emitted when task execution starts."""

    event_type: Literal["task_start"] = "task_start"
    task: str = Field(..., description="Task content")


class TaskCompleteEvent(BaseEvent):
    """Event emitted when task execution completes."""

    event_type: Literal["task_complete"] = "task_complete"
    result: str = Field(..., description="Final task result content")
    finish_reason: str = Field(..., description="Why task execution stopped")


class ModelCallEvent(BaseEvent):
    """Event emitted before model invocation."""

    event_type: Literal["model_call"] = "model_call"
    input_messages: Sequence[Message] = Field(
        ...,
        description="Messages sent to the model",
    )
    model: str = Field(..., description="Model name")


class ModelResponseEvent(BaseEvent):
    """Event emitted after model response."""

    event_type: Literal["model_response"] = "model_response"
    model: str = Field(..., description="Model name")
    response: str = Field(..., description="Model response content")
    has_tool_calls: bool = Field(
        default=False,
        description="Whether response contains tool calls",
    )


class ModelStreamChunkEvent(BaseEvent):
    """Event emitted for each streamed model chunk."""

    event_type: Literal["model_stream_chunk"] = "model_stream_chunk"
    chunk: str = Field(..., description="Streamed text chunk")
    is_final: bool = Field(default=False, description="Whether this is the final chunk")


class ToolCallEvent(BaseEvent):
    """Event emitted before tool execution."""

    event_type: Literal["tool_call"] = "tool_call"
    tool_name: str = Field(..., description="Tool name")
    parameters: Dict[str, Any] = Field(..., description="Tool call parameters")
    call_id: str = Field(..., description="Tool call identifier")


class ToolCallResponseEvent(BaseEvent):
    """Event emitted after tool execution."""

    event_type: Literal["tool_call_response"] = "tool_call_response"
    call_id: str = Field(..., description="Tool call identifier")
    result: Optional[ToolResult] = Field(
        default=None,
        description="Tool execution result",
    )


class ToolApprovalEvent(BaseEvent):
    """Event emitted when tool execution requires approval."""

    event_type: Literal["tool_approval"] = "tool_approval"
    approval_request: ToolApprovalRequest = Field(
        ...,
        description="Approval request details",
    )


class ToolValidationEvent(BaseEvent):
    """Event emitted after tool parameter validation."""

    event_type: Literal["tool_validation"] = "tool_validation"
    tool_name: str = Field(..., description="Tool name")
    is_valid: bool = Field(..., description="Whether parameters are valid")
    errors: Optional[List[str]] = Field(
        default=None,
        description="Validation errors, if any",
    )


class MemoryUpdateEvent(BaseEvent):
    """Event emitted when memory state changes."""

    event_type: Literal["memory_update"] = "memory_update"
    operation: str = Field(
        ...,
        description="Memory operation type: add, update, delete",
    )
    content_summary: str = Field(..., description="Summary of updated memory content")


class MemoryRetrievalEvent(BaseEvent):
    """Event emitted when memory content is retrieved."""

    event_type: Literal["memory_retrieval"] = "memory_retrieval"
    query: str = Field(..., description="Query used for retrieval")
    results_count: int = Field(..., description="Number of retrieved memory items")


class ErrorEvent(BaseEvent):
    """Event emitted for recoverable errors."""

    event_type: Literal["error"] = "error"
    error_message: str = Field(..., description="Description of the error")
    error_type: str = Field(..., description="Type/category of error")
    is_recoverable: bool = Field(
        default=True,
        description="Whether error can be recovered from",
    )


class FatalErrorEvent(BaseEvent):
    """Event emitted for unrecoverable errors that terminate execution."""

    event_type: Literal["fatal_error"] = "fatal_error"
    error_message: str = Field(..., description="Description of the fatal error")
    error_type: str = Field(..., description="Type/category of error")
    is_recoverable: bool = Field(
        default=False,
        description="Always false for fatal errors",
    )


class AgentResponse(BaseModel):
    """Final response payload for a completed agent run."""

    model_config = ConfigDict(frozen=False)

    context: AgentContext = Field(..., description="Final execution context")
    source: str = Field(..., description="Agent name")
    finish_reason: str = Field(
        ...,
        description="Why the agent stopped: stop, approval_needed, max_iterations, error, cancelled",
    )
    usage: Usage = Field(default_factory=Usage, description="Aggregated usage")
    timestamp: datetime = Field(
        default_factory=datetime.now,
        description="When the response was created",
    )

    @property
    def messages(self) -> List[Message]:
        """Shortcut accessor for context message history."""
        return self.context.messages

    @property
    def needs_approval(self) -> bool:
        """Check if response is waiting for approvals."""
        return self.context.waiting_for_approval

    @property
    def approval_requests(self) -> List[ToolApprovalRequest]:
        """Get pending approval requests."""
        return self.context.pending_approval_requests

    @property
    def final_content(self) -> str:
        """Final message content if available."""
        if not self.context.messages:
            return ""
        return self.context.messages[-1].content

    def __str__(self) -> str:
        """Returns a user-friendly string representation with messages and usage."""
        # Concat all message str representations
        messages_str = "\n".join(str(msg) for msg in self.messages)

        # Format duration
        duration_s = self.usage.duration_ms / 1000

        # Format tokens with k suffix for thousands
        tokens_in = (
            f"{self.usage.tokens_input/1000:.1f}k"
            if self.usage.tokens_input >= 1000
            else str(self.usage.tokens_input)
        )
        tokens_out = (
            f"{self.usage.tokens_output/1000:.1f}k"
            if self.usage.tokens_output >= 1000
            else str(self.usage.tokens_output)
        )

        # Format cost if available
        cost_str = (
            f", cost: ${self.usage.cost_estimate:.4f}"
            if self.usage.cost_estimate
            else ""
        )

        # Add approval status if needed
        if self.needs_approval:
            approval_str = f" | ⚠️ {len(self.approval_requests)} approvals needed"
        else:
            approval_str = f" | finish: {self.finish_reason}"

        usage_line = f"[usage] duration: {duration_s:.1f}s, tokens: in:{tokens_in}, out:{tokens_out}{cost_str}{approval_str}"

        return f"{messages_str}\n\n{usage_line}"

    def __repr__(self) -> str:
        """Returns an unambiguous, developer-friendly representation."""
        approval_info = (
            f", approvals_needed={len(self.approval_requests)}"
            if self.needs_approval
            else ""
        )
        return f"AgentResponse(source='{self.source}', messages={len(self.messages)}, finish_reason='{self.finish_reason}', usage={self.usage}{approval_info})"


# Union type for all agent events (useful for type hints)
AgentEvent = Union[
    TaskStartEvent,
    TaskCompleteEvent,
    ModelCallEvent,
    ModelResponseEvent,
    ModelStreamChunkEvent,
    ToolCallEvent,
    ToolCallResponseEvent,
    ToolApprovalEvent,
    ToolValidationEvent,
    MemoryUpdateEvent,
    MemoryRetrievalEvent,
    ErrorEvent,
    FatalErrorEvent,
]


__all__ = [
    "BaseEvent",
    "AgentEvent",
    "TaskStartEvent",
    "TaskCompleteEvent",
    "ModelCallEvent",
    "ModelResponseEvent",
    "ToolCallEvent",
    "ToolCallResponseEvent",
    "ToolApprovalEvent",
    "ToolValidationEvent",
    "MemoryUpdateEvent",
    "MemoryRetrievalEvent",
    "ModelStreamChunkEvent",
    "ErrorEvent",
    "FatalErrorEvent",
    "AgentResponse",
]
