//! Anthropic Claude provider
//!
//! This provider supports Anthropic's Claude models via the Messages API.
//! It can be configured either programmatically or via config.yml.

use crate::config::ModelConfig;
use crate::http::{post_json, HttpRequestSpec};
use async_trait::async_trait;
use enact_core::providers::{
    ChatChoice, ChatMessage, ChatRequest, ChatResponse, ChatUsage, ModelCapabilities, ModelProvider,
};
use reqwest::Client;
use serde::{Deserialize, Serialize};
use std::time::Duration;

#[derive(Debug, Serialize)]
struct AnthropicRequest {
    model: String,
    max_tokens: u32,
    #[serde(skip_serializing_if = "Option::is_none")]
    temperature: Option<f32>,
    messages: Vec<AnthropicMessage>,
    #[serde(skip_serializing_if = "Option::is_none")]
    system: Option<String>,
}

#[derive(Debug, Serialize)]
struct AnthropicMessage {
    role: String,
    content: String,
}

#[derive(Debug, Deserialize)]
struct AnthropicResponse {
    id: String,
    content: Vec<AnthropicContentBlock>,
    #[serde(default)]
    usage: Option<AnthropicUsage>,
}

#[derive(Debug, Deserialize)]
struct AnthropicContentBlock {
    #[serde(rename = "type")]
    kind: String,
    #[serde(default)]
    text: Option<String>,
}

#[derive(Debug, Deserialize)]
struct AnthropicUsage {
    #[serde(default)]
    input_tokens: u32,
    #[serde(default)]
    output_tokens: u32,
}

/// Anthropic Claude provider.
///
/// Supports Claude models via the Messages API.
/// All configuration can be loaded from config.yml via `from_model_config()`.
pub struct Anthropic {
    client: Client,
    /// Model identifier
    model_id: String,
    /// The actual model name to pass to Anthropic
    anthropic_model: String,
    /// API key for authentication
    api_key: String,
    /// Full URL for API calls
    full_url: String,
    /// API version header
    api_version: String,
    /// Request timeout
    timeout: Duration,
    /// Whether temperature is supported
    has_temperature_supported: bool,
    /// Default max tokens
    max_tokens_default: u32,
    /// Default temperature
    temperature_default: f32,
    /// Whether tool calls are supported
    supports_tool_calls: bool,
    /// Cost per 1K input tokens (from config cost.cost_per_1m_input)
    cost_per_1m_input: Option<f64>,
    /// Cost per 1K output tokens (from config cost.cost_per_1m_output)
    cost_per_1m_output: Option<f64>,
    /// Cost per 1K pixels for image (from config cost.cost_per_1m_pixels)
    cost_per_1m_pixels: Option<f64>,
}

impl Anthropic {
    /// Create a new Anthropic provider from model configuration.
    ///
    /// # Arguments
    ///
    /// * `model_id` - The model identifier
    /// * `config` - The model configuration from config.yml
    /// * `api_key` - The resolved API key
    pub fn from_model_config(
        model_id: &str,
        config: &ModelConfig,
        api_key: &str,
    ) -> anyhow::Result<Self> {
        // Extract the actual Anthropic model name from cost.provider_key or use model_id
        let anthropic_model = config
            .cost
            .provider_key
            .clone()
            .unwrap_or_else(|| model_id.to_string());

        Ok(Self {
            client: Client::new(),
            model_id: model_id.to_string(),
            anthropic_model,
            api_key: api_key.to_string(),
            full_url: config.runtime.full_url.clone(),
            api_version: config.runtime.api_version.clone(),
            timeout: Duration::from_secs(config.runtime.timeout_seconds),
            has_temperature_supported: config.runtime.has_temperature_supported,
            max_tokens_default: config.runtime.max_tokens_default,
            temperature_default: config.runtime.temperature_default,
            supports_tool_calls: config.capabilities.tool_calls,
            cost_per_1m_input: config.cost.cost_per_1m_input,
            cost_per_1m_output: config.cost.cost_per_1m_output,
            cost_per_1m_pixels: config.cost.cost_per_1m_pixels,
        })
    }

    fn convert_messages(messages: &[ChatMessage]) -> (Option<String>, Vec<AnthropicMessage>) {
        let mut system = None;
        let mut out = Vec::new();

        for msg in messages {
            if msg.role == "system" && system.is_none() {
                system = msg.content.clone();
                continue;
            }

            let role = if msg.role == "assistant" {
                "assistant"
            } else {
                "user"
            };

            out.push(AnthropicMessage {
                role: role.to_string(),
                content: msg.content.clone().unwrap_or_default(),
            });
        }

        (system, out)
    }
}

#[async_trait]
impl ModelProvider for Anthropic {
    fn name(&self) -> &str {
        "anthropic"
    }

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

    fn capabilities(&self) -> ModelCapabilities {
        ModelCapabilities {
            max_tokens: self.max_tokens_default,
            max_output_tokens: self.max_tokens_default,
            supports_streaming: false,
            supports_tools: self.supports_tool_calls,
            supports_reasoning: false,
            supports_vision: true,
            supports_json_mode: true,
            supports_embeddings: false,
            supports_image_generation: false,
            supports_audio_transcription: false,
            supports_speech: false,
            supports_video_generation: false,
            pii_safe: false,
            cost_per_1m_input: self.cost_per_1m_input,
            cost_per_1m_output: self.cost_per_1m_output,
            cost_per_1m_pixels: self.cost_per_1m_pixels,
        }
    }

    async fn chat(&self, request: ChatRequest) -> anyhow::Result<ChatResponse> {
        let (system, messages) = Self::convert_messages(&request.messages);

        // Build temperature (only include if supported)
        let temperature = if self.has_temperature_supported {
            Some(request.temperature.unwrap_or(self.temperature_default))
        } else {
            None
        };

        let body = AnthropicRequest {
            model: self.anthropic_model.clone(),
            max_tokens: request.max_tokens.unwrap_or(self.max_tokens_default),
            temperature,
            messages,
            system,
        };

        let spec = HttpRequestSpec {
            url: self.full_url.clone(),
            headers: vec![
                ("x-api-key".to_string(), self.api_key.clone()),
                ("anthropic-version".to_string(), self.api_version.clone()),
                ("content-type".to_string(), "application/json".to_string()),
            ],
            body: serde_json::to_value(body)?,
            timeout: self.timeout,
        };

        let response: AnthropicResponse =
            serde_json::from_value(post_json(&self.client, spec).await?)?;

        let text = response
            .content
            .into_iter()
            .filter(|block| block.kind == "text")
            .filter_map(|block| block.text)
            .collect::<Vec<_>>()
            .join("\n");

        let usage = response.usage.map(|u| ChatUsage {
            prompt_tokens: u.input_tokens,
            completion_tokens: u.output_tokens,
            total_tokens: u.input_tokens.saturating_add(u.output_tokens),
        });

        Ok(ChatResponse {
            id: response.id,
            choices: vec![ChatChoice {
                index: 0,
                message: ChatMessage::assistant(text),
                finish_reason: Some("stop".to_string()),
            }],
            usage,
        })
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::config::{
        AuthConfig, AuthType, CapabilitiesConfig, CostConfig, GovernanceConfig, LifecycleStatus,
        Modality, Protocol, RuntimeConfig, SecretType, SecretsConfig,
    };

    fn create_test_config() -> ModelConfig {
        ModelConfig {
            display_name: "Claude Test".to_string(),
            provider_name: "anthropic".to_string(),
            input_modality: vec![Modality::Text],
            output_modality: vec![Modality::Text],
            runtime: RuntimeConfig {
                base_url: "https://api.anthropic.com".to_string(),
                full_url: "https://api.anthropic.com/v1/messages".to_string(),
                protocol: Protocol::OpenAICompatible,
                api_version: "2023-06-01".to_string(),
                use_max_completion_tokens: false,
                has_temperature_supported: true,
                additional_notes: vec![],
                model_param: false,
                timeout_seconds: 60,
                max_tokens_default: 4096,
                temperature_default: 0.7,
            },
            auth: AuthConfig {
                auth_type: AuthType::Bearer,
            },
            secrets: SecretsConfig {
                secret_type: SecretType::Environment,
                name: "ANTHROPIC_API_KEY".to_string(),
            },
            capabilities: CapabilitiesConfig {
                tool_calls: true,
                reasoning: false,
            },
            governance: GovernanceConfig {
                is_use_approve: true,
                approved_by: Some("admin".to_string()),
                approval_date: Some("2024-01-01".to_string()),
                lifecycle_status: LifecycleStatus::Active,
            },
            cost: CostConfig {
                provider: Some("anthropic".to_string()),
                provider_key: Some("claude-3-sonnet".to_string()),
                currency: Some("USD".to_string()),
                cost_per_1m_input: Some(0.003),
                cost_per_1m_output: Some(0.015),
                cost_per_1m_cached_input: None,
                cost_per_1m_cached_output: None,
                cost_per_1m_pixels: None,
            },
            fallbacks: vec![],
        }
    }

    #[test]
    fn test_from_model_config() {
        let config = create_test_config();
        let provider =
            Anthropic::from_model_config("anthropic:text:claude", &config, "test-key").unwrap();

        assert_eq!(provider.name(), "anthropic");
        assert_eq!(provider.model(), "anthropic:text:claude");
        assert_eq!(provider.anthropic_model, "claude-3-sonnet");
    }

    #[test]
    fn test_capabilities_from_config() {
        let config = create_test_config();
        let provider = Anthropic::from_model_config("test", &config, "key").unwrap();

        let caps = provider.capabilities();
        assert!(caps.supports_tools);
        assert!(caps.supports_vision);
    }

    #[test]
    fn test_convert_messages_extracts_system() {
        let messages = vec![
            ChatMessage::system("You are a helpful assistant"),
            ChatMessage::user("Hello"),
        ];

        let (system, converted) = Anthropic::convert_messages(&messages);

        assert_eq!(system, Some("You are a helpful assistant".to_string()));
        assert_eq!(converted.len(), 1);
        assert_eq!(converted[0].role, "user");
    }
}
