//! Response object for Open Responses.
//!
//! The Response is the top-level object returned by the API,
//! containing output items, usage statistics, and metadata.

use serde::{Deserialize, Serialize};
use std::time::{SystemTime, UNIX_EPOCH};

use super::{OpenResponseError, OpenUsage, OutputItem};
use crate::llm::provider::ToolDefinition;

/// Unique identifier for a response.
pub type ResponseId = String;

/// Status of a response.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum ResponseStatus {
    /// Response is queued for processing.
    Queued,

    /// Response is currently being processed.
    #[default]
    InProgress,

    /// Response completed successfully.
    Completed,

    /// Response failed with an error.
    Failed,

    /// Response is incomplete (e.g., token limit reached).
    Incomplete,
}

impl ResponseStatus {
    /// Returns true if this is a terminal status.
    pub fn is_terminal(&self) -> bool {
        matches!(self, Self::Completed | Self::Failed | Self::Incomplete)
    }
}

impl std::fmt::Display for ResponseStatus {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Queued => write!(f, "queued"),
            Self::InProgress => write!(f, "in_progress"),
            Self::Completed => write!(f, "completed"),
            Self::Failed => write!(f, "failed"),
            Self::Incomplete => write!(f, "incomplete"),
        }
    }
}

/// Details about why a response was incomplete.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct IncompleteDetails {
    /// The reason the response could not be completed.
    pub reason: IncompleteReason,
}

/// Reasons why a response may be incomplete.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum IncompleteReason {
    /// Maximum output tokens reached.
    MaxOutputTokens,

    /// Maximum tool calls reached.
    MaxToolCalls,

    /// Content filter triggered.
    ContentFilter,

    /// User cancelled the request.
    Cancelled,
}

/// The main response object per the Open Responses specification.
///
/// This is the top-level object returned by the API, containing all
/// output items, usage statistics, and metadata about the response.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Response {
    /// Unique ID of the response.
    pub id: ResponseId,

    /// Object type, always "response".
    pub object: String,

    /// Unix timestamp (seconds) when the response was created.
    pub created_at: u64,

    /// Unix timestamp (seconds) when the response was completed, if applicable.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub completed_at: Option<u64>,

    /// Current status of the response.
    pub status: ResponseStatus,

    /// Details about why the response was incomplete, if applicable.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub incomplete_details: Option<IncompleteDetails>,

    /// The model that generated this response.
    pub model: String,

    /// ID of the previous response in the chain, if any.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub previous_response_id: Option<String>,

    /// Additional instructions used to guide the model.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub instructions: Option<String>,

    /// Output items generated by the model.
    pub output: Vec<OutputItem>,

    /// Error that occurred, if the response failed.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub error: Option<Box<OpenResponseError>>,

    /// Tools that were available to the model.
    pub tools: Option<Vec<ToolDefinition>>,

    /// Token usage statistics.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub usage: Option<Box<OpenUsage>>,

    /// Whether parallel tool calls were allowed.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub parallel_tool_calls: Option<bool>,

    /// Maximum output tokens allowed.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub max_output_tokens: Option<u64>,

    /// Maximum tool calls allowed.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub max_tool_calls: Option<u64>,

    /// Sampling temperature used.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub temperature: Option<f64>,

    /// Nucleus sampling parameter used.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub top_p: Option<f64>,

    /// Whether this response was stored.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub store: Option<bool>,

    /// Whether this request ran in the background.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub background: Option<bool>,

    /// Service tier used for this response.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub service_tier: Option<String>,

    /// Developer-defined metadata.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub metadata: Option<serde_json::Value>,
}

impl Response {
    /// Creates a new response with the given ID and model.
    pub fn new(id: impl Into<String>, model: impl Into<String>) -> Self {
        let now = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .map(|d| d.as_secs())
            .unwrap_or(0);

        Self {
            id: id.into(),
            object: "response".to_string(),
            created_at: now,
            completed_at: None,
            status: ResponseStatus::InProgress,
            incomplete_details: None,
            model: model.into(),
            previous_response_id: None,
            instructions: None,
            output: Vec::new(),
            error: None,
            tools: None,
            usage: None,
            parallel_tool_calls: None,
            max_output_tokens: None,
            max_tool_calls: None,
            temperature: None,
            top_p: None,
            store: None,
            background: None,
            service_tier: None,
            metadata: None,
        }
    }

    /// Adds an output item to the response.
    pub fn add_output(&mut self, item: OutputItem) {
        self.output.push(item);
    }

    /// Marks the response as completed.
    pub fn complete(&mut self) {
        self.status = ResponseStatus::Completed;
        self.completed_at = Some(
            SystemTime::now()
                .duration_since(UNIX_EPOCH)
                .map(|d| d.as_secs())
                .unwrap_or(0),
        );
    }

    /// Marks the response as failed with the given error.
    pub fn fail(&mut self, error: OpenResponseError) {
        self.status = ResponseStatus::Failed;
        self.error = Some(error.into());
        self.completed_at = Some(
            SystemTime::now()
                .duration_since(UNIX_EPOCH)
                .map(|d| d.as_secs())
                .unwrap_or(0),
        );
    }

    /// Marks the response as incomplete with the given reason.
    pub fn incomplete(&mut self, reason: IncompleteReason) {
        self.status = ResponseStatus::Incomplete;
        self.incomplete_details = Some(IncompleteDetails { reason });
        self.completed_at = Some(
            SystemTime::now()
                .duration_since(UNIX_EPOCH)
                .map(|d| d.as_secs())
                .unwrap_or(0),
        );
    }

    /// Sets the usage statistics.
    pub fn with_usage(mut self, usage: OpenUsage) -> Self {
        self.usage = Some(usage.into());
        self
    }

    /// Sets the available tools.
    pub fn with_tools(mut self, tools: Vec<ToolDefinition>) -> Self {
        self.tools = Some(tools);
        self
    }
}

impl Default for Response {
    fn default() -> Self {
        Self::new(generate_response_id(), "unknown")
    }
}

/// Generates a unique response ID.
pub fn generate_response_id() -> String {
    use std::sync::atomic::{AtomicU64, Ordering};
    static COUNTER: AtomicU64 = AtomicU64::new(0);

    let timestamp = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .map(|d| d.as_millis())
        .unwrap_or(0);
    let count = COUNTER.fetch_add(1, Ordering::Relaxed);

    format!("resp_{:x}_{:04x}", timestamp, count)
}

/// Generates a unique output item ID.
pub fn generate_item_id() -> String {
    use std::sync::atomic::{AtomicU64, Ordering};
    static COUNTER: AtomicU64 = AtomicU64::new(0);

    let count = COUNTER.fetch_add(1, Ordering::Relaxed);
    format!("item_{count}")
}

/// Generates a unique content part ID.
#[expect(dead_code)]
pub fn generate_content_part_id() -> String {
    use std::sync::atomic::{AtomicU64, Ordering};
    static COUNTER: AtomicU64 = AtomicU64::new(0);

    let count = COUNTER.fetch_add(1, Ordering::Relaxed);
    format!("cp_{count}")
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_response_creation() {
        let response = Response::new("resp_123", "gpt-5");
        assert_eq!(response.id, "resp_123");
        assert_eq!(response.model, "gpt-5");
        assert_eq!(response.object, "response");
        assert_eq!(response.status, ResponseStatus::InProgress);
    }

    #[test]
    fn test_response_complete() {
        let mut response = Response::new("resp_123", "gpt-5");
        response.complete();
        assert_eq!(response.status, ResponseStatus::Completed);
        assert!(response.completed_at.is_some());
    }

    #[test]
    fn test_response_fail() {
        let mut response = Response::new("resp_123", "gpt-5");
        response.fail(OpenResponseError::server_error("Test error"));
        assert_eq!(response.status, ResponseStatus::Failed);
        assert!(response.error.is_some());
    }

    #[test]
    fn test_id_generation() {
        let id1 = generate_response_id();
        let id2 = generate_response_id();
        assert_ne!(id1, id2);
        assert!(id1.starts_with("resp_"));
    }

    #[test]
    fn boxed_sparse_fields_are_smaller_than_inline_options() {
        use std::mem::size_of;

        assert!(size_of::<Option<Box<OpenUsage>>>() < size_of::<Option<OpenUsage>>());
        assert!(
            size_of::<Option<Box<OpenResponseError>>>() < size_of::<Option<OpenResponseError>>()
        );
    }
}
