from __future__ import annotations

import secrets
import time
from enum import Enum
from typing import Literal

from pydantic import BaseModel, Field

from orchard.server.models.responses.format import ResponseFormat
from orchard.server.models.responses.tools import (
    Function,
    FunctionID,
    ToolUseMode,
)
from orchard.server.models.tools import generate_tool_call_id

# --- Constants ---
RESPONSE_ID_PREFIX = "resp_"
RESPONSE_OBJECT = "response"
MESSAGE_ID_PREFIX = "msg_"
MESSAGE_OBJECT = "message"
OUTPUT_TEXT_OBJECT = "output_text"
FUNCTION_CALL_ID_PREFIX = "fc_"


def generate_message_id(prefix: str = MESSAGE_ID_PREFIX) -> str:
    random_part = secrets.token_urlsafe(22)
    return f"{prefix}{random_part}"


def generate_response_id(prefix: str = RESPONSE_ID_PREFIX) -> str:
    random_part = secrets.token_urlsafe(22)
    return f"{prefix}{random_part}"


def generate_function_call_id(prefix: str = FUNCTION_CALL_ID_PREFIX) -> str:
    random_part = secrets.token_urlsafe(22)
    return f"{prefix}{random_part}"


def get_current_timestamp() -> int:
    return int(time.time())


class OutputTextContent(BaseModel):
    """Represents text content within an output message."""

    type: Literal["output_text"] = OUTPUT_TEXT_OBJECT
    text: str = Field(description="The generated text content.")
    annotations: list = Field(
        default_factory=list,
        description="Annotations for the text content.",
    )
    logprobs: list = Field(
        default_factory=list,
        description="Log probability information for the text.",
    )


class OutputStatus(Enum):
    """Represents the status of an output item."""

    COMPLETED = "completed"
    INCOMPLETE = "incomplete"
    IN_PROGRESS = "in_progress"
    FAILED = "failed"


class OutputMessage(BaseModel):
    """Represents an assistant message output item."""

    type: Literal["message"] = "message"
    id: str = Field(
        default_factory=generate_message_id,
        description="Unique message identifier.",
    )
    status: OutputStatus = Field(
        default=OutputStatus.COMPLETED,
        description="The status of the message.",
    )
    role: Literal["assistant"] = "assistant"
    content: list[OutputTextContent] = Field(
        default_factory=list,
        description="Content parts generated by the assistant.",
    )


class OutputFunctionCall(BaseModel):
    """Represents a function call output item."""

    type: Literal["function_call"] = "function_call"
    id: str = Field(
        default_factory=generate_function_call_id,
        description="Unique function call item identifier.",
    )
    call_id: str = Field(
        default_factory=generate_tool_call_id,
        description="Identifier used to match function call with its output.",
    )
    name: str = Field(description="The name of the function to call.")
    arguments: str = Field(description="JSON-encoded arguments for the function.")
    status: OutputStatus = Field(
        default=OutputStatus.COMPLETED,
        description="The status of the function call.",
    )


class ReasoningSummaryTextContent(BaseModel):
    """Represents a summary text within reasoning output."""

    type: Literal["summary_text"] = "summary_text"
    text: str = Field(description="Summary text of the reasoning.")


class ReasoningContent(BaseModel):
    """Represents reasoning text content within reasoning output."""

    type: Literal["reasoning_text"] = "reasoning_text"
    text: str = Field(description="The reasoning/thinking content.")


class OutputReasoning(BaseModel):
    """Represents reasoning output item."""

    type: Literal["reasoning"] = "reasoning"
    id: str = Field(
        default_factory=lambda: generate_message_id("reasoning_"),
        description="Unique reasoning item identifier.",
    )
    status: OutputStatus = Field(
        default=OutputStatus.COMPLETED,
        description="The status of the reasoning.",
    )
    summary: list[ReasoningSummaryTextContent] = Field(
        default_factory=list,
        description="Summary of the reasoning.",
    )
    content: list[ReasoningContent] = Field(
        default_factory=list,
        description="The full reasoning content.",
    )
    encrypted_content: str | None = Field(
        default=None,
        description="Encrypted reasoning for continuation.",
    )


OutputItem = OutputMessage | OutputFunctionCall | OutputReasoning


class IncompleteDetails(BaseModel):
    """Details about why a response was incomplete."""

    reason: str = Field(
        description="The reason the response was incomplete (e.g., 'max_output_tokens', 'content_filter').",
    )


class ResponseError(BaseModel):
    """Structured error information when response fails."""

    code: str = Field(
        description="Error code (e.g., 'server_error', 'invalid_request')."
    )
    message: str = Field(description="Human-readable error message.")


class InputTokensDetails(BaseModel):
    """Details about input token usage."""

    cached_tokens: int = Field(
        default=0,
        description="Number of tokens served from cache.",
    )


class OutputTokensDetails(BaseModel):
    """Details about output token usage."""

    reasoning_tokens: int = Field(
        default=0,
        description="Number of tokens used for reasoning/thinking.",
    )


class ResponseUsage(BaseModel):
    """Provides token usage statistics for the chat completion request."""

    input_tokens: int = Field(
        description="The number of tokens constituting the input prompt(s)."
    )
    output_tokens: int = Field(
        description="The total number of tokens generated across all completion choices."
    )
    total_tokens: int = Field(
        description="The sum of `input_tokens` and `output_tokens`."
    )
    input_tokens_details: InputTokensDetails | None = Field(
        default=None,
        description="Breakdown of input token usage.",
    )
    output_tokens_details: OutputTokensDetails | None = Field(
        default=None,
        description="Breakdown of output token usage.",
    )


class ResponseObject(BaseModel):
    """Defines the response schema for the /v1/responses endpoint."""

    id: str = Field(
        default_factory=generate_response_id,
        description="Unique identifier for the response.",
    )
    object: Literal["response"] = RESPONSE_OBJECT
    created_at: int = Field(
        default_factory=get_current_timestamp,
        description="Unix timestamp of response creation.",
    )
    completed_at: int | None = Field(
        default=None,
        description="Unix timestamp when response generation completed.",
    )
    status: OutputStatus = Field(
        default=OutputStatus.COMPLETED,
        description="The status of the response.",
    )
    incomplete_details: IncompleteDetails | None = Field(
        default=None,
        description="Details about why the response was incomplete, if applicable.",
    )
    error: ResponseError | None = Field(
        default=None,
        description="Error information if the response failed.",
    )
    model: str = Field(description="Model ID used for the response.")
    output: list[OutputItem] = Field(
        default_factory=list,
        description="List of output items (messages, function calls, reasoning).",
    )
    usage: ResponseUsage | None = Field(
        default=None,
        description="Usage statistics for the response request.",
    )
    # Echoed configuration
    metadata: dict[str, str] | None = Field(
        default=None,
        description="Key-value pairs attached to the response.",
    )
    parallel_tool_calls: bool = Field(
        default=False,
        description="Whether parallel tool calls were enabled.",
    )
    temperature: float | None = Field(
        default=None,
        description="Sampling temperature used.",
    )
    top_p: float | None = Field(
        default=None,
        description="Nucleus sampling threshold used.",
    )
    presence_penalty: float | None = Field(
        default=None,
        description="Presence penalty used.",
    )
    frequency_penalty: float | None = Field(
        default=None,
        description="Frequency penalty used.",
    )
    top_k: int | None = Field(
        default=None,
        description="Top-k value used.",
    )
    min_p: float | None = Field(
        default=None,
        description="Min-p value used.",
    )
    truncation: str | None = Field(
        default=None,
        description="How input was truncated.",
    )
    instructions: str | None = Field(
        default=None,
        description="System/developer instructions used.",
    )
    max_output_tokens: int | None = Field(
        default=None,
        description="Maximum output tokens allowed.",
    )
    top_logprobs: int | None = Field(
        default=None,
        description="Number of top log probabilities returned.",
    )
    tool_choice: ToolUseMode | FunctionID = Field(
        default=ToolUseMode.AUTO,
        description="Tool choice mode used.",
    )
    tools: list[Function] = Field(
        default_factory=list,
        description="Tools that were available.",
    )
    max_tool_calls: int | None = Field(
        default=None,
        description="Maximum number of tool calls allowed.",
    )
    text: ResponseFormat | None = Field(
        default=None,
        description="Response format used.",
    )
    reasoning: dict | None = Field(
        default=None,
        description="Reasoning configuration used.",
    )

    @property
    def output_text(self) -> str:
        """Concatenate all assistant output text parts into a single string."""
        chunks: list[str] = []
        for item in self.output:
            if isinstance(item, OutputMessage):
                for part in item.content:
                    chunks.append(part.text)
        return "".join(chunks)

    @property
    def tool_calls(self) -> list[OutputFunctionCall]:
        """Return all function-call output items in this response."""
        return [
            item for item in self.output if isinstance(item, OutputFunctionCall)
        ]
