use async_trait::async_trait;
use reqwest::Client;
use serde::{Deserialize, Serialize};
use swarm_core::error::SwarmResult;
use swarm_core::message::ToolCall;

use crate::provider::{
    ChatMessage, CompletionRequest, CompletionResponse, LlmProvider, StopReason, ToolDefinition,
    Usage,
};

const API_BASE: &str = "https://api.anthropic.com/v1";
const API_VERSION: &str = "2023-06-01";

pub struct AnthropicProvider {
    client: Client,
    api_key: String,
}

impl AnthropicProvider {
    pub fn new(api_key: String) -> Self {
        Self {
            client: Client::new(),
            api_key,
        }
    }
}

// --- Anthropic API types ---

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

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

#[derive(Serialize)]
#[serde(untagged)]
enum AnthropicContent {
    Text(String),
    Blocks(Vec<ContentBlock>),
}

#[derive(Serialize, Deserialize, Debug)]
#[serde(tag = "type")]
enum ContentBlock {
    #[serde(rename = "text")]
    Text { text: String },
    #[serde(rename = "tool_use")]
    ToolUse {
        id: String,
        name: String,
        input: serde_json::Value,
    },
    #[serde(rename = "tool_result")]
    ToolResult {
        tool_use_id: String,
        content: String,
        #[serde(skip_serializing_if = "Option::is_none")]
        is_error: Option<bool>,
    },
}

#[derive(Serialize)]
struct AnthropicTool {
    name: String,
    description: String,
    input_schema: serde_json::Value,
}

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

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

#[derive(Deserialize, Debug)]
struct AnthropicError {
    error: AnthropicErrorBody,
}

#[derive(Deserialize, Debug)]
struct AnthropicErrorBody {
    message: String,
}

fn convert_messages(messages: &[ChatMessage]) -> Vec<AnthropicMessage> {
    let mut result = Vec::new();

    for msg in messages {
        if !msg.tool_results.is_empty() {
            let blocks: Vec<ContentBlock> = msg
                .tool_results
                .iter()
                .map(|tr| ContentBlock::ToolResult {
                    tool_use_id: tr.tool_call_id.clone(),
                    content: tr.content.clone(),
                    is_error: if tr.is_error { Some(true) } else { None },
                })
                .collect();
            result.push(AnthropicMessage {
                role: "user".to_string(),
                content: AnthropicContent::Blocks(blocks),
            });
        } else {
            result.push(AnthropicMessage {
                role: msg.role.clone(),
                content: AnthropicContent::Text(msg.content.clone()),
            });
        }
    }

    result
}

fn convert_tools(tools: &[ToolDefinition]) -> Vec<AnthropicTool> {
    tools
        .iter()
        .map(|t| AnthropicTool {
            name: t.name.clone(),
            description: t.description.clone(),
            input_schema: t.parameters.clone(),
        })
        .collect()
}

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

    async fn complete(&self, request: CompletionRequest) -> SwarmResult<CompletionResponse> {
        let api_request = AnthropicRequest {
            model: request.model,
            messages: convert_messages(&request.messages),
            system: request.system,
            max_tokens: request.max_tokens,
            temperature: request.temperature,
            tools: convert_tools(&request.tools),
        };

        let response = self
            .client
            .post(format!("{API_BASE}/messages"))
            .header("x-api-key", &self.api_key)
            .header("anthropic-version", API_VERSION)
            .header("content-type", "application/json")
            .json(&api_request)
            .send()
            .await
            .map_err(|e| swarm_core::error::SwarmError::LlmError(e.to_string()))?;

        let status = response.status();
        let body = response
            .text()
            .await
            .map_err(|e| swarm_core::error::SwarmError::LlmError(e.to_string()))?;

        if !status.is_success() {
            let err: AnthropicError = serde_json::from_str(&body).unwrap_or(AnthropicError {
                error: AnthropicErrorBody {
                    message: body.clone(),
                },
            });
            return Err(swarm_core::error::SwarmError::LlmError(format!(
                "Anthropic API {}: {}",
                status, err.error.message
            )));
        }

        let api_response: AnthropicResponse = serde_json::from_str(&body)
            .map_err(|e| swarm_core::error::SwarmError::LlmError(e.to_string()))?;

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

        for block in &api_response.content {
            match block {
                ContentBlock::Text { text } => {
                    if !content.is_empty() {
                        content.push('\n');
                    }
                    content.push_str(text);
                }
                ContentBlock::ToolUse { id, name, input } => {
                    tool_calls.push(ToolCall {
                        id: id.clone(),
                        name: name.clone(),
                        arguments: input.clone(),
                    });
                }
                ContentBlock::ToolResult { .. } => {}
            }
        }

        let stop_reason = match api_response.stop_reason.as_deref() {
            Some("tool_use") => StopReason::ToolUse,
            Some("max_tokens") => StopReason::MaxTokens,
            Some("stop_sequence") => StopReason::StopSequence,
            _ => StopReason::EndTurn,
        };

        Ok(CompletionResponse {
            content,
            tool_calls,
            model: api_response.model,
            usage: Usage {
                input_tokens: api_response.usage.input_tokens,
                output_tokens: api_response.usage.output_tokens,
            },
            stop_reason,
        })
    }
}
