//! Request/response types for the OpenAI-compatible API.
//!
//! All response shapes strictly match the official OpenAI Responses API spec:
//! https://platform.openai.com/docs/api-reference/responses

use serde::{Deserialize, Serialize};

// ─── Request types ───────────────────────────────────────────────────────────

#[derive(Deserialize)]
pub(crate) struct ResponsesRequest {
    pub model: Option<String>,
    pub input: serde_json::Value,
    #[serde(default)]
    pub instructions: Option<String>,
    #[serde(default)]
    pub stream: bool,
    #[serde(default = "default_timeout")]
    pub timeout: u64,
    #[allow(dead_code)]
    pub conversation: Option<serde_json::Value>,
    #[serde(default = "default_true")]
    pub store: bool,
    #[serde(default)]
    pub temperature: Option<f64>,
    #[serde(default)]
    pub top_p: Option<f64>,
    #[serde(default)]
    pub max_output_tokens: Option<u64>,
    #[serde(default)]
    pub previous_response_id: Option<String>,
    #[serde(default)]
    pub metadata: Option<serde_json::Value>,
    #[serde(default)]
    pub user: Option<String>,
    /// Tool definitions (OpenAI format).
    #[serde(default)]
    pub tools: Option<Vec<serde_json::Value>>,
    /// Tool choice: "auto", "required", "none", or {"type":"function","function":{"name":"X"}}.
    #[serde(default)]
    pub tool_choice: Option<serde_json::Value>,
    /// Reasoning effort — forwarded as thinkingConfig.thinkingLevel to Google.
    /// Values: "low", "medium", "high".
    #[serde(default)]
    pub reasoning_effort: Option<String>,
    /// Maximum number of tool calls allowed per response.
    #[serde(default)]
    pub max_tool_calls: Option<u32>,
    /// Text output format — {format: {type: "json_schema", name: "...", schema: {...}}}.
    /// When json_schema, injects responseMimeType + responseSchema via MITM.
    #[serde(default)]
    pub text: Option<serde_json::Value>,
}

/// Stream options for Chat Completions (controls usage emission in final chunk).
#[derive(Deserialize, Serialize, Default)]
pub(crate) struct StreamOptions {
    /// When true, emit a final chunk with usage statistics before [DONE].
    #[serde(default)]
    pub include_usage: bool,
}

/// Chat Completions request (OpenAI-compatible).
#[derive(Deserialize, Serialize)]
pub(crate) struct CompletionRequest {
    pub model: Option<String>,
    pub messages: Vec<CompletionMessage>,
    #[serde(default)]
    pub stream: bool,
    #[serde(default = "default_timeout")]
    pub timeout: u64,
    /// OpenAI-format tool definitions
    pub tools: Option<Vec<serde_json::Value>>,
    /// Tool choice: "auto", "none", "required", or {"type":"function","function":{"name":"..."}}
    pub tool_choice: Option<serde_json::Value>,
    /// Stream options — controls whether usage is emitted in the final streaming chunk.
    #[serde(default)]
    pub stream_options: Option<StreamOptions>,
    /// Sampling temperature — forwarded to Google via MITM generationConfig injection.
    #[serde(default)]
    pub temperature: Option<f64>,
    /// Nucleus sampling — forwarded to Google via MITM.
    #[serde(default)]
    pub top_p: Option<f64>,
    /// Max tokens — forwarded to Google as maxOutputTokens via MITM.
    #[serde(default)]
    pub max_tokens: Option<u64>,
    /// Max completion tokens — forwarded to Google as maxOutputTokens via MITM.
    #[serde(default)]
    pub max_completion_tokens: Option<u64>,
    /// User identifier -- accepted, not used.
    #[serde(default)]
    #[allow(dead_code)]
    pub user: Option<String>,
    /// Frequency penalty — forwarded to Google via MITM.
    #[serde(default)]
    pub frequency_penalty: Option<f64>,
    /// Presence penalty — forwarded to Google via MITM.
    #[serde(default)]
    pub presence_penalty: Option<f64>,
    /// Reasoning effort — forwarded as thinkingConfig.thinkingLevel to Google.
    /// Values: "low", "medium", "high".
    #[serde(default)]
    pub reasoning_effort: Option<String>,
    /// Stop sequences — forwarded as generationConfig.stopSequences to Google.
    /// Up to 4 sequences where the API will stop generating further tokens.
    #[serde(default)]
    pub stop: Option<StopSequence>,
    /// Response format — {"type": "json_object"} or {"type": "json_schema", "json_schema": {...}}.
    /// Injected as responseMimeType (+ responseSchema) in generationConfig via MITM.
    #[serde(default)]
    pub response_format: Option<ResponseFormat>,
    /// Session/conversation ID for multi-turn reuse (custom extension).
    #[serde(default)]
    pub conversation: Option<serde_json::Value>,
    /// Metadata -- accepted and ignored (no upstream equivalent).
    #[serde(default)]
    #[allow(dead_code)]
    pub metadata: Option<serde_json::Value>,
    /// Number of completions to generate. Each uses a separate cascade (costs N× quota).
    /// Defaults to 1. Only supported in sync mode; streaming always uses n=1.
    #[serde(default = "default_n")]
    pub n: u32,
    /// Enable Google Search grounding. When true, the model can search the web
    /// and responses include grounding metadata with search results/citations.
    #[serde(default)]
    pub web_search: bool,
}

fn default_n() -> u32 {
    1
}

/// Stop sequence can be a single string or array of strings (OpenAI accepts both).
#[derive(Deserialize, Serialize, Clone)]
#[serde(untagged)]
pub(crate) enum StopSequence {
    Single(String),
    Multiple(Vec<String>),
}

impl StopSequence {
    pub fn into_vec(self) -> Vec<String> {
        match self {
            StopSequence::Single(s) => vec![s],
            StopSequence::Multiple(v) => v,
        }
    }
}

/// Response format for structured output.
/// Supports:
/// - `{"type": "json_object"}` — JSON mode (responseMimeType only)
/// - `{"type": "json_schema", "json_schema": {"name": "...", "schema": {...}}}` — structured output (responseMimeType + responseSchema)
/// - `{"type": "text"}` — plain text (default, no injection)
#[derive(Deserialize, Serialize, Clone)]
pub(crate) struct ResponseFormat {
    #[serde(rename = "type")]
    pub format_type: String,
    /// JSON schema definition for structured output.
    /// Only used when format_type is "json_schema".
    #[serde(default)]
    pub json_schema: Option<JsonSchemaFormat>,
}

/// JSON schema structured output format.
#[derive(Deserialize, Serialize, Clone)]
pub(crate) struct JsonSchemaFormat {
    /// Schema name (for client identification).
    #[serde(default)]
    #[allow(dead_code)]
    pub name: Option<String>,
    /// The actual JSON schema object — forwarded as Gemini's responseSchema.
    #[serde(default)]
    pub schema: Option<serde_json::Value>,
    /// Whether to enable strict schema adherence.
    #[serde(default)]
    #[allow(dead_code)]
    pub strict: Option<bool>,
}

#[derive(Deserialize, Serialize)]
pub(crate) struct CompletionMessage {
    pub role: String,
    #[serde(default)]
    pub content: serde_json::Value,
    /// Tool calls made by the assistant (for assistant messages)
    pub tool_calls: Option<Vec<serde_json::Value>>,
    /// Tool call ID this message is responding to (for tool messages)
    pub tool_call_id: Option<String>,
}

use super::util::default_timeout;

fn default_true() -> bool {
    true
}

// ─── Response types (official OpenAI Responses API shape) ────────────────────

/// Top-level Response object — matches OpenAI exactly.
#[derive(Serialize, Clone)]
pub(crate) struct ResponsesResponse {
    pub id: String,
    pub object: &'static str,
    pub created_at: u64,
    pub status: &'static str,
    #[serde(serialize_with = "serialize_option_u64")]
    pub completed_at: Option<u64>,
    pub error: Option<serde_json::Value>,
    pub incomplete_details: Option<serde_json::Value>,
    pub instructions: Option<String>,
    #[serde(serialize_with = "serialize_option_u64")]
    pub max_output_tokens: Option<u64>,
    pub model: String,
    /// Output items — can contain both `"reasoning"` and `"message"` items.
    /// Uses serde_json::Value because reasoning and message items have different shapes.
    pub output: Vec<serde_json::Value>,
    pub parallel_tool_calls: bool,
    pub previous_response_id: Option<String>,
    pub reasoning: Reasoning,
    pub store: bool,
    pub temperature: f64,
    pub text: TextFormat,
    pub tool_choice: serde_json::Value,
    pub tools: Vec<serde_json::Value>,
    pub top_p: f64,
    pub truncation: &'static str,
    pub usage: Option<Usage>,
    pub user: Option<String>,
    pub metadata: serde_json::Value,
    /// Proxy extension: opaque thinking verification signature.
    /// Required for multi-turn chaining with thinking models.
    /// Not part of the official OpenAI spec — internal proxy data.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub thinking_signature: Option<String>,
}

#[derive(Serialize, Clone)]
pub(crate) struct Usage {
    pub input_tokens: u64,
    pub input_tokens_details: InputTokensDetails,
    pub output_tokens: u64,
    pub output_tokens_details: OutputTokensDetails,
    pub total_tokens: u64,
}

#[derive(Serialize, Clone)]
pub(crate) struct InputTokensDetails {
    pub cached_tokens: u64,
}

#[derive(Serialize, Clone)]
pub(crate) struct OutputTokensDetails {
    pub reasoning_tokens: u64,
}

#[derive(Serialize, Clone, Default)]
pub(crate) struct Reasoning {
    pub effort: Option<String>,
    pub summary: Option<String>,
}

#[derive(Serialize, Clone)]
pub(crate) struct TextFormat {
    pub format: TextFormatInner,
}

#[derive(Serialize, Clone)]
pub(crate) struct TextFormatInner {
    #[serde(rename = "type")]
    pub format_type: String,
    /// JSON schema — present when format_type is "json_schema".
    #[serde(skip_serializing_if = "Option::is_none")]
    pub name: Option<String>,
    /// The actual schema object.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub schema: Option<serde_json::Value>,
    /// Whether strict mode was requested.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub strict: Option<bool>,
}

impl Usage {
    /// Estimate token counts from actual text.
    /// Uses ~4 chars/token heuristic (standard GPT tokenizer average).
    pub fn estimate(input_text: &str, output_text: &str) -> Self {
        let input_tokens = (input_text.len() as u64).div_ceil(4);
        let output_tokens = (output_text.len() as u64).div_ceil(4);
        Self {
            input_tokens,
            input_tokens_details: InputTokensDetails { cached_tokens: 0 },
            output_tokens,
            output_tokens_details: OutputTokensDetails {
                reasoning_tokens: 0,
            },
            total_tokens: input_tokens + output_tokens,
        }
    }
}

impl Default for Usage {
    fn default() -> Self {
        Self {
            input_tokens: 0,
            input_tokens_details: InputTokensDetails { cached_tokens: 0 },
            output_tokens: 0,
            output_tokens_details: OutputTokensDetails {
                reasoning_tokens: 0,
            },
            total_tokens: 0,
        }
    }
}

impl Default for TextFormat {
    fn default() -> Self {
        Self {
            format: TextFormatInner {
                format_type: "text".to_string(),
                name: None,
                schema: None,
                strict: None,
            },
        }
    }
}

// ─── Output item builders ────────────────────────────────────────────────────

/// Build a reasoning output item (goes BEFORE the message item in `output`).
/// Matches: https://platform.openai.com/docs/api-reference/responses
pub fn build_reasoning_output(thinking_text: &str) -> serde_json::Value {
    serde_json::json!({
        "id": format!("rs_{}", uuid::Uuid::new_v4().to_string().replace('-', "")),
        "type": "reasoning",
        "summary": [{
            "type": "summary_text",
            "text": thinking_text,
        }],
    })
}

/// Build a message output item.
pub fn build_message_output(msg_id: &str, text: &str) -> serde_json::Value {
    serde_json::json!({
        "type": "message",
        "id": msg_id,
        "status": "completed",
        "role": "assistant",
        "content": [{
            "type": "output_text",
            "text": text,
            "annotations": [],
        }],
    })
}

/// Build an in-progress message output item (no content yet, for streaming).
pub fn build_message_output_in_progress(msg_id: &str) -> serde_json::Value {
    serde_json::json!({
        "type": "message",
        "id": msg_id,
        "status": "in_progress",
        "role": "assistant",
        "content": [],
    })
}

/// Build a function_call output item (OpenAI Responses API format).
pub fn build_function_call_output(call_id: &str, name: &str, arguments: &str) -> serde_json::Value {
    serde_json::json!({
        "type": "function_call",
        "id": call_id,
        "call_id": call_id,
        "name": name,
        "arguments": arguments,
        "status": "completed",
    })
}

// ─── Helpers ─────────────────────────────────────────────────────────────────

/// Serialize Option<u64> as either the number or JSON null (not omitted).
fn serialize_option_u64<S>(val: &Option<u64>, s: S) -> Result<S::Ok, S::Error>
where
    S: serde::Serializer,
{
    match val {
        Some(v) => s.serialize_u64(*v),
        None => s.serialize_none(),
    }
}

// ─── Shared types ────────────────────────────────────────────────────────────

#[derive(Deserialize)]
pub(crate) struct TokenRequest {
    pub token: String,
}

#[derive(Serialize)]
pub(crate) struct ErrorResponse {
    pub error: ErrorDetail,
}

#[derive(Serialize)]
pub(crate) struct ErrorDetail {
    pub message: String,
    #[serde(rename = "type")]
    pub error_type: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub code: Option<u16>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub param: Option<String>,
}
