use anyhow::Result;
use async_trait::async_trait;
use futures::stream::BoxStream;
use futures::StreamExt;

use super::helpers;
use super::{ProviderResponse, ProviderStreamChunk};
use crate::{config::AppConfig, errors::AppError, models::UnifiedRequest};

pub struct OpenAIProvider {
    client: reqwest::Client,
    config: crate::config::OpenAIConfig,
    api_key: String,
    pricing: Vec<crate::config::ModelPricing>,
}

impl OpenAIProvider {
    pub fn new(config: &crate::config::OpenAIConfig, app_config: &AppConfig) -> Result<Self> {
        let api_key = app_config.get_api_key("openai")?;
        Self::new_with_key(config, app_config, api_key)
    }

    pub fn new_with_key(config: &crate::config::OpenAIConfig, app_config: &AppConfig, api_key: String) -> Result<Self> {
        let client = reqwest::Client::builder()
            .connect_timeout(std::time::Duration::from_secs(5))
            .timeout(std::time::Duration::from_secs(300))
            .pool_idle_timeout(std::time::Duration::from_secs(90))
            .pool_max_idle_per_host(4)
            .tcp_keepalive(std::time::Duration::from_secs(30))
            .build()?;

        Ok(Self {
            client,
            config: config.clone(),
            api_key,
            pricing: app_config.pricing.openai.clone(),
        })
    }
}

#[async_trait]
impl super::Provider for OpenAIProvider {
    fn name(&self) -> &str {
        "openai"
    }

    fn supports_model(&self, model: &str) -> bool {
        model.starts_with("gpt-") || 
        model.starts_with("o1-") || 
        model.starts_with("o2-") ||
        model.starts_with("o3-") || 
        model.starts_with("o4-") || 
        model.starts_with("o5-") ||
        model.contains("gpt-5")
    }

    fn supports_multimodal(&self) -> bool {
        true
    }

    async fn chat_completion(&self, request: UnifiedRequest) -> Result<ProviderResponse, AppError> {
        // Allow proactive routing to Responses API based on heuristic
        let model_lc = request.model.to_lowercase();
        if model_lc.contains("gpt-5") || model_lc.contains("codex") {
             return self.chat_responses(request).await;
        }

        let messages_json = helpers::messages_to_openai_json(&request.messages).await?;
        let mut body = helpers::build_openai_body(&request, messages_json, false);

        // Transition: Newer OpenAI models (o1, o3, gpt-5) require max_completion_tokens
        // instead of the legacy max_tokens parameter.
        if request.model.starts_with("o1-") || request.model.starts_with("o3-") || request.model.contains("gpt-5") {
            if let Some(max_tokens) = body.as_object_mut().and_then(|obj| obj.remove("max_tokens")) {
                body["max_completion_tokens"] = max_tokens;
            }
        }

        let response = self
            .client
            .post(format!("{}/chat/completions", self.config.base_url))
            .header("Authorization", format!("Bearer {}", self.api_key))
            .json(&body)
            .send()
            .await
            .map_err(|e| AppError::ProviderError(e.to_string()))?;

        if !response.status().is_success() {
            let status = response.status();
            let error_text = response.text().await.unwrap_or_default();
            
            // Read error body to diagnose. If the model requires the Responses
            // API (v1/responses), retry against that endpoint.
            if error_text.to_lowercase().contains("v1/responses") || error_text.to_lowercase().contains("only supported in v1/responses") {
                // Build a simple `input` string by concatenating message parts.
                let messages_json = helpers::messages_to_openai_json(&request.messages).await?;
                let mut inputs: Vec<String> = Vec::new();
                for m in &messages_json {
                    let role = m["role"].as_str().unwrap_or("");
                    let parts = m.get("content").and_then(|c| c.as_array()).cloned().unwrap_or_default();
                    let mut text_parts = Vec::new();
                    for p in parts {
                        if let Some(t) = p.get("text").and_then(|v| v.as_str()) {
                            text_parts.push(t.to_string());
                        }
                    }
                    inputs.push(format!("{}: {}", role, text_parts.join("")));
                }
                let input_text = inputs.join("\n");

                let resp = self
                    .client
                    .post(format!("{}/responses", self.config.base_url))
                    .header("Authorization", format!("Bearer {}", self.api_key))
                    .json(&serde_json::json!({ "model": request.model, "input": input_text }))
                    .send()
                    .await
                    .map_err(|e| AppError::ProviderError(e.to_string()))?;

                if !resp.status().is_success() {
                    let err = resp.text().await.unwrap_or_default();
                    return Err(AppError::ProviderError(format!("OpenAI Responses API error: {}", err)));
                }

                let resp_json: serde_json::Value = resp.json().await.map_err(|e| AppError::ProviderError(e.to_string()))?;
                // Try to normalize: if it's chat-style, use existing parser
                if resp_json.get("choices").is_some() {
                    return helpers::parse_openai_response(&resp_json, request.model);
                }

                // Responses API: try to extract text from `output` or `candidates`
                let mut content_text = String::new();
                if let Some(output) = resp_json.get("output").and_then(|o| o.as_array()) {
                    if let Some(first) = output.get(0) {
                        if let Some(contents) = first.get("content").and_then(|c| c.as_array()) {
                            for item in contents {
                                if let Some(text) = item.get("text").and_then(|t| t.as_str()) {
                                    if !content_text.is_empty() { content_text.push_str("\n"); }
                                    content_text.push_str(text);
                                } else if let Some(parts) = item.get("parts").and_then(|p| p.as_array()) {
                                    for p in parts {
                                        if let Some(t) = p.as_str() {
                                            if !content_text.is_empty() { content_text.push_str("\n"); }
                                            content_text.push_str(t);
                                        }
                                    }
                                }
                            }
                        }
                    }
                }

                if content_text.is_empty() {
                    if let Some(cands) = resp_json.get("candidates").and_then(|c| c.as_array()) {
                        if let Some(c0) = cands.get(0) {
                            if let Some(content) = c0.get("content") {
                                if let Some(parts) = content.get("parts").and_then(|p| p.as_array()) {
                                    for p in parts {
                                        if let Some(t) = p.get("text").and_then(|v| v.as_str()) {
                                            if !content_text.is_empty() { content_text.push_str("\n"); }
                                            content_text.push_str(t);
                                        }
                                    }
                                }
                            }
                        }
                    }
                }

                let prompt_tokens = resp_json.get("usage").and_then(|u| u.get("prompt_tokens")).and_then(|v| v.as_u64()).unwrap_or(0) as u32;
                let completion_tokens = resp_json.get("usage").and_then(|u| u.get("completion_tokens")).and_then(|v| v.as_u64()).unwrap_or(0) as u32;
                let total_tokens = resp_json.get("usage").and_then(|u| u.get("total_tokens")).and_then(|v| v.as_u64()).unwrap_or(0) as u32;

                return Ok(ProviderResponse {
                    content: content_text,
                    reasoning_content: None,
                    tool_calls: None,
                    prompt_tokens,
                    completion_tokens,
                    total_tokens,
                    cache_read_tokens: 0,
                    cache_write_tokens: 0,
                    model: request.model,
                });
            }

            tracing::error!("OpenAI API error ({}): {}", status, error_text);
            return Err(AppError::ProviderError(format!("OpenAI API error ({}): {}", status, error_text)));
        }

        let resp_json: serde_json::Value = response
            .json()
            .await
            .map_err(|e| AppError::ProviderError(e.to_string()))?;

        helpers::parse_openai_response(&resp_json, request.model)
    }

    async fn chat_responses(&self, request: UnifiedRequest) -> Result<ProviderResponse, AppError> {
        // Build a structured input for the Responses API.
        let messages_json = helpers::messages_to_openai_json(&request.messages).await?;
        let mut input_parts = Vec::new();
        for m in &messages_json {
            let role = m["role"].as_str().unwrap_or("user");
            let content = m.get("content").cloned().unwrap_or(serde_json::json!(""));
            
            input_parts.push(serde_json::json!({
                "role": role,
                "content": content
            }));
        }

        let resp = self
            .client
            .post(format!("{}/responses", self.config.base_url))
            .header("Authorization", format!("Bearer {}", self.api_key))
            .json(&serde_json::json!({ "model": request.model, "input": input_parts }))
            .send()
            .await
            .map_err(|e| AppError::ProviderError(e.to_string()))?;

        if !resp.status().is_success() {
            let err = resp.text().await.unwrap_or_default();
            return Err(AppError::ProviderError(format!("OpenAI Responses API error: {}", err)));
        }

        let resp_json: serde_json::Value = resp.json().await.map_err(|e| AppError::ProviderError(e.to_string()))?;

        // Normalize Responses API output into ProviderResponse
        let mut content_text = String::new();
        if let Some(output) = resp_json.get("output").and_then(|o| o.as_array()) {
            if let Some(first) = output.get(0) {
                if let Some(contents) = first.get("content").and_then(|c| c.as_array()) {
                    for item in contents {
                        if let Some(text) = item.get("text").and_then(|t| t.as_str()) {
                            if !content_text.is_empty() { content_text.push_str("\n"); }
                            content_text.push_str(text);
                        } else if let Some(parts) = item.get("parts").and_then(|p| p.as_array()) {
                            for p in parts {
                                if let Some(t) = p.as_str() {
                                    if !content_text.is_empty() { content_text.push_str("\n"); }
                                    content_text.push_str(t);
                                }
                            }
                        }
                    }
                }
            }
        }

        if content_text.is_empty() {
            if let Some(cands) = resp_json.get("candidates").and_then(|c| c.as_array()) {
                if let Some(c0) = cands.get(0) {
                    if let Some(content) = c0.get("content") {
                        if let Some(parts) = content.get("parts").and_then(|p| p.as_array()) {
                            for p in parts {
                                if let Some(t) = p.get("text").and_then(|v| v.as_str()) {
                                    if !content_text.is_empty() { content_text.push_str("\n"); }
                                    content_text.push_str(t);
                                }
                            }
                        }
                    }
                }
            }
        }

        let prompt_tokens = resp_json.get("usage").and_then(|u| u.get("prompt_tokens")).and_then(|v| v.as_u64()).unwrap_or(0) as u32;
        let completion_tokens = resp_json.get("usage").and_then(|u| u.get("completion_tokens")).and_then(|v| v.as_u64()).unwrap_or(0) as u32;
        let total_tokens = resp_json.get("usage").and_then(|u| u.get("total_tokens")).and_then(|v| v.as_u64()).unwrap_or(0) as u32;

        Ok(ProviderResponse {
            content: content_text,
            reasoning_content: None,
            tool_calls: None,
            prompt_tokens,
            completion_tokens,
            total_tokens,
            cache_read_tokens: 0,
            cache_write_tokens: 0,
            model: request.model,
        })
    }

    fn estimate_tokens(&self, request: &UnifiedRequest) -> Result<u32> {
        Ok(crate::utils::tokens::estimate_request_tokens(&request.model, request))
    }

    fn calculate_cost(
        &self,
        model: &str,
        prompt_tokens: u32,
        completion_tokens: u32,
        cache_read_tokens: u32,
        cache_write_tokens: u32,
        registry: &crate::models::registry::ModelRegistry,
    ) -> f64 {
        helpers::calculate_cost_with_registry(
            model,
            prompt_tokens,
            completion_tokens,
            cache_read_tokens,
            cache_write_tokens,
            registry,
            &self.pricing,
            0.15,
            0.60,
        )
    }

    async fn chat_completion_stream(
        &self,
        request: UnifiedRequest,
    ) -> Result<BoxStream<'static, Result<ProviderStreamChunk, AppError>>, AppError> {
        let messages_json = helpers::messages_to_openai_json(&request.messages).await?;
        let mut body = helpers::build_openai_body(&request, messages_json, true);

        // Standard OpenAI cleanup
        if let Some(obj) = body.as_object_mut() {
            // stream_options.include_usage is supported by OpenAI for token usage in streaming
            // Transition: Newer OpenAI models (o1, o3, gpt-5) require max_completion_tokens
            if request.model.starts_with("o1-") || request.model.starts_with("o3-") || request.model.contains("gpt-5") {
                if let Some(max_tokens) = obj.remove("max_tokens") {
                    obj.insert("max_completion_tokens".to_string(), max_tokens);
                }
            }
        }

        let url = format!("{}/chat/completions", self.config.base_url);
        let api_key = self.api_key.clone();
        let probe_client = self.client.clone();
        let probe_body = body.clone();
        let model = request.model.clone();

        let es = reqwest_eventsource::EventSource::new(
            self.client
                .post(&url)
                .header("Authorization", format!("Bearer {}", self.api_key))
                .json(&body),
        )
        .map_err(|e| AppError::ProviderError(format!("Failed to create EventSource: {}", e)))?;

        let stream = async_stream::try_stream! {
            let mut es = es;
            while let Some(event) = es.next().await {
                match event {
                    Ok(reqwest_eventsource::Event::Message(msg)) => {
                        if msg.data == "[DONE]" {
                            break;
                        }

                        let chunk: serde_json::Value = serde_json::from_str(&msg.data)
                            .map_err(|e| AppError::ProviderError(format!("Failed to parse stream chunk: {}", e)))?;

                        if let Some(p_chunk) = helpers::parse_openai_stream_chunk(&chunk, &model, None) {
                            yield p_chunk?;
                        }
                    }
                    Ok(_) => continue,
                    Err(e) => {
                        // Attempt to probe for the actual error body
                        let probe_resp = probe_client
                            .post(&url)
                            .header("Authorization", format!("Bearer {}", api_key))
                            .json(&probe_body)
                            .send()
                            .await;
                        
                        match probe_resp {
                            Ok(r) if !r.status().is_success() => {
                                let status = r.status();
                                let error_body = r.text().await.unwrap_or_default();
                                tracing::error!("OpenAI Stream Error Probe ({}): {}", status, error_body);
                                tracing::debug!("Offending OpenAI Request Body: {}", serde_json::to_string(&probe_body).unwrap_or_default());
                                Err(AppError::ProviderError(format!("OpenAI API error ({}): {}", status, error_body)))?;
                            }
                            Ok(_) => {
                                // Probe returned success? This is unexpected if the original stream failed.
                                Err(AppError::ProviderError(format!("Stream error (probe returned 200): {}", e)))?;
                            }
                            Err(probe_err) => {
                                // Probe itself failed
                                tracing::error!("OpenAI Stream Error Probe failed: {}", probe_err);
                                Err(AppError::ProviderError(format!("Stream error (probe failed: {}): {}", probe_err, e)))?;
                            }
                        }
                    }
                }
            }
        };

        Ok(Box::pin(stream))
    }

    async fn chat_responses_stream(
        &self,
        request: UnifiedRequest,
    ) -> Result<BoxStream<'static, Result<ProviderStreamChunk, AppError>>, AppError> {
        // Build a structured input for the Responses API.
        let messages_json = helpers::messages_to_openai_json(&request.messages).await?;
        let mut input_parts = Vec::new();
        for m in &messages_json {
            let role = m["role"].as_str().unwrap_or("user");
            let content = m.get("content").cloned().unwrap_or(serde_json::json!(""));
            
            input_parts.push(serde_json::json!({
                "role": role,
                "content": content
            }));
        }

        let body = serde_json::json!({
            "model": request.model,
            "input": input_parts,
            "stream": true
        });

        let url = format!("{}/responses", self.config.base_url);
        let api_key = self.api_key.clone();
        let model = request.model.clone();
        let probe_client = self.client.clone();
        let probe_body = body.clone();

        let es = reqwest_eventsource::EventSource::new(
            self.client
                .post(&url)
                .header("Authorization", format!("Bearer {}", api_key))
                .json(&body),
        )
        .map_err(|e| AppError::ProviderError(format!("Failed to create EventSource for Responses API: {}", e)))?;

        let stream = async_stream::try_stream! {
            let mut es = es;
            while let Some(event) = es.next().await {
                match event {
                    Ok(reqwest_eventsource::Event::Message(msg)) => {
                        if msg.data == "[DONE]" {
                            break;
                        }

                        let chunk: serde_json::Value = serde_json::from_str(&msg.data)
                            .map_err(|e| AppError::ProviderError(format!("Failed to parse Responses stream chunk: {}", e)))?;

                        // Try standard OpenAI parsing first
                        if let Some(p_chunk) = helpers::parse_openai_stream_chunk(&chunk, &model, None) {
                            yield p_chunk?;
                        } else {
                            // Responses API specific parsing for streaming
                            // Often it follows a similar structure to the non-streaming response but in chunks
                            let mut content = String::new();
                            
                            // Check for output[0].content[0].text (similar to non-stream)
                            if let Some(output) = chunk.get("output").and_then(|o| o.as_array()) {
                                if let Some(first) = output.get(0) {
                                    if let Some(contents) = first.get("content").and_then(|c| c.as_array()) {
                                        for item in contents {
                                            if let Some(text) = item.get("text").and_then(|t| t.as_str()) {
                                                content.push_str(text);
                                            }
                                        }
                                    }
                                }
                            }
                            
                            // Check for candidates[0].content.parts[0].text (Gemini-like, which OpenAI sometimes uses for v1/responses)
                            if content.is_empty() {
                                if let Some(cands) = chunk.get("candidates").and_then(|c| c.as_array()) {
                                    if let Some(c0) = cands.get(0) {
                                        if let Some(content_obj) = c0.get("content") {
                                            if let Some(parts) = content_obj.get("parts").and_then(|p| p.as_array()) {
                                                for p in parts {
                                                    if let Some(t) = p.get("text").and_then(|v| v.as_str()) {
                                                        content.push_str(t);
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }

                            if !content.is_empty() {
                                yield ProviderStreamChunk {
                                    content,
                                    reasoning_content: None,
                                    finish_reason: None,
                                    tool_calls: None,
                                    model: model.clone(),
                                    usage: None,
                                };
                            }
                        }
                    }
                    Ok(_) => continue,
                    Err(e) => {
                         // Attempt to probe for the actual error body
                        let probe_resp = probe_client
                            .post(&url)
                            .header("Authorization", format!("Bearer {}", api_key))
                            .json(&probe_body)
                            .send()
                            .await;
                        
                        match probe_resp {
                            Ok(r) if !r.status().is_success() => {
                                let status = r.status();
                                let error_body = r.text().await.unwrap_or_default();
                                tracing::error!("OpenAI Responses Stream Error Probe ({}): {}", status, error_body);
                                Err(AppError::ProviderError(format!("OpenAI Responses API error ({}): {}", status, error_body)))?;
                            }
                            Ok(_) => {
                                Err(AppError::ProviderError(format!("Responses stream error (probe returned 200): {}", e)))?;
                            }
                            Err(probe_err) => {
                                tracing::error!("OpenAI Responses Stream Error Probe failed: {}", probe_err);
                                Err(AppError::ProviderError(format!("Responses stream error (probe failed: {}): {}", probe_err, e)))?;
                            }
                        }
                    }
                }
            }
        };

        Ok(Box::pin(stream))
    }
}
