//! OpenAI LLM provider (GPT-3.5, GPT-4)

use super::super::error::{LlmError, Result};
use super::super::messages::{Message, Role};
use super::super::options::GenerationOptions;
use super::super::{LlmProvider, StreamResponse};
use async_trait::async_trait;
use futures::stream::StreamExt;
use reqwest::Client;
use serde::{Deserialize, Serialize};
use std::sync::Arc;

const OPENAI_API_URL: &str = "https://api.openai.com/v1/chat/completions";

/// OpenAI API request
#[derive(Debug, Serialize)]
struct OpenAiRequest {
    model: String,
    messages: Vec<OpenAiMessage>,
    temperature: f32,
    #[serde(skip_serializing_if = "Option::is_none")]
    max_tokens: Option<usize>,
    #[serde(skip_serializing_if = "Option::is_none")]
    top_p: Option<f32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    presence_penalty: Option<f32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    frequency_penalty: Option<f32>,
    stream: bool,
}

/// OpenAI message format
#[derive(Debug, Serialize, Deserialize)]
struct OpenAiMessage {
    role: String,
    content: String,
}

/// OpenAI API response
#[derive(Debug, Deserialize)]
struct OpenAiResponse {
    choices: Vec<OpenAiChoice>,
}

/// OpenAI choice (completion option)
#[derive(Debug, Deserialize)]
struct OpenAiChoice {
    message: OpenAiMessage,
    #[allow(dead_code)]
    finish_reason: String,
}

/// Streaming response chunk
#[derive(Debug, Deserialize)]
struct OpenAiStreamChunk {
    choices: Vec<StreamChoice>,
}

/// Streaming choice
#[derive(Debug, Deserialize)]
struct StreamChoice {
    delta: StreamDelta,
}

/// Stream delta content
#[derive(Debug, Deserialize)]
struct StreamDelta {
    #[serde(default)]
    content: String,
}

/// OpenAI provider
pub struct OpenAiProvider {
    api_key: String,
    model: String,
    client: Arc<Client>,
}

impl OpenAiProvider {
    /// Create new OpenAI provider
    ///
    /// # Arguments
    /// * `api_key` - OpenAI API key
    /// * `model` - Model name (e.g., "gpt-4", "gpt-3.5-turbo")
    ///
    /// # Errors
    /// Returns error if api_key is empty
    pub fn new(api_key: impl Into<String>, model: impl Into<String>) -> Result<Self> {
        let api_key = api_key.into();
        if api_key.is_empty() {
            return Err(LlmError::ConfigError("OpenAI API key is empty".into()));
        }

        Ok(OpenAiProvider {
            api_key,
            model: model.into(),
            client: Arc::new(Client::new()),
        })
    }

    /// Create from environment variable `OPENAI_API_KEY`
    pub fn from_env(model: impl Into<String>) -> Result<Self> {
        let api_key = std::env::var("OPENAI_API_KEY")
            .map_err(|_| LlmError::ConfigError("OPENAI_API_KEY not set".into()))?;
        Self::new(api_key, model)
    }

    fn build_request(&self, messages: &[Message], options: &GenerationOptions) -> OpenAiRequest {
        let openai_messages = messages
            .iter()
            .map(|msg| OpenAiMessage {
                role: match msg.role {
                    Role::System => "system",
                    Role::User => "user",
                    Role::Assistant => "assistant",
                }
                .to_string(),
                content: msg.content.clone(),
            })
            .collect();

        OpenAiRequest {
            model: self.model.clone(),
            messages: openai_messages,
            temperature: options.temperature.clamp(0.0, 2.0),
            max_tokens: options.max_tokens,
            top_p: options.top_p,
            presence_penalty: options.presence_penalty,
            frequency_penalty: options.frequency_penalty,
            stream: false,
        }
    }

    fn build_stream_request(
        &self,
        messages: &[Message],
        options: &GenerationOptions,
    ) -> OpenAiRequest {
        let mut req = self.build_request(messages, options);
        req.stream = true;
        req
    }
}

#[async_trait]
impl LlmProvider for OpenAiProvider {
    async fn generate(&self, messages: &[Message], options: &GenerationOptions) -> Result<String> {
        let req = self.build_request(messages, options);

        let response = self
            .client
            .post(OPENAI_API_URL)
            .header("Authorization", format!("Bearer {}", self.api_key))
            .json(&req)
            .send()
            .await
            .map_err(|e| {
                if e.is_timeout() {
                    LlmError::Timeout("OpenAI request timed out".into())
                } else if e.status().is_some_and(|s| s.as_u16() == 429) {
                    LlmError::RateLimit("OpenAI rate limited".into())
                } else {
                    LlmError::NetworkError(e)
                }
            })?;

        let status = response.status();
        if !status.is_success() {
            let error_text = response
                .text()
                .await
                .unwrap_or_else(|_| "Unknown error".to_string());
            return Err(LlmError::ApiError(format!(
                "OpenAI API error ({}): {}",
                status, error_text
            )));
        }

        let body: OpenAiResponse = response
            .json()
            .await
            .map_err(|e| LlmError::ApiError(format!("Failed to parse response: {}", e)))?;

        body.choices
            .into_iter()
            .next()
            .map(|choice| choice.message.content)
            .ok_or_else(|| LlmError::ApiError("No choices in response".into()))
    }

    async fn stream(
        &self,
        messages: &[Message],
        options: &GenerationOptions,
    ) -> Result<StreamResponse> {
        let req = self.build_stream_request(messages, options);

        let response = self
            .client
            .post(OPENAI_API_URL)
            .header("Authorization", format!("Bearer {}", self.api_key))
            .json(&req)
            .send()
            .await
            .map_err(|e| {
                if e.is_timeout() {
                    LlmError::Timeout("OpenAI request timed out".into())
                } else {
                    LlmError::NetworkError(e)
                }
            })?;

        let status = response.status();
        if !status.is_success() {
            return Err(LlmError::ApiError(format!("OpenAI API error: {}", status)));
        }

        let stream = response
            .bytes_stream()
            .filter_map(|result| async move {
                match result {
                    Ok(bytes) => {
                        let text = String::from_utf8_lossy(&bytes);
                        let text = text.trim();
                        if text.is_empty() {
                            return None;
                        }

                        for line in text.lines() {
                            if let Some(data) = line.strip_prefix("data: ") {
                                if data == "[DONE]" {
                                    return None;
                                }

                                if let Ok(chunk) = serde_json::from_str::<OpenAiStreamChunk>(data) {
                                    if let Some(choice) = chunk.choices.first() {
                                        if !choice.delta.content.is_empty() {
                                            return Some(Ok(choice.delta.content.clone()));
                                        }
                                    }
                                }
                            }
                        }
                        None
                    }
                    Err(e) => Some(Err(LlmError::StreamError(e.to_string()))),
                }
            })
            .boxed();

        Ok(Box::pin(stream))
    }

    fn name(&self) -> &str {
        "OpenAI"
    }

    fn model(&self) -> &str {
        &self.model
    }

    async fn is_available(&self) -> bool {
        // Quick health check - attempt to verify API key
        let req = serde_json::json!({
            "model": &self.model,
            "messages": [{"role": "user", "content": "test"}],
            "max_tokens": 1
        });

        self.client
            .post(OPENAI_API_URL)
            .header("Authorization", format!("Bearer {}", self.api_key))
            .json(&req)
            .send()
            .await
            .map(|r| r.status().is_success())
            .unwrap_or(false)
    }
}

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

    #[test]
    fn test_openai_new() {
        let provider = OpenAiProvider::new("sk-test", "gpt-4");
        assert!(provider.is_ok());
    }

    #[test]
    fn test_openai_empty_key() {
        let provider = OpenAiProvider::new("", "gpt-4");
        assert!(provider.is_err());
    }

    #[test]
    fn test_provider_name() {
        let provider = OpenAiProvider::new("sk-test", "gpt-4").unwrap();
        assert_eq!(provider.name(), "OpenAI");
        assert_eq!(provider.model(), "gpt-4");
    }

    #[test]
    fn test_build_request() {
        let provider = OpenAiProvider::new("sk-test", "gpt-4").unwrap();
        let messages = vec![Message::user("Hello")];
        let options = GenerationOptions::default();

        let req = provider.build_request(&messages, &options);
        assert_eq!(req.model, "gpt-4");
        assert_eq!(req.messages.len(), 1);
        assert!(!req.stream);
    }

    #[test]
    fn test_build_stream_request() {
        let provider = OpenAiProvider::new("sk-test", "gpt-4").unwrap();
        let messages = vec![Message::user("Hello")];
        let options = GenerationOptions::default();

        let req = provider.build_stream_request(&messages, &options);
        assert!(req.stream);
    }
}
