//! Anthropic Messages API Client (Phase-78: T78.7)
//!
//! Implements `LlmClient` for the Anthropic Messages API protocol.
//!
//! # Key differences from OpenAI
//!
//! - Uses `x-api-key` header (not `Authorization: Bearer`)
//! - Requires `anthropic-version` header (e.g., "2023-06-01")
//! - System prompt via top-level `system` field (not a message)
//! - Streaming uses `event: content_block_delta` / `message_delta` events
//! - Thinking/reasoning via `type: "thinking"` content blocks
//! - Image content via base64 `source` objects

use std::pin::Pin;
use std::time::Instant;

use async_trait::async_trait;
use reqwest::Client as HttpClient;
use serde::Deserialize;
use serde_json::{json, Value};
use tokio::sync::mpsc;
use tracing::{debug, warn, trace};

use super::client::{ContentPart, LlmClient, LlmMessage, MessageRole};
use super::error::{LlmError, LlmResult};
use super::types::{
    ChatConfig, ChatResponse, FinishReason, LlmProvider, StreamEvent, TokenUsage, ToolCall,
};

// ============================================================================
// Anthropic API types
// ============================================================================

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

#[derive(Debug, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
enum AnthropicContentBlock {
    Text {
        text: String,
    },
    Thinking {
        thinking: String,
    },
    ToolUse {
        id: String,
        name: String,
        input: Value,
    },
}

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

#[derive(Debug, Deserialize)]
struct AnthropicErrorResponse {
    error: Option<AnthropicErrorDetail>,
}

#[derive(Debug, Deserialize)]
struct AnthropicErrorDetail {
    message: String,
    #[allow(dead_code)]
    r#type: Option<String>,
}

// Streaming event types
#[derive(Debug, Deserialize)]
#[allow(dead_code)]
struct AnthropicStreamEvent {
    r#type: String,
    index: Option<usize>,
    delta: Option<AnthropicStreamDelta>,
    content_block: Option<AnthropicContentBlockStart>,
    message: Option<AnthropicStreamMessage>,
    usage: Option<AnthropicUsage>,
}

#[derive(Debug, Deserialize)]
#[serde(untagged)]
#[allow(dead_code)]
enum AnthropicStreamDelta {
    TextDelta {
        r#type: String,
        text: Option<String>,
    },
    ThinkingDelta {
        r#type: String,
        thinking: Option<String>,
    },
    InputJsonDelta {
        r#type: String,
        partial_json: Option<String>,
    },
    StopDelta {
        r#type: String,
        stop_reason: Option<String>,
    },
}

#[derive(Debug, Deserialize)]
struct AnthropicContentBlockStart {
    r#type: String,
    #[allow(dead_code)]
    id: Option<String>,
    name: Option<String>,
}

#[derive(Debug, Deserialize)]
struct AnthropicStreamMessage {
    #[allow(dead_code)]
    id: Option<String>,
    usage: Option<AnthropicUsage>,
}

// ============================================================================
// AnthropicClient
// ============================================================================

/// Anthropic Messages API client.
pub struct AnthropicClient {
    provider: LlmProvider,
    http: HttpClient,
    /// Anthropic API version header.
    api_version: String,
}

impl AnthropicClient {
    /// Default Anthropic API version.
    const DEFAULT_API_VERSION: &'static str = "2023-06-01";

    /// Create a new Anthropic client.
    pub fn new(provider: LlmProvider) -> LlmResult<Self> {
        let http = HttpClient::builder()
            .timeout(std::time::Duration::from_secs(provider.timeout_secs as u64))
            .build()
            .map_err(|e| LlmError::ConfigError(format!("Failed to create HTTP client: {e}")))?;

        let api_version = provider
            .api_version
            .clone()
            .unwrap_or_else(|| Self::DEFAULT_API_VERSION.to_string());

        Ok(Self {
            provider,
            http,
            api_version,
        })
    }

    /// Build the messages API URL.
    fn messages_url(&self) -> String {
        let base = self.provider.base_url.trim_end_matches('/');
        if base.ends_with("/messages") {
            base.to_string()
        } else if base.ends_with("/v1") {
            format!("{base}/messages")
        } else {
            format!("{base}/v1/messages")
        }
    }

    /// Convert LlmMessages to Anthropic format.
    ///
    /// Returns: (system_prompt, messages_array)
    fn convert_messages(&self, messages: &[LlmMessage]) -> (Option<String>, Vec<Value>) {
        let mut system_prompt = None;
        let mut api_messages: Vec<Value> = Vec::new();

        for msg in messages {
            match msg.role {
                MessageRole::System => {
                    // Anthropic: system is a top-level field, not a message
                    system_prompt = Some(msg.content.clone());
                }
                MessageRole::User => {
                    if msg.is_multimodal() {
                        let content: Vec<Value> = msg
                            .content_parts
                            .iter()
                            .map(|p| match p {
                                ContentPart::Text { text } => {
                                    json!({"type": "text", "text": text})
                                }
                                ContentPart::Image { url, mime_type } => {
                                    if url.starts_with("data:") {
                                        // Extract base64 data from data URI
                                        let parts: Vec<&str> = url.splitn(2, ',').collect();
                                        let data = parts.get(1).unwrap_or(&"");
                                        let media_type = mime_type
                                            .as_deref()
                                            .unwrap_or("image/png");
                                        json!({
                                            "type": "image",
                                            "source": {
                                                "type": "base64",
                                                "media_type": media_type,
                                                "data": data,
                                            }
                                        })
                                    } else {
                                        // URL-based image
                                        json!({
                                            "type": "image",
                                            "source": {
                                                "type": "url",
                                                "url": url,
                                            }
                                        })
                                    }
                                }
                                ContentPart::Audio { url, .. } => {
                                    json!({"type": "text", "text": format!("[Audio: {url}]")})
                                }
                            })
                            .collect();
                        api_messages.push(json!({
                            "role": "user",
                            "content": content,
                        }));
                    } else {
                        api_messages.push(json!({
                            "role": "user",
                            "content": msg.content.clone(),
                        }));
                    }
                }
                MessageRole::Assistant => {
                    let mut content_blocks: Vec<Value> = Vec::new();
                    if !msg.content.is_empty() {
                        content_blocks.push(json!({"type": "text", "text": msg.content}));
                    }
                    for tc in &msg.tool_calls {
                        content_blocks.push(json!({
                            "type": "tool_use",
                            "id": tc.id,
                            "name": tc.name,
                            "input": serde_json::from_str::<Value>(&tc.arguments).unwrap_or(Value::Null),
                        }));
                    }
                    if content_blocks.is_empty() {
                        content_blocks.push(json!({"type": "text", "text": ""}));
                    }
                    api_messages.push(json!({
                        "role": "assistant",
                        "content": content_blocks,
                    }));
                }
                MessageRole::Tool => {
                    api_messages.push(json!({
                        "role": "user",
                        "content": [{
                            "type": "tool_result",
                            "tool_use_id": msg.tool_call_id.as_deref().unwrap_or(""),
                            "content": msg.content.clone(),
                        }],
                    }));
                }
            }
        }

        (system_prompt, api_messages)
    }

    /// Build the request body.
    fn build_body(
        &self,
        model_name: &str,
        messages: &[LlmMessage],
        config: &ChatConfig,
        stream: bool,
    ) -> Value {
        let (system_prompt, api_messages) = self.convert_messages(messages);

        let mut body = json!({
            "model": model_name,
            "messages": api_messages,
            "stream": stream,
        });

        if let Some(sys) = system_prompt {
            body["system"] = json!(sys);
        }

        if let Some(max_tokens) = config.max_tokens {
            body["max_tokens"] = json!(max_tokens);
        } else {
            // Anthropic requires max_tokens
            body["max_tokens"] = json!(4096);
        }

        if let Some(temp) = config.temperature {
            body["temperature"] = json!(temp);
        }
        if let Some(top_p) = config.top_p {
            body["top_p"] = json!(top_p);
        }
        if let Some(top_k) = config.top_k {
            body["top_k"] = json!(top_k);
        }
        if !config.stop.is_empty() {
            body["stop_sequences"] = json!(config.stop);
        }

        // Enable thinking/extended thinking
        if config.enable_thinking {
            body["metadata"] = json!({"thinking": {"type": "enabled", "budget_tokens": 10000}});
        }

        // Tools
        if !config.tools.is_empty() {
            let tools: Vec<Value> = config
                .tools
                .iter()
                .map(|t| {
                    json!({
                        "name": t.function.name,
                        "description": t.function.description,
                        "input_schema": t.function.parameters,
                    })
                })
                .collect();
            body["tools"] = json!(tools);
        }

        body
    }

    /// Parse Anthropic stop reason to FinishReason.
    fn parse_stop_reason(reason: Option<&str>) -> FinishReason {
        match reason {
            Some("end_turn") | None => FinishReason::Stop,
            Some("max_tokens") => FinishReason::Length,
            Some("tool_use") => FinishReason::ToolCalls,
            Some("stop_sequence") => FinishReason::Stop,
            Some(other) => FinishReason::Other(other.to_string()),
        }
    }

    /// Send an authenticated request.
    async fn send_request(&self, body: &Value, stream: bool) -> LlmResult<reqwest::Response> {
        let url = self.messages_url();
        debug!(url = %url, stream, "Sending Anthropic request");

        let api_key = self
            .provider
            .api_key
            .as_deref()
            .ok_or_else(|| LlmError::AuthError("Anthropic API key is required".into()))?;

        let resp = self
            .http
            .post(&url)
            .header("x-api-key", api_key)
            .header("anthropic-version", &self.api_version)
            .header("content-type", "application/json")
            .json(body)
            .send()
            .await
            .map_err(|e| {
                if e.is_timeout() {
                    LlmError::RequestError(format!("Anthropic timeout: {e}"))
                } else if e.is_connect() {
                    LlmError::ProviderUnhealthy(format!("Cannot reach Anthropic: {e}"))
                } else {
                    LlmError::RequestError(format!("Anthropic request error: {e}"))
                }
            })?;

        let status = resp.status();
        if !status.is_success() {
            let text = resp.text().await.unwrap_or_default();
            if let Ok(err) = serde_json::from_str::<AnthropicErrorResponse>(&text) {
                if let Some(detail) = err.error {
                    return match status.as_u16() {
                        401 => Err(LlmError::AuthError(detail.message)),
                        429 => Err(LlmError::RateLimitExceeded(detail.message)),
                        _ => Err(LlmError::RequestError(format!(
                            "Anthropic HTTP {status}: {}",
                            detail.message
                        ))),
                    };
                }
            }
            return Err(LlmError::RequestError(format!(
                "Anthropic HTTP {status}: {text}"
            )));
        }

        Ok(resp)
    }
}

#[async_trait]
impl LlmClient for AnthropicClient {
    async fn chat(
        &self,
        messages: &[LlmMessage],
        config: &ChatConfig,
    ) -> LlmResult<ChatResponse> {
        let model_name = &self.provider.id;
        let body = self.build_body(model_name, messages, config, false);

        let start = Instant::now();
        let resp = self.send_request(&body, false).await?;
        let duration_ms = start.elapsed().as_millis() as u64;

        let anthropic_resp: AnthropicResponse = resp.json().await.map_err(|e| {
            LlmError::ResponseParseError(format!("Failed to parse Anthropic response: {e}"))
        })?;

        let mut content = String::new();
        let mut thinking = None;
        let mut tool_calls = Vec::new();

        for block in &anthropic_resp.content {
            match block {
                AnthropicContentBlock::Text { text } => {
                    if !content.is_empty() {
                        content.push('\n');
                    }
                    content.push_str(text);
                }
                AnthropicContentBlock::Thinking { thinking: t } => {
                    thinking = Some(t.clone());
                }
                AnthropicContentBlock::ToolUse { id, name, input } => {
                    tool_calls.push(ToolCall {
                        id: id.clone(),
                        name: name.clone(),
                        arguments: input.to_string(),
                    });
                }
            }
        }

        let usage = anthropic_resp.usage.map(|u| TokenUsage {
            prompt_tokens: u.input_tokens,
            completion_tokens: u.output_tokens,
            total_tokens: u.input_tokens + u.output_tokens,
            ..Default::default()
        });

        Ok(ChatResponse {
            content,
            thinking,
            tool_calls,
            usage,
            finish_reason: Self::parse_stop_reason(anthropic_resp.stop_reason.as_deref()),
            model: anthropic_resp.model.unwrap_or_else(|| model_name.to_string()),
            duration_ms: Some(duration_ms),
        })
    }

    async fn chat_stream(
        &self,
        messages: &[LlmMessage],
        config: &ChatConfig,
    ) -> LlmResult<Pin<Box<dyn futures_core::Stream<Item = LlmResult<StreamEvent>> + Send>>> {
        let model_name = self.provider.id.clone();
        let body = self.build_body(&model_name, messages, config, true);
        let resp = self.send_request(&body, true).await?;

        let (tx, rx) = mpsc::channel::<LlmResult<StreamEvent>>(64);

        tokio::spawn(async move {
            use tokio_stream::StreamExt;

            let mut byte_stream = resp.bytes_stream();
            let mut buffer = String::new();

            // Track active content block types by index
            let mut block_types: std::collections::HashMap<usize, String> = std::collections::HashMap::new();
            let mut tool_names: std::collections::HashMap<usize, String> = std::collections::HashMap::new();
            let mut tool_ids: std::collections::HashMap<usize, String> = std::collections::HashMap::new();

            while let Some(chunk_result) = byte_stream.next().await {
                let chunk = match chunk_result {
                    Ok(bytes) => match String::from_utf8(bytes.to_vec()) {
                        Ok(s) => s,
                        Err(e) => {
                            let _ = tx.send(Err(LlmError::StreamError(format!("Invalid UTF-8: {e}")))).await;
                            break;
                        }
                    },
                    Err(e) => {
                        let _ = tx.send(Err(LlmError::StreamError(format!("Stream error: {e}")))).await;
                        break;
                    }
                };

                buffer.push_str(&chunk);

                // Anthropic SSE: "event: type\ndata: {...}\n\n"
                while let Some(pos) = buffer.find("\n\n") {
                    let event_block = buffer[..pos].to_string();
                    buffer = buffer[pos + 2..].to_string();

                    let mut event_type = String::new();
                    let mut event_data = String::new();

                    for line in event_block.lines() {
                        let line = line.trim();
                        if let Some(et) = line.strip_prefix("event: ") {
                            event_type = et.trim().to_string();
                        } else if let Some(ed) = line.strip_prefix("data: ") {
                            event_data = ed.trim().to_string();
                        }
                    }

                    if event_data.is_empty() {
                        continue;
                    }

                    match event_type.as_str() {
                        "content_block_start" => {
                            if let Ok(evt) = serde_json::from_str::<AnthropicStreamEvent>(&event_data) {
                                let idx = evt.index.unwrap_or(0);
                                if let Some(block) = &evt.content_block {
                                    block_types.insert(idx, block.r#type.clone());
                                    if block.r#type == "tool_use" {
                                        if let Some(name) = &block.name {
                                            tool_names.insert(idx, name.clone());
                                        }
                                        if let Some(id) = &block.id {
                                            tool_ids.insert(idx, id.clone());
                                        }
                                    }
                                }
                            }
                        }
                        "content_block_delta" => {
                            if let Ok(evt) = serde_json::from_str::<AnthropicStreamEvent>(&event_data) {
                                let idx = evt.index.unwrap_or(0);
                                let block_type = block_types.get(&idx).map(|s| s.as_str()).unwrap_or("text");

                                if let Some(delta) = &evt.delta {
                                    match delta {
                                        AnthropicStreamDelta::TextDelta { text, .. } => {
                                            if let Some(t) = text {
                                                if !t.is_empty() {
                                                    if block_type == "thinking" {
                                                        let _ = tx.send(Ok(StreamEvent::ThinkingDelta { content: t.clone() })).await;
                                                    } else {
                                                        let _ = tx.send(Ok(StreamEvent::TextDelta { content: t.clone() })).await;
                                                    }
                                                }
                                            }
                                        }
                                        AnthropicStreamDelta::ThinkingDelta { thinking, .. } => {
                                            if let Some(t) = thinking {
                                                if !t.is_empty() {
                                                    let _ = tx.send(Ok(StreamEvent::ThinkingDelta { content: t.clone() })).await;
                                                }
                                            }
                                        }
                                        AnthropicStreamDelta::InputJsonDelta { partial_json, .. } => {
                                            if let Some(pj) = partial_json {
                                                let name = tool_names.get(&idx).cloned().unwrap_or_default();
                                                let id = tool_ids.get(&idx).cloned().unwrap_or_default();
                                                let _ = tx.send(Ok(StreamEvent::ToolCallDelta {
                                                    id,
                                                    name,
                                                    arguments_delta: pj.clone(),
                                                })).await;
                                            }
                                        }
                                        AnthropicStreamDelta::StopDelta { stop_reason, .. } => {
                                            if let Some(reason) = stop_reason {
                                                let fr = Self::parse_stop_reason(Some(reason.as_str()));
                                                let _ = tx.send(Ok(StreamEvent::Done {
                                                    message_id: String::new(),
                                                    finish_reason: fr,
                                                })).await;
                                            }
                                        }
                                    }
                                }
                            }
                        }
                        "message_delta" => {
                            if let Ok(evt) = serde_json::from_str::<AnthropicStreamEvent>(&event_data) {
                                // stop_reason from message_delta
                                if let Some(delta) = &evt.delta {
                                    if let AnthropicStreamDelta::StopDelta { stop_reason, .. } = delta {
                                        if let Some(reason) = stop_reason {
                                            let fr = Self::parse_stop_reason(Some(reason.as_str()));
                                            let _ = tx.send(Ok(StreamEvent::Done {
                                                message_id: String::new(),
                                                finish_reason: fr,
                                            })).await;
                                        }
                                    }
                                }
                                // Usage from message_delta
                                if let Some(usage) = &evt.usage {
                                    let _ = tx.send(Ok(StreamEvent::Usage(TokenUsage {
                                        prompt_tokens: usage.input_tokens,
                                        completion_tokens: usage.output_tokens,
                                        total_tokens: usage.input_tokens + usage.output_tokens,
                                        ..Default::default()
                                    }))).await;
                                }
                            }
                        }
                        "message_start" => {
                            if let Ok(evt) = serde_json::from_str::<AnthropicStreamEvent>(&event_data) {
                                if let Some(msg) = &evt.message {
                                    if let Some(usage) = &msg.usage {
                                        let _ = tx.send(Ok(StreamEvent::Usage(TokenUsage {
                                            prompt_tokens: usage.input_tokens,
                                            completion_tokens: usage.output_tokens,
                                            total_tokens: usage.input_tokens + usage.output_tokens,
                                            ..Default::default()
                                        }))).await;
                                    }
                                }
                            }
                        }
                        "message_stop" => {
                            // Final event; stream is done
                        }
                        "ping" | "content_block_stop" => {
                            // Ignore
                        }
                        other => {
                            trace!(event_type = other, "Unknown Anthropic SSE event type");
                        }
                    }
                }
            }
        });

        let stream = tokio_stream::wrappers::ReceiverStream::new(rx);
        Ok(Box::pin(stream))
    }

    fn provider(&self) -> &LlmProvider {
        &self.provider
    }

    async fn health_check(&self) -> LlmResult<bool> {
        // Anthropic doesn't have a dedicated health endpoint;
        // use a minimal messages request or just check connectivity
        let api_key = self.provider.api_key.as_deref().unwrap_or("");
        let url = self.messages_url();

        match self
            .http
            .post(&url)
            .header("x-api-key", api_key)
            .header("anthropic-version", &self.api_version)
            .header("content-type", "application/json")
            .json(&json!({
                "model": "claude-3-haiku-20240307",
                "max_tokens": 1,
                "messages": [{"role": "user", "content": "hi"}],
            }))
            .timeout(std::time::Duration::from_secs(10))
            .send()
            .await
        {
            Ok(resp) => {
                // Any non-5xx response means the service is reachable
                let healthy = !resp.status().is_server_error();
                debug!(provider = %self.provider.id, status = %resp.status(), healthy, "Anthropic health check");
                Ok(healthy)
            }
            Err(e) => {
                warn!(provider = %self.provider.id, err = %e, "Anthropic health check failed");
                Ok(false)
            }
        }
    }
}

impl std::fmt::Debug for AnthropicClient {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("AnthropicClient")
            .field("provider_id", &self.provider.id)
            .field("api_version", &self.api_version)
            .finish()
    }
}
