//! Anthropic Claude LLM provider

use super::super::error::{LlmError, Result};
use super::super::messages::{Message, Role};
use super::super::options::GenerationOptions;
use super::super::{LlmProvider, StreamResponse};
use async_trait::async_trait;
use futures::stream::StreamExt;
use reqwest::Client;
use serde::{Deserialize, Serialize};
use std::sync::Arc;

const ANTHROPIC_API_URL: &str = "https://api.anthropic.com/v1/messages";

/// Anthropic API request
#[derive(Debug, Serialize)]
struct AnthropicRequest {
    model: String,
    max_tokens: usize,
    system: String,
    messages: Vec<AnthropicMessage>,
    temperature: f32,
    stream: bool,
}

/// Anthropic message format
#[derive(Debug, Serialize, Deserialize)]
struct AnthropicMessage {
    role: String,
    content: String,
}

/// Anthropic API response
#[derive(Debug, Deserialize)]
struct AnthropicResponse {
    content: Vec<ContentBlock>,
}

/// Content block in response
#[derive(Debug, Deserialize)]
struct ContentBlock {
    #[serde(default)]
    text: String,
}

/// Streaming event
#[derive(Debug, Deserialize)]
#[serde(tag = "type")]
#[serde(rename_all = "snake_case")]
enum StreamEvent {
    ContentBlockDelta {
        delta: StreamDelta,
    },
    #[serde(other)]
    Other,
}

/// Stream delta
#[derive(Debug, Deserialize)]
struct StreamDelta {
    #[serde(default)]
    text: String,
}

/// Anthropic provider
pub struct AnthropicProvider {
    api_key: String,
    model: String,
    client: Arc<Client>,
    api_version: String,
}

impl AnthropicProvider {
    /// Create new Anthropic provider
    ///
    /// # Arguments
    /// * `api_key` - Anthropic API key
    /// * `model` - Model name (e.g., "claude-3-opus", "claude-3-sonnet")
    ///
    /// # Errors
    /// Returns error if api_key is empty
    pub fn new(api_key: impl Into<String>, model: impl Into<String>) -> Result<Self> {
        let api_key = api_key.into();
        if api_key.is_empty() {
            return Err(LlmError::ConfigError("Anthropic API key is empty".into()));
        }

        Ok(AnthropicProvider {
            api_key,
            model: model.into(),
            client: Arc::new(Client::new()),
            api_version: "2023-06-01".to_string(),
        })
    }

    /// Create from environment variable `ANTHROPIC_API_KEY`
    pub fn from_env(model: impl Into<String>) -> Result<Self> {
        let api_key = std::env::var("ANTHROPIC_API_KEY")
            .map_err(|_| LlmError::ConfigError("ANTHROPIC_API_KEY not set".into()))?;
        Self::new(api_key, model)
    }

    fn build_request(
        &self,
        messages: &[Message],
        options: &GenerationOptions,
    ) -> Result<AnthropicRequest> {
        // Extract system message if present
        let system = messages
            .iter()
            .find(|m| m.role == Role::System)
            .map(|m| m.content.clone())
            .unwrap_or_else(|| "You are a helpful assistant".to_string());

        // Filter out system messages from message list (Anthropic expects them separately)
        let anthropic_messages = messages
            .iter()
            .filter(|m| m.role != Role::System)
            .map(|msg| AnthropicMessage {
                role: match msg.role {
                    Role::System => unreachable!(),
                    Role::User => "user",
                    Role::Assistant => "assistant",
                }
                .to_string(),
                content: msg.content.clone(),
            })
            .collect();

        Ok(AnthropicRequest {
            model: self.model.clone(),
            max_tokens: options.max_tokens.unwrap_or(2048),
            system,
            messages: anthropic_messages,
            temperature: options.temperature.clamp(0.0, 1.0),
            stream: false,
        })
    }

    fn build_stream_request(
        &self,
        messages: &[Message],
        options: &GenerationOptions,
    ) -> Result<AnthropicRequest> {
        let mut req = self.build_request(messages, options)?;
        req.stream = true;
        Ok(req)
    }
}

#[async_trait]
impl LlmProvider for AnthropicProvider {
    async fn generate(&self, messages: &[Message], options: &GenerationOptions) -> Result<String> {
        let req = self.build_request(messages, options)?;

        let response = self
            .client
            .post(ANTHROPIC_API_URL)
            .header("x-api-key", &self.api_key)
            .header("anthropic-version", &self.api_version)
            .header("content-type", "application/json")
            .json(&req)
            .send()
            .await
            .map_err(|e| {
                if e.is_timeout() {
                    LlmError::Timeout("Anthropic request timed out".into())
                } else if e.status().is_some_and(|s| s.as_u16() == 429) {
                    LlmError::RateLimit("Anthropic rate limited".into())
                } else {
                    LlmError::NetworkError(e)
                }
            })?;

        let status = response.status();
        if !status.is_success() {
            let error_text = response
                .text()
                .await
                .unwrap_or_else(|_| "Unknown error".to_string());
            return Err(LlmError::ApiError(format!(
                "Anthropic API error ({}): {}",
                status, error_text
            )));
        }

        let body: AnthropicResponse = response
            .json()
            .await
            .map_err(|e| LlmError::ApiError(format!("Failed to parse response: {}", e)))?;

        body.content
            .into_iter()
            .next()
            .map(|block| block.text)
            .ok_or_else(|| LlmError::ApiError("No content in response".into()))
    }

    async fn stream(
        &self,
        messages: &[Message],
        options: &GenerationOptions,
    ) -> Result<StreamResponse> {
        let req = self.build_stream_request(messages, options)?;

        let response = self
            .client
            .post(ANTHROPIC_API_URL)
            .header("x-api-key", &self.api_key)
            .header("anthropic-version", &self.api_version)
            .header("content-type", "application/json")
            .json(&req)
            .send()
            .await
            .map_err(|e| {
                if e.is_timeout() {
                    LlmError::Timeout("Anthropic request timed out".into())
                } else {
                    LlmError::NetworkError(e)
                }
            })?;

        let status = response.status();
        if !status.is_success() {
            return Err(LlmError::ApiError(format!(
                "Anthropic API error: {}",
                status
            )));
        }

        let stream = response
            .bytes_stream()
            .filter_map(|result| async move {
                match result {
                    Ok(bytes) => {
                        let text = String::from_utf8_lossy(&bytes);
                        let text = text.trim();
                        if text.is_empty() {
                            return None;
                        }

                        for line in text.lines() {
                            if let Some(data) = line.strip_prefix("data: ") {
                                if let Ok(event) = serde_json::from_str::<StreamEvent>(data) {
                                    match event {
                                        StreamEvent::ContentBlockDelta { delta } => {
                                            if !delta.text.is_empty() {
                                                return Some(Ok(delta.text));
                                            }
                                        }
                                        StreamEvent::Other => {}
                                    }
                                }
                            }
                        }
                        None
                    }
                    Err(e) => Some(Err(LlmError::StreamError(e.to_string()))),
                }
            })
            .boxed();

        Ok(Box::pin(stream))
    }

    fn name(&self) -> &str {
        "Anthropic"
    }

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

    async fn is_available(&self) -> bool {
        let req = AnthropicRequest {
            model: self.model.clone(),
            max_tokens: 1,
            system: "test".to_string(),
            messages: vec![AnthropicMessage {
                role: "user".to_string(),
                content: "test".to_string(),
            }],
            temperature: 0.7,
            stream: false,
        };

        self.client
            .post(ANTHROPIC_API_URL)
            .header("x-api-key", &self.api_key)
            .header("anthropic-version", &self.api_version)
            .json(&req)
            .send()
            .await
            .map(|r| r.status().is_success())
            .unwrap_or(false)
    }
}

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

    #[test]
    fn test_anthropic_new() {
        let provider = AnthropicProvider::new("sk-ant-test", "claude-3-opus");
        assert!(provider.is_ok());
    }

    #[test]
    fn test_anthropic_empty_key() {
        let provider = AnthropicProvider::new("", "claude-3-opus");
        assert!(provider.is_err());
    }

    #[test]
    fn test_provider_name() {
        let provider = AnthropicProvider::new("sk-ant-test", "claude-3-opus").unwrap();
        assert_eq!(provider.name(), "Anthropic");
        assert_eq!(provider.model(), "claude-3-opus");
    }

    #[test]
    fn test_build_request() {
        let provider = AnthropicProvider::new("sk-ant-test", "claude-3-opus").unwrap();
        let messages = vec![Message::system("You are helpful"), Message::user("Hello")];
        let options = GenerationOptions::default();

        let req = provider.build_request(&messages, &options).unwrap();
        assert_eq!(req.model, "claude-3-opus");
        assert_eq!(req.messages.len(), 1); // System filtered out
        assert!(!req.stream);
    }
}
