//! Anthropic (Claude) client implementation.
//!
//! This module provides the Anthropic provider implementation for chat completions
//! and streaming responses.

use crate::streaming::AnthropicStreamingResponse;
use aix_core::{
    error::{AixError, AixResult},
    retry::RetryStrategy,
    streaming::TokenStream,
    traits::{AiProvider, ModelCapabilities},
    types::{ChatRequest, ChatResponse, Role, Usage},
};
use async_trait::async_trait;
use futures_util::TryStreamExt;
use reqwest::{Client, StatusCode};
use serde::{Deserialize, Serialize};
use std::time::Duration;

/// Default Anthropic API base URL.
pub const DEFAULT_BASE_URL: &str = "https://api.anthropic.com";

/// Default Anthropic API version.
pub const DEFAULT_API_VERSION: &str = "2023-06-01";

/// Configuration for the Anthropic provider.
#[derive(Debug, Clone)]
pub struct AnthropicConfig {
    /// Anthropic API key
    pub api_key: String,
    /// Base URL for the Anthropic API
    pub base_url: String,
    /// API version
    pub api_version: String,
    /// Request timeout
    pub timeout: Duration,
    /// Retry configuration
    pub retry_config: aix_core::retry::RetryConfig,
}

impl AnthropicConfig {
    /// Create a new Anthropic configuration with the given API key.
    pub fn new<S: Into<String>>(api_key: S) -> Self {
        Self {
            api_key: api_key.into(),
            base_url: DEFAULT_BASE_URL.to_string(),
            api_version: DEFAULT_API_VERSION.to_string(),
            timeout: Duration::from_secs(60),
            retry_config: aix_core::retry::RetryConfig::default(),
        }
    }

    /// Set the base URL.
    pub fn with_base_url<S: Into<String>>(mut self, base_url: S) -> Self {
        self.base_url = base_url.into();
        self
    }

    /// Set the API version.
    pub fn with_api_version<S: Into<String>>(mut self, api_version: S) -> Self {
        self.api_version = api_version.into();
        self
    }

    /// Set the timeout.
    pub fn with_timeout(mut self, timeout: Duration) -> Self {
        self.timeout = timeout;
        self
    }

    /// Set the retry configuration.
    pub fn with_retry_config(mut self, retry_config: aix_core::retry::RetryConfig) -> Self {
        self.retry_config = retry_config;
        self
    }

    /// Build the Anthropic provider.
    pub fn build(self) -> AixResult<AnthropicProvider> {
        AnthropicProvider::new(self)
    }
}

/// Anthropic provider implementation.
pub struct AnthropicProvider {
    config: AnthropicConfig,
    client: Client,
    retry: RetryStrategy,
}

impl AnthropicProvider {
    /// Create a new Anthropic provider with the given configuration.
    pub fn new(config: AnthropicConfig) -> AixResult<Self> {
        let client = Client::builder()
            .timeout(config.timeout)
            .build()
            .map_err(|e| AixError::transport(e.to_string(), "creating HTTP client"))?;

        let retry = RetryStrategy::new(config.retry_config.clone());

        Ok(Self {
            config,
            client,
            retry,
        })
    }

    /// Get a reference to the configuration.
    pub fn config(&self) -> &AnthropicConfig {
        &self.config
    }

    /// Convert a chat request to Anthropic's request format.
    fn to_anthropic_request(&self, request: &ChatRequest) -> AixResult<AnthropicRequest> {
        // Separate system message from user/assistant messages
        let mut system_message = None;
        let messages: Vec<AnthropicMessage> = request
            .messages
            .iter()
            .filter_map(|m| {
                if m.role == Role::System {
                    system_message = Some(m.content.clone());
                    None
                } else {
                    Some(AnthropicMessage {
                        role: match m.role {
                            Role::User => "user".to_string(),
                            Role::Assistant => "assistant".to_string(),
                            _ => "user".to_string(), // Default to user for unsupported roles
                        },
                        content: vec![AnthropicContent {
                            type_: "text".to_string(),
                            text: m.content.clone(),
                        }],
                    })
                }
            })
            .collect();

        // Anthropic requires max_tokens
        let max_tokens = request.config.max_tokens.unwrap_or(4096);

        Ok(AnthropicRequest {
            model: request.model.clone(),
            messages,
            system: system_message,
            max_tokens,
            temperature: request.config.temperature,
            top_p: request.config.top_p,
            stop_sequences: if request.config.stop_sequences.is_empty() {
                None
            } else {
                Some(request.config.stop_sequences.clone())
            },
            stream: request.stream,
        })
    }

    /// Convert Anthropic response to our format.
    fn from_anthropic_response(&self, response: AnthropicResponse) -> ChatResponse {
        let usage = Usage::new(
            response.usage.input_tokens,
            response.usage.output_tokens,
        );

        ChatResponse {
            id: response.id,
            model: response.model,
            content: response.content.first().unwrap().text.clone(),
            role: Role::Assistant,
            usage,
            finish_reason: response.stop_reason,
        }
    }

    /// Execute an HTTP request with retry logic.
    async fn execute_request(&self, request: AnthropicRequest) -> AixResult<AnthropicResponse> {
        let url = format!("{}/v1/messages", self.config.base_url);
        
        self.retry.execute(|| async {
            let response = self.client
                .post(&url)
                .header("x-api-key", &self.config.api_key)
                .header("anthropic-version", &self.config.api_version)
                .header("Content-Type", "application/json")
                .json(&request)
                .send()
                .await
                .map_err(|e| AixError::transport(e.to_string(), "sending request"))?;

            let status = response.status();
            let response_text = response
                .text()
                .await
                .map_err(|e| AixError::transport(e.to_string(), "reading response body"))?;

            if status.is_success() {
                serde_json::from_str(&response_text)
                    .map_err(|e| AixError::serialization(e.to_string(), "parsing Anthropic response"))
            } else {
                Err(self.map_error(status, &response_text))
            }
        }).await
    }

    /// Map HTTP errors to AixError.
    fn map_error(&self, status: StatusCode, body: &str) -> AixError {
        match status.as_u16() {
            401 | 403 => {
                AixError::auth("anthropic", format!("Authentication failed: {}", body))
            }
            429 => {
                AixError::rate_limit("anthropic", format!("Rate limit exceeded: {}", body))
            }
            400 => {
                AixError::provider_with_details("anthropic", format!("Bad request: {}", body), 400, "bad_request")
            }
            500..=599 => {
                AixError::provider_with_details("anthropic", format!("Server error: {}", body), status.as_u16(), "server_error")
            }
            _ => {
                AixError::provider_with_details("anthropic", format!("HTTP error {}: {}", status.as_u16(), body), status.as_u16(), "http_error")
            }
        }
    }

    /// Execute a streaming request.
    async fn execute_streaming_request(&self, request: AnthropicRequest) -> AixResult<TokenStream> {
        let url = format!("{}/v1/messages", self.config.base_url);
        
        let response = self.client
            .post(&url)
            .header("x-api-key", &self.config.api_key)
            .header("anthropic-version", &self.config.api_version)
            .header("Content-Type", "application/json")
            .json(&request)
            .send()
            .await
            .map_err(|e| AixError::transport(e.to_string(), "sending streaming request"))?;

        if response.status().is_success() {
            let byte_stream = response
                .bytes_stream()
                .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e));
            
            Ok(Box::pin(AnthropicStreamingResponse::new(byte_stream)))
        } else {
            let status = response.status();
            let body = response
                .text()
                .await
                .map_err(|e| AixError::transport(e.to_string(), "reading error response"))?;
            
            Err(self.map_error(status, &body))
        }
    }
}

#[async_trait]
impl AiProvider for AnthropicProvider {
    async fn chat(&self, request: ChatRequest) -> AixResult<ChatResponse> {
        self.validate_request(&request)?;
        
        let anthropic_request = self.to_anthropic_request(&request)?;
        let anthropic_response = self.execute_request(anthropic_request).await?;
        
        Ok(self.from_anthropic_response(anthropic_response))
    }

    async fn chat_stream(&self, request: ChatRequest) -> AixResult<TokenStream> {
        self.validate_request(&request)?;
        
        if !request.stream {
            return Err(AixError::config("Stream must be enabled for streaming requests"));
        }

        let mut anthropic_request = self.to_anthropic_request(&request)?;
        anthropic_request.stream = true;

        self.execute_streaming_request(anthropic_request).await
    }

    fn provider_name(&self) -> &str {
        "anthropic"
    }

    fn capabilities(&self) -> ModelCapabilities {
        ModelCapabilities::new(
            true,  // supports_streaming
            true,  // supports_function_calling
            true,  // supports_vision
            4096,  // max_tokens (varies by model)
            200000, // max_context_window (varies by model)
        )
    }

    fn validate_request(&self, request: &ChatRequest) -> AixResult<()> {
        // Anthropic-specific validation
        // Anthropic requires at least one user message (system messages are handled separately)
        let has_user_message = request.messages.iter().any(|m| m.role == Role::User);
        if !has_user_message {
            return Err(AixError::config("Anthropic requires at least one user message"));
        }

        // Anthropic requires max_tokens
        if request.config.max_tokens.is_none() {
            return Err(AixError::config("Anthropic requires max_tokens to be specified"));
        }

        Ok(())
    }
}

/// Anthropic request format.
#[derive(Debug, Serialize)]
struct AnthropicRequest {
    model: String,
    messages: Vec<AnthropicMessage>,
    system: Option<String>,
    max_tokens: u32,
    temperature: Option<f32>,
    top_p: Option<f32>,
    stop_sequences: Option<Vec<String>>,
    stream: bool,
}

/// Anthropic message format.
#[derive(Debug, Serialize)]
struct AnthropicMessage {
    role: String,
    content: Vec<AnthropicContent>,
}

/// Anthropic content format.
#[derive(Debug, Serialize)]
struct AnthropicContent {
    #[serde(rename = "type")]
    type_: String,
    text: String,
}

/// Anthropic response format.
#[derive(Debug, Deserialize)]
struct AnthropicResponse {
    id: String,
    type_: String,
    role: String,
    content: Vec<AnthropicResponseContent>,
    model: String,
    stop_reason: Option<String>,
    stop_sequence: Option<String>,
    usage: AnthropicUsage,
}

/// Anthropic response content format.
#[derive(Debug, Deserialize)]
struct AnthropicResponseContent {
    type_: String,
    text: String,
}

/// Anthropic usage format.
#[derive(Debug, Deserialize)]
struct AnthropicUsage {
    input_tokens: u32,
    output_tokens: u32,
}

/// Anthropic streaming response format.
#[derive(Debug, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
enum AnthropicStreamEvent {
    MessageStart {
        message: AnthropicMessageStart,
    },
    ContentBlockStart {
        content_block: AnthropicContentBlockStart,
    },
    ContentBlockDelta {
        delta: AnthropicDelta,
    },
    ContentBlockStop,
    MessageDelta {
        delta: AnthropicMessageDelta,
    },
    MessageStop,
}

/// Anthropic message start event.
#[derive(Debug, Deserialize)]
struct AnthropicMessageStart {
    id: String,
    type_: String,
    role: String,
    content: Vec<AnthropicResponseContent>,
    model: String,
    usage: AnthropicUsage,
}

/// Anthropic content block start event.
#[derive(Debug, Deserialize)]
struct AnthropicContentBlockStart {
    type_: String,
    text: String,
}

/// Anthropic delta event.
#[derive(Debug, Deserialize)]
struct AnthropicDelta {
    type_: String,
    text: Option<String>,
}

/// Anthropic message delta event.
#[derive(Debug, Deserialize)]
struct AnthropicMessageDelta {
    stop_reason: Option<String>,
    stop_sequence: Option<String>,
}

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

    #[test]
    fn test_anthropic_config() {
        let config = AnthropicConfig::new("test-key")
            .with_base_url("https://api.anthropic.com")
            .with_api_version("2023-06-01")
            .with_timeout(Duration::from_secs(30))
            .with_retry_config(aix_core::retry::RetryConfig::no_retry());

        assert_eq!(config.api_key, "test-key");
        assert_eq!(config.base_url, "https://api.anthropic.com");
        assert_eq!(config.api_version, "2023-06-01");
        assert_eq!(config.timeout, Duration::from_secs(30));
        assert_eq!(config.retry_config.max_attempts, 1);
    }

    #[test]
    fn test_request_conversion() {
        let provider = AnthropicProvider::new(AnthropicConfig::new("test-key")).unwrap();
        let request = ChatRequest::new("claude-3-sonnet")
            .message(ChatMessage::system("You are a helpful assistant."))
            .message(ChatMessage::user("Hello!"))
            .config(ModelConfig::new().with_temperature(0.7).with_max_tokens(1000))
            .build();

        let anthropic_request = provider.to_anthropic_request(&request).unwrap();
        
        assert_eq!(anthropic_request.model, "claude-3-sonnet");
        assert_eq!(anthropic_request.messages.len(), 1); // System message is separated
        assert_eq!(anthropic_request.messages[0].role, "user");
        assert_eq!(anthropic_request.messages[0].content[0].text, "Hello!");
        assert_eq!(anthropic_request.system, Some("You are a helpful assistant.".to_string()));
        assert_eq!(anthropic_request.temperature, Some(0.7));
        assert_eq!(anthropic_request.max_tokens, 1000);
    }

    #[test]
    fn test_response_conversion() {
        let provider = AnthropicProvider::new(AnthropicConfig::new("test-key")).unwrap();
        
        let anthropic_response = AnthropicResponse {
            id: "msg_123".to_string(),
            type_: "message".to_string(),
            role: "assistant".to_string(),
            content: vec![AnthropicResponseContent {
                type_: "text".to_string(),
                text: "Hello! How can I help you?".to_string(),
            }],
            model: "claude-3-sonnet".to_string(),
            stop_reason: Some("end_turn".to_string()),
            stop_sequence: None,
            usage: AnthropicUsage {
                input_tokens: 10,
                output_tokens: 20,
            },
        };

        let response = provider.from_anthropic_response(anthropic_response);
        
        assert_eq!(response.id, "msg_123");
        assert_eq!(response.model, "claude-3-sonnet");
        assert_eq!(response.content, "Hello! How can I help you?");
        assert_eq!(response.role, Role::Assistant);
        assert_eq!(response.usage.prompt_tokens, 10);
        assert_eq!(response.usage.completion_tokens, 20);
        assert_eq!(response.finish_reason, Some("end_turn".to_string()));
    }

    #[test]
    fn test_error_mapping() {
        let provider = AnthropicProvider::new(AnthropicConfig::new("test-key")).unwrap();
        
        // Test auth error
        let auth_error = provider.map_error(StatusCode::UNAUTHORIZED, "Invalid API key");
        assert!(matches!(auth_error, AixError::Auth { .. }));
        
        // Test rate limit error
        let rate_limit_error = provider.map_error(StatusCode::TOO_MANY_REQUESTS, "Rate limit exceeded");
        assert!(matches!(rate_limit_error, AixError::RateLimit { .. }));
        
        // Test server error
        let server_error = provider.map_error(StatusCode::INTERNAL_SERVER_ERROR, "Internal server error");
        assert!(matches!(server_error, AixError::Provider { status: Some(500), .. }));
    }

    #[test]
    fn test_anthropic_validation() {
        let provider = AnthropicProvider::new(AnthropicConfig::new("test-key")).unwrap();
        
        // Valid request should pass
        let valid_request = ChatRequest::new("claude-3-sonnet")
            .message(ChatMessage::user("Hello"))
            .config(ModelConfig::new().with_max_tokens(1000))
            .build();
        assert!(provider.validate_request(&valid_request).is_ok());

        // Request without user message should fail
        let no_user_request = ChatRequest::new("claude-3-sonnet")
            .message(ChatMessage::system("You are helpful"))
            .config(ModelConfig::new().with_max_tokens(1000))
            .build();
        assert!(provider.validate_request(&no_user_request).is_err());

        // Request without max_tokens should fail
        let no_max_tokens_request = ChatRequest::new("claude-3-sonnet")
            .message(ChatMessage::user("Hello"))
            .build();
        assert!(provider.validate_request(&no_max_tokens_request).is_err());
    }

    #[tokio::test]
    async fn test_provider_capabilities() {
        let provider = AnthropicProvider::new(AnthropicConfig::new("test-key")).unwrap();
        
        assert!(provider.supports_streaming());
        assert!(provider.supports_function_calling());
        assert!(provider.supports_vision());
        assert_eq!(provider.provider_name(), "anthropic");
    }
}
