//! `DeepSeek` provider.
//!
//! Uses the OpenAI-compatible API at `https://api.deepseek.com`.

use std::pin::Pin;
use std::sync::Arc;

use async_trait::async_trait;
use futures_util::Stream;

use super::openai_compat::{AuthMethod, OpenAiCompatConfig, OpenAiCompatProvider};
use crate::error::BlazenError;
use crate::http::HttpClient;
use crate::retry::RetryConfig;
use crate::traits::{Model, ModelInfo, ModelRegistry, ProviderCapabilities, ProviderInfo};
use crate::types::{ModelRequest, ModelResponse, StreamChunk};

// ---------------------------------------------------------------------------
// Provider
// ---------------------------------------------------------------------------

/// A `DeepSeek` chat completion provider.
///
/// Delegates to [`OpenAiCompatProvider`] with the `DeepSeek` base URL and
/// authentication pre-configured.
///
/// # Examples
///
/// ```rust,no_run
/// use blazen_llm::providers::deepseek::DeepSeekProvider;
///
/// let provider = DeepSeekProvider::new("your-api-key")
///     .with_model("deepseek-reasoner");
/// ```
pub struct DeepSeekProvider {
    inner: OpenAiCompatProvider,
    retry_config: Option<Arc<RetryConfig>>,
}

impl std::fmt::Debug for DeepSeekProvider {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("DeepSeekProvider")
            .field("inner", &self.inner)
            .finish_non_exhaustive()
    }
}

impl Clone for DeepSeekProvider {
    fn clone(&self) -> Self {
        Self {
            inner: self.inner.clone(),
            retry_config: self.retry_config.clone(),
        }
    }
}

impl DeepSeekProvider {
    /// Create a new `DeepSeek` provider with the given API key.
    ///
    /// Uses the default HTTP client backend (reqwest on native, fetch on WASM).
    #[cfg(any(
        all(target_arch = "wasm32", not(target_os = "wasi")),
        feature = "reqwest",
        target_os = "wasi"
    ))]
    #[must_use]
    pub fn new(api_key: impl Into<String>) -> Self {
        Self {
            inner: OpenAiCompatProvider::new(OpenAiCompatConfig {
                provider_name: "deepseek".into(),
                base_url: "https://api.deepseek.com".into(),
                api_key: api_key.into(),
                default_model: "deepseek-chat".into(),
                auth_method: AuthMethod::Bearer,
                extra_headers: Vec::new(),
                query_params: Vec::new(),
                supports_model_listing: false,
            }),
            retry_config: None,
        }
    }

    /// Create a new `DeepSeek` provider with an explicit HTTP client backend.
    #[must_use]
    pub fn new_with_client(api_key: impl Into<String>, client: Arc<dyn HttpClient>) -> Self {
        Self {
            inner: OpenAiCompatProvider::new_with_client(
                OpenAiCompatConfig {
                    provider_name: "deepseek".into(),
                    base_url: "https://api.deepseek.com".into(),
                    api_key: api_key.into(),
                    default_model: "deepseek-chat".into(),
                    auth_method: AuthMethod::Bearer,
                    extra_headers: Vec::new(),
                    query_params: Vec::new(),
                    supports_model_listing: false,
                },
                client,
            ),
            retry_config: None,
        }
    }

    /// Override the default model for this provider instance.
    #[must_use]
    pub fn with_model(mut self, model: impl Into<String>) -> Self {
        self.inner = self.inner.with_model(model);
        self
    }

    /// Use a custom HTTP client backend.
    #[must_use]
    pub fn with_http_client(mut self, client: Arc<dyn HttpClient>) -> Self {
        self.inner = self.inner.with_http_client(client);
        self
    }

    /// Return a clone of the underlying HTTP client.
    ///
    /// Escape hatch delegating to the wrapped
    /// [`OpenAiCompatProvider`]. Useful for issuing raw HTTP requests
    /// (custom headers, debugging, endpoints not yet covered by Blazen)
    /// while reusing the provider's connection pool and TLS config.
    #[must_use]
    pub fn http_client(&self) -> Arc<dyn HttpClient> {
        self.inner.http_client()
    }

    /// Set the provider-level default retry configuration.
    #[must_use]
    pub fn with_retry_config(mut self, config: RetryConfig) -> Self {
        self.retry_config = Some(Arc::new(config));
        self
    }
}

super::impl_simple_from_options!(DeepSeekProvider, "deepseek", no_base_url);

// ---------------------------------------------------------------------------
// Model implementation
// ---------------------------------------------------------------------------

#[async_trait]
impl Model for DeepSeekProvider {
    fn model_id(&self) -> &str {
        self.inner.model_id()
    }

    async fn complete(&self, request: ModelRequest) -> Result<ModelResponse, BlazenError> {
        self.inner.complete(request).await
    }

    async fn stream(
        &self,
        request: ModelRequest,
    ) -> Result<Pin<Box<dyn Stream<Item = Result<StreamChunk, BlazenError>> + Send>>, BlazenError>
    {
        self.inner.stream(request).await
    }

    fn retry_config(&self) -> Option<&Arc<RetryConfig>> {
        self.retry_config.as_ref()
    }

    fn http_client(&self) -> Option<Arc<dyn HttpClient>> {
        Some(Self::http_client(self))
    }
}

// ---------------------------------------------------------------------------
// ModelRegistry implementation
// ---------------------------------------------------------------------------

#[async_trait]
impl ModelRegistry for DeepSeekProvider {
    async fn list_models(&self) -> Result<Vec<ModelInfo>, BlazenError> {
        self.inner.list_models().await
    }

    async fn get_model(&self, model_id: &str) -> Result<Option<ModelInfo>, BlazenError> {
        self.inner.get_model(model_id).await
    }
}

// ---------------------------------------------------------------------------
// ProviderInfo implementation
// ---------------------------------------------------------------------------

impl ProviderInfo for DeepSeekProvider {
    fn provider_name(&self) -> &'static str {
        "deepseek"
    }

    fn base_url(&self) -> &'static str {
        "https://api.deepseek.com"
    }

    fn capabilities(&self) -> ProviderCapabilities {
        ProviderCapabilities {
            streaming: true,
            tool_calling: true,
            structured_output: true,
            vision: false,
            model_listing: false,
            embeddings: false,
        }
    }
}

// ---------------------------------------------------------------------------
// BaseProvider + LLMProvider impls (polymorphic provider class hierarchy)
// ---------------------------------------------------------------------------

impl crate::providers::root::BaseProvider for DeepSeekProvider {
    fn metadata(&self) -> &crate::providers::root::ProviderMetadata {
        static META: std::sync::LazyLock<crate::providers::root::ProviderMetadata> =
            std::sync::LazyLock::new(|| {
                crate::providers::root::ProviderMetadata::new(
                    "deepseek",
                    crate::providers::root::CapabilityKind::Llm,
                )
            });
        &META
    }
}

#[async_trait::async_trait]
impl crate::providers::capabilities::LLMProvider for DeepSeekProvider {
    async fn complete(
        &self,
        request: crate::types::ModelRequest,
    ) -> Result<crate::types::ModelResponse, crate::error::BlazenError> {
        <Self as crate::traits::Model>::complete(self, request).await
    }
    async fn stream(
        &self,
        request: crate::types::ModelRequest,
    ) -> Result<
        std::pin::Pin<
            Box<
                dyn futures_util::Stream<
                        Item = Result<crate::types::StreamChunk, crate::error::BlazenError>,
                    > + Send,
            >,
        >,
        crate::error::BlazenError,
    > {
        <Self as crate::traits::Model>::stream(self, request).await
    }
    fn model_id(&self) -> &str {
        <Self as crate::traits::Model>::model_id(self)
    }
}

// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------

#[cfg(test)]
mod tests {
    use crate::providers::sse::OaiResponse;

    #[test]
    fn test_r1_reasoning_content_passes_through() {
        let json_body = r#"{
            "id": "chatcmpl-x",
            "object": "chat.completion",
            "created": 1234567890,
            "model": "deepseek-reasoner",
            "choices": [{
                "index": 0,
                "message": {
                    "role": "assistant",
                    "content": "the answer is 42",
                    "reasoning_content": "first I considered..."
                },
                "finish_reason": "stop"
            }],
            "usage": {
                "prompt_tokens": 10,
                "completion_tokens": 5,
                "total_tokens": 15,
                "completion_tokens_details": { "reasoning_tokens": 3 }
            }
        }"#;
        let parsed: OaiResponse = serde_json::from_str(json_body).unwrap();
        let msg = &parsed.choices[0].message;
        assert_eq!(
            msg.reasoning_content.as_deref(),
            Some("first I considered...")
        );
        let usage = parsed.usage.unwrap();
        assert_eq!(usage.completion_tokens_details.unwrap().reasoning_tokens, 3);
    }
}
