use crate::client::OllmClient;
use crate::error::OllmError;
use serde::{Deserialize, Serialize};

/// A message in a chat conversation.
///
/// # Example
///
/// ```no_run
/// use ollm_sdk::ChatMessage;
///
/// let message = ChatMessage {
///     role: "user".to_string(),
///     content: "Hello!".to_string(),
/// };
/// ```
#[derive(Serialize, Clone, Debug)]
pub struct ChatMessage {
    /// The role of the message sender (e.g., "user", "assistant", "system")
    pub role: String,
    /// The content of the message
    pub content: String,
}

#[derive(Serialize)]
pub struct ChatRequest {
    pub model: String,
    pub messages: Vec<ChatMessage>,
}

#[derive(Deserialize, Debug)]
pub struct ChatResponse {
    pub id: Option<String>,
    pub object: Option<String>,
    pub created: Option<u64>,
    pub model: String,
    pub choices: Vec<ChatChoice>,
    pub usage: Option<Usage>,
}

#[derive(Deserialize, Debug)]
pub struct Usage {
    pub prompt_tokens: Option<u32>,
    pub completion_tokens: Option<u32>,
    pub total_tokens: Option<u32>,
}

#[derive(Deserialize, Debug)]
pub struct ChatChoice {
    pub message: ChatMessageResponse,
}

#[derive(Deserialize, Debug)]
pub struct ChatMessageResponse {
    pub role: String,
    pub content: String,
}

impl ChatResponse {
    /// Gets the content of the first choice in the response.
    ///
    /// # Returns
    ///
    /// Returns `Ok(&str)` with the content if a choice exists, or `Err(OllmError::MissingChoice)` if no choices are present.
    ///
    /// # Example
    ///
    /// ```no_run
    /// # use ollm_sdk::ChatResponse;
    /// # let response: ChatResponse = todo!();
    /// match response.first_content() {
    ///     Ok(content) => println!("Response: {}", content),
    ///     Err(e) => eprintln!("Error: {}", e),
    /// }
    /// ```
    pub fn first_content(&self) -> Result<&str, OllmError> {
        self.choices
            .first()
            .map(|c| c.message.content.as_str())
            .ok_or(OllmError::MissingChoice)
    }
}

impl OllmClient {
    /// Sends a chat completion request to the OLLM API.
    ///
    /// # Arguments
    ///
    /// * `messages` - A vector of chat messages in the conversation
    /// * `model` - The model to use (e.g., "near/GLM-4.6")
    ///
    /// # Returns
    ///
    /// Returns `Ok(ChatResponse)` on success, or `Err(OllmError)` on failure.
    ///
    /// # Example
    ///
    /// ```no_run
    /// use ollm_sdk::{ChatMessage, OllmClient, Model};
    ///
    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
    /// let client = OllmClient::new("https://api.ollm.com/v1", "your-api-key");
    ///
    /// let response = client
    ///     .chat(
    ///         vec![ChatMessage {
    ///             role: "user".to_string(),
    ///             content: "Hello!".to_string(),
    ///         }],
    ///         Model::NearGLM46.as_str(),
    ///     )
    ///     .await?;
    ///
    /// println!("{}", response.first_content()?);
    /// # Ok(())
    /// # }
    /// ```
    pub async fn chat(
        &self,
        messages: Vec<ChatMessage>,
        model: &str,
    ) -> Result<ChatResponse, OllmError> {
        let url = format!("{}/chat/completions", self.base_url);

        let body = ChatRequest {
            model: model.to_string(),
            messages,
        };

        let response = self
            .http
            .post(&url)
            .bearer_auth(&self.api_key)
            .json(&body)
            .send()
            .await?;

        let status = response.status();

        if !status.is_success() {
            let error_text = response
                .text()
                .await
                .unwrap_or_else(|e| format!("Failed to read error response: {}", e));
            return Err(OllmError::ApiError {
                status: status.as_u16(),
                message: error_text,
            });
        }

        let res = response.json::<ChatResponse>().await?;
        Ok(res)
    }
}
