use std::fmt;

use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use serde_json::Value;

use super::tool::ToolCall;

/// Wrapper for `DateTime<Utc>` that produces `time::OffsetDateTime`-compatible
/// `Debug` output: `2025-12-09 12:51:48.0 +00:00:00`.
#[derive(Clone, Copy, PartialEq, Eq, Deserialize)]
#[serde(transparent)]
pub struct OffsetDateTimeFmt(#[serde(with = "chrono::serde::ts_seconds")] pub DateTime<Utc>);

impl fmt::Debug for OffsetDateTimeFmt {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let nanos = self.0.timestamp_subsec_nanos();
        if nanos == 0 {
            write!(f, "{} +00:00:00", self.0.format("%Y-%m-%d %H:%M:%S.0"))
        } else {
            let full = self.0.format("%Y-%m-%d %H:%M:%S.%9f").to_string();
            write!(f, "{} +00:00:00", full.trim_end_matches('0'))
        }
    }
}

/// A chat completion response.
#[derive(Debug, Deserialize)]
pub struct ChatCompletion {
    /// The ID of the response.
    pub id: String,

    /// The name of the provider serving the response.
    ///
    /// This can differ from the model provider, e.g. if the model provider is
    /// Anthropic, but the provider server is Amazon AWS.
    pub provider: String,

    /// A list of "choices" made by the model in response to the prompt.
    ///
    /// Open AI supports requesting multiple "choices" (responses) from the
    /// model, but this is not currently supported by the Openrouter API.
    ///
    /// See: <https://github.com/OpenRouterTeam/openrouter-runner/issues/99>
    pub choices: Vec<Choice>,

    /// The time the response was created.
    pub created: OffsetDateTimeFmt,

    /// The model used to generate the response.
    pub model: String,

    /// The object returned by the model (differs for streaming and
    /// non-streaming mode).
    pub object: ResponseObject,

    /// This fingerprint represents the backend configuration that the model
    /// runs with.
    ///
    /// Can be used in conjunction with the seed request parameter to understand
    /// when backend changes have been made that might impact determinism.
    ///
    /// Only present if the provider supports it.
    pub system_fingerprint: Option<String>,

    /// Usage statistics for the completion request.
    pub usage: Option<Usage>,
}

#[derive(Debug, Clone, Deserialize)]
pub struct ChatCompletionError {
    pub error: ErrorResponse,

    #[serde(flatten)]
    _other: serde_json::Value,
}

#[derive(Debug, Clone, Deserialize)]
#[serde(untagged, variant_identifier)]
pub enum ResponseObject {
    #[serde(rename = "chat.completion")]
    ChatCompletion,
    #[serde(rename = "chat.completion.chunk")]
    ChatCompletionChunk,
}

#[derive(Debug, Clone, PartialEq, Deserialize)]
#[serde(untagged)]
pub enum Choice {
    NonStreaming(NonStreamingChoice),
    Streaming(StreamingChoice),
    NonChat(NonChatChoice),
}

impl Choice {
    #[must_use]
    pub fn content(&self) -> Option<&str> {
        match self {
            Self::NonStreaming(choice) => choice.message.content.as_deref(),
            Self::Streaming(choice) => choice.delta.content.as_deref(),
            Self::NonChat(choice) => Some(&choice.text),
        }
    }

    #[must_use]
    pub fn reasoning(&self) -> Option<&str> {
        match self {
            Self::NonStreaming(choice) => choice.message.reasoning.as_deref(),
            Self::Streaming(choice) => choice.delta.reasoning.as_deref(),
            Self::NonChat(_) => None,
        }
    }
}

#[derive(Debug, Clone, PartialEq, Deserialize)]
pub struct NonStreamingChoice {
    pub finish_reason: FinishReason,
    pub native_finish_reason: String,
    pub message: NonStreamingMessage,
    pub error: Option<ErrorResponse>,

    // TODO: figure out what this is used for.
    pub index: usize,
}

/// The reason why the assistant stopped generating tokens.
#[derive(Debug, Clone, Copy, PartialEq, Deserialize)]
#[serde(rename = "snake_case", rename_all = "snake_case")]
pub enum FinishReason {
    /// The assistant has finished requesting a tool call execution.
    ToolCalls,

    /// The assistant has stopped generating tokens.
    Stop,

    /// The assistant has reached the maximum length of accepted tokens.
    Length,

    /// The assistant has filtered out the content due to a flag from content
    /// filters.
    ContentFilter,

    /// The assistant encountered an error generating the response.
    Error,

    /// Undefined/unknown finish reason.
    #[serde(other)]
    Unknown,
}

impl FinishReason {
    #[must_use]
    pub fn as_str(&self) -> &'static str {
        match self {
            Self::ToolCalls => "tool_calls",
            Self::Stop => "stop",
            Self::Length => "length",
            Self::ContentFilter => "content_filter",
            Self::Error => "error",
            Self::Unknown => "unknown",
        }
    }
}

#[derive(Debug, Clone, PartialEq, Deserialize)]
pub struct NonStreamingMessage {
    pub role: String,
    pub content: Option<String>,
    pub reasoning: Option<String>,
    #[serde(default)]
    pub reasoning_details: Vec<ReasoningDetails>,
    #[serde(default)]
    pub tool_calls: Vec<ToolCall>,
}

#[derive(Debug, Clone, PartialEq, Deserialize)]
pub struct StreamingChoice {
    /// Similar to [`NonStreamingChoice::finish_reason`], but can be `None` if
    /// the stream is not finished.
    pub finish_reason: Option<FinishReason>,

    /// Similar to [`NonStreamingChoice::native_finish_reason`], but can be
    /// `None` if the stream is not finished.
    pub native_finish_reason: Option<String>,
    pub delta: StreamingDelta,
    pub error: Option<ErrorResponse>,
}

#[derive(Debug, Clone, PartialEq, Deserialize)]
pub struct StreamingDelta {
    pub role: Option<String>,
    pub content: Option<String>,
    pub reasoning: Option<String>,
    #[serde(default)]
    pub reasoning_details: Vec<ReasoningDetails>,
    #[serde(default)]
    pub tool_calls: Vec<ToolCall>,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ReasoningDetails {
    #[serde(default)]
    pub id: Option<String>,
    #[serde(default)]
    pub format: Option<ReasoningDetailsFormat>,
    #[serde(default)]
    pub index: Option<usize>,
    #[serde(flatten)]
    pub kind: ReasoningDetailsKind,
}

mod strings {
    crate::named_unit_variant!("openai-responses-v1", openai_responses_v1);
    crate::named_unit_variant!("xai-responses-v1", xai_responses_v1);
    crate::named_unit_variant!("anthropic-claude-v1", anthropic_claude_v1);
    crate::named_unit_variant!("google-gemini-v1", google_gemini_v1);
    crate::named_unit_variant!(unknown);
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum ReasoningDetailsFormat {
    #[serde(with = "strings::openai_responses_v1")]
    OpenaiResponsesV1,
    #[serde(with = "strings::xai_responses_v1")]
    XaiResponsesV1,
    #[serde(with = "strings::anthropic_claude_v1")]
    AnthropicClaudeV1,
    #[serde(with = "strings::google_gemini_v1")]
    GoogleGeminiV1,
    #[serde(with = "strings::unknown")]
    Unknown,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum ReasoningDetailsKind {
    #[serde(rename = "reasoning.summary")]
    Summary { summary: String },

    #[serde(rename = "reasoning.encrypted")]
    Encrypted { data: String },

    #[serde(rename = "reasoning.text")]
    Text {
        text: Option<String>,
        signature: Option<String>,
    },

    #[serde(untagged)]
    Unknown(serde_json::Value),
}

#[derive(Debug, Clone, PartialEq, Deserialize)]
pub struct NonChatChoice {
    pub finish_reason: FinishReason,
    pub text: String,
    pub error: Option<ErrorResponse>,
}

#[derive(Debug, Clone, PartialEq, Deserialize)]
pub struct CompletionChunk {
    pub id: String,
    pub choices: Vec<StreamingChoice>,
    pub usage: Option<Usage>,
}

#[derive(Debug, Clone, PartialEq, Deserialize)]
pub struct Usage {
    pub completion_tokens: u32,
    pub completion_tokens_details: Option<CompletionTokensDetails>,
    pub cost: Option<f32>,
    pub prompt_tokens: u32,
    pub prompt_tokens_details: Option<PromptTokensDetails>,
    pub total_tokens: u32,
}

#[derive(Debug, Clone, PartialEq, Deserialize)]
pub struct PromptTokensDetails {
    pub cached_tokens: u32,
}

#[derive(Debug, Clone, PartialEq, Deserialize)]
pub struct CompletionTokensDetails {
    pub reasoning_tokens: u32,
}

#[derive(Debug, Clone, PartialEq, Deserialize)]
pub struct ErrorResponse {
    pub code: u16,
    pub message: String,
    pub metadata: Option<ErrorMetadata>,
}

#[derive(Debug, Clone, PartialEq, Deserialize)]
#[serde(untagged)]
pub enum ErrorMetadata {
    /// Model provider flagged the input for moderation.
    Moderation {
        /// Why the input was flagged.
        reasons: Vec<String>,

        /// The text segment that was flagged, limited to 100 characters.
        /// If the flagged input is longer than 100 characters, it will be
        /// truncated in the middle and replaced with ...
        flagged_input: String,

        /// The name of the provider that requested moderation.
        provider_name: Option<String>,

        /// The slug of the model that was used.
        model_slug: String,
    },

    /// If the model provider encounters an error, the error will contain
    /// information about the issue.
    Provider {
        /// The name of the provider that encountered the error.
        provider_name: Option<String>,

        /// The raw error from the provider.
        raw: Value,
    },

    /// Catch-all for metadata shapes we don't recognize.
    Other(Value),
}

/// API error codes returned by the Openrouter API.
#[derive(Debug, Clone, PartialEq)]
pub enum ErrorCode {
    BadRequest,
    InvalidCredentials,
    InsufficientCredits,
    ModerationInputFlagged,
    RequestTimeout,
    RateLimited,
    InvalidModelResponse,
    UnmatchedRoutingRequirements,
    Other(usize),
}

impl<'de> Deserialize<'de> for ErrorCode {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        usize::deserialize(deserializer).map(Into::into)
    }
}

impl From<usize> for ErrorCode {
    fn from(code: usize) -> Self {
        match code {
            400 => ErrorCode::BadRequest,
            401 => ErrorCode::InvalidCredentials,
            402 => ErrorCode::InsufficientCredits,
            403 => ErrorCode::ModerationInputFlagged,
            408 => ErrorCode::RequestTimeout,
            429 => ErrorCode::RateLimited,
            502 => ErrorCode::InvalidModelResponse,
            503 => ErrorCode::UnmatchedRoutingRequirements,
            n => ErrorCode::Other(n),
        }
    }
}

#[derive(Debug, Deserialize)]
pub struct ModelsResponse {
    pub data: Vec<Model>,
}

#[derive(Debug, Deserialize)]
pub struct Model {
    pub id: String,
    pub name: String,
    pub created: OffsetDateTimeFmt,
    pub context_length: u32,
}
