use super::OpenAIProvider;
use crate::config::constants::models;
use crate::llm::client::LLMClient;
use crate::llm::provider::{self, LLMNormalizedStream};
use crate::llm::types as llm_types;
use async_trait::async_trait;

#[async_trait]
impl provider::LLMProvider for OpenAIProvider {
    fn name(&self) -> &str {
        self.provider_key_override.as_deref().unwrap_or("openai")
    }

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

    fn supports_non_streaming(&self, model: &str) -> bool {
        let requested = if model.trim().is_empty() {
            self.model.as_ref()
        } else {
            model
        };

        !self.is_chatgpt_backend() && !Self::requires_streaming_responses(requested)
    }

    fn supports_reasoning(&self, model: &str) -> bool {
        let requested = if model.trim().is_empty() {
            self.model.as_ref()
        } else {
            model
        };

        // Codex-inspired robustness: Setting model_supports_reasoning to false
        // does NOT disable it for known reasoning models.
        models::openai::REASONING_MODELS.contains(&requested)
            || self
                .model_behavior
                .as_ref()
                .and_then(|b| b.model_supports_reasoning)
                .unwrap_or(false)
    }

    fn supports_reasoning_effort(&self, model: &str) -> bool {
        let requested = if model.trim().is_empty() {
            self.model.as_ref()
        } else {
            model
        };

        // Same robustness logic for reasoning effort
        models::openai::REASONING_MODELS
            .iter()
            .any(|candidate| *candidate == requested)
            || self
                .model_behavior
                .as_ref()
                .and_then(|b| b.model_supports_reasoning_effort)
                .unwrap_or(false)
    }

    fn supports_tools(&self, model: &str) -> bool {
        let requested = if model.trim().is_empty() {
            self.model.as_ref()
        } else {
            model
        };

        !models::openai::TOOL_UNAVAILABLE_MODELS.contains(&requested)
    }

    fn supports_responses_compaction(&self, model: &str) -> bool {
        if self.is_chatgpt_backend() {
            return false;
        }
        let requested = if model.trim().is_empty() {
            self.model.as_ref()
        } else {
            model
        };
        !matches!(
            self.responses_api_state(requested),
            super::super::types::ResponsesApiState::Disabled
        )
    }

    fn supports_manual_openai_compaction(&self, model: &str) -> bool {
        let requested = if model.trim().is_empty() {
            self.model.as_ref()
        } else {
            model
        };
        self.supports_manual_openai_compaction_for_model(requested)
    }

    fn manual_openai_compaction_unavailable_message(&self, model: &str) -> String {
        self.manual_openai_compaction_unavailable_message_for_model(model)
    }

    async fn stream(
        &self,
        request: provider::LLMRequest,
    ) -> Result<provider::LLMStream, provider::LLMError> {
        self.stream_request(request).await
    }

    async fn stream_normalized(
        &self,
        request: provider::LLMRequest,
    ) -> Result<LLMNormalizedStream, provider::LLMError> {
        self.stream_normalized_request(request).await
    }

    async fn generate(
        &self,
        request: provider::LLMRequest,
    ) -> Result<provider::LLMResponse, provider::LLMError> {
        self.generate_request(request).await
    }

    async fn compact_history(
        &self,
        model: &str,
        history: &[provider::Message],
    ) -> Result<Vec<provider::Message>, provider::LLMError> {
        if !self.supports_responses_compaction(model) {
            return Err(provider::LLMError::Provider {
                message: "OpenAI Responses compaction is not supported for this endpoint/model"
                    .to_string(),
                metadata: None,
            });
        }

        self.compact_history_request(model, history).await
    }

    async fn compact_history_with_options(
        &self,
        model: &str,
        history: &[provider::Message],
        options: &provider::ResponsesCompactionOptions,
    ) -> Result<Vec<provider::Message>, provider::LLMError> {
        let requested = if model.trim().is_empty() {
            self.model.as_ref()
        } else {
            model
        };
        if !self.supports_manual_openai_compaction_for_model(requested) {
            return Err(provider::LLMError::Provider {
                message: self.manual_openai_compaction_unavailable_message_for_model(requested),
                metadata: None,
            });
        }

        self.compact_history_request_with_options(requested, history, options)
            .await
    }

    fn supported_models(&self) -> Vec<String> {
        if let Some(models) = &self.supported_models_override {
            return models.clone();
        }
        if self.provider_key_override.is_some() {
            return vec![self.model.to_string()];
        }
        models::openai::SUPPORTED_MODELS
            .iter()
            .map(|s| s.to_string())
            .collect()
    }

    fn validate_request(&self, request: &provider::LLMRequest) -> Result<(), provider::LLMError> {
        let supported_models =
            (!self.base_url.contains("api.openai.com")).then(|| self.supported_models());

        let display_name = self
            .provider_display_override
            .as_deref()
            .unwrap_or("OpenAI");
        let key = self.provider_key_override.as_deref().unwrap_or("openai");
        super::super::super::common::validate_request_common(
            request,
            display_name,
            key,
            supported_models.as_deref(),
        )
    }
}

#[async_trait]
impl LLMClient for OpenAIProvider {
    async fn generate(
        &mut self,
        prompt: &str,
    ) -> Result<llm_types::LLMResponse, provider::LLMError> {
        let request = super::super::super::common::make_default_request(prompt, &self.model);
        let request_model = request.model.to_string();
        let response = provider::LLMProvider::generate(self, request).await?;

        Ok(llm_types::LLMResponse {
            content: Some(response.content.unwrap_or_default()),
            model: request_model,
            usage: response
                .usage
                .map(super::super::super::common::convert_usage_to_llm_types),
            reasoning: response.reasoning,
            reasoning_details: response.reasoning_details,
            request_id: response.request_id,
            organization_id: response.organization_id,
            finish_reason: response.finish_reason,
            tool_calls: response.tool_calls,
            tool_references: response.tool_references,
            compaction: None,
        })
    }

    fn backend_kind(&self) -> llm_types::BackendKind {
        llm_types::BackendKind::OpenAI
    }

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