//! Anthropic Messages API backend (raw-model). Implements [`ChatModel`] for the
//! shared agent loop, plus [`Provider`].

use super::{AuthStatus, Provider, ProviderKind};
use crate::agent::runner::{self, ChatModel, ModelStep, Msg, ToolCall};
use crate::agent::tools::{ToolCtx, ToolSchema};
use crate::agent::{AgentOutcome, AgentTask};
use crate::config::ProviderId;
use crate::progress::Progress;
use anyhow::{anyhow, bail, Context, Result};
use async_trait::async_trait;
use serde_json::{json, Value};

const API: &str = "https://api.anthropic.com/v1";
const VERSION: &str = "2023-06-01";
const MAX_TOKENS: u32 = 4096;

pub struct AnthropicProvider {
    client: reqwest::Client,
    api_key: Option<String>,
    model: Option<String>,
}

impl AnthropicProvider {
    pub fn new(api_key: Option<String>, model: Option<String>) -> AnthropicProvider {
        AnthropicProvider {
            client: super::http_client(),
            api_key,
            model,
        }
    }

    fn key(&self) -> Result<&str> {
        self.api_key
            .as_deref()
            .ok_or_else(|| anyhow!("Anthropic API key is not set"))
    }

    fn model(&self) -> Result<&str> {
        self.model
            .as_deref()
            .ok_or_else(|| anyhow!("no model set — pass --model or set it in config"))
    }
}

fn to_messages(messages: &[Msg]) -> Vec<Value> {
    let mut out = Vec::new();
    for m in messages {
        match m {
            Msg::User(text) => out.push(json!({
                "role":"user",
                "content":[{"type":"text","text":text.clone()}]
            })),
            Msg::Assistant { text, tool_calls } => {
                let mut content = Vec::new();
                if let Some(t) = text {
                    if !t.trim().is_empty() {
                        content.push(json!({"type":"text","text":t.clone()}));
                    }
                }
                for tc in tool_calls {
                    content.push(json!({
                        "type":"tool_use",
                        "id":tc.id.clone(),
                        "name":tc.name.clone(),
                        "input":tc.input.clone()
                    }));
                }
                out.push(json!({"role":"assistant","content":content}));
            }
            Msg::ToolResults(results) => {
                let content: Vec<Value> = results
                    .iter()
                    .map(|r| {
                        json!({
                            "type":"tool_result",
                            "tool_use_id":r.id.clone(),
                            "content":r.content.clone()
                        })
                    })
                    .collect();
                out.push(json!({"role":"user","content":content}));
            }
        }
    }
    out
}

fn to_tools(tools: &[ToolSchema]) -> Vec<Value> {
    tools
        .iter()
        .map(|t| {
            json!({
                "name":t.name,
                "description":t.description,
                "input_schema":t.parameters
            })
        })
        .collect()
}

#[async_trait]
impl ChatModel for AnthropicProvider {
    async fn step(
        &self,
        system: &str,
        messages: &[Msg],
        tools: &[ToolSchema],
    ) -> Result<ModelStep> {
        let body = json!({
            "model": self.model()?,
            "max_tokens": MAX_TOKENS,
            "system": system,
            "messages": to_messages(messages),
            "tools": to_tools(tools),
        });

        let resp = self
            .client
            .post(format!("{API}/messages"))
            .header("x-api-key", self.key()?)
            .header("anthropic-version", VERSION)
            .header("content-type", "application/json")
            .json(&body)
            .send()
            .await
            .context("calling Anthropic API")?;

        let status = resp.status();
        let text = resp.text().await.unwrap_or_default();
        if !status.is_success() {
            bail!(
                "Anthropic API error {status}: {}",
                crate::util::truncate_chars(&text, 600)
            );
        }
        let v: Value = serde_json::from_str(&text).context("parsing Anthropic response")?;

        let mut out_text = String::new();
        let mut tool_calls = Vec::new();
        if let Some(content) = v.get("content").and_then(|c| c.as_array()) {
            for block in content {
                match block.get("type").and_then(|t| t.as_str()) {
                    Some("text") => {
                        if let Some(t) = block.get("text").and_then(|t| t.as_str()) {
                            out_text.push_str(t);
                        }
                    }
                    Some("tool_use") => {
                        let id = block.get("id").and_then(|i| i.as_str()).unwrap_or_default();
                        let name = block
                            .get("name")
                            .and_then(|n| n.as_str())
                            .unwrap_or_default();
                        // A tool_use block without an id/name is malformed; skip
                        // it rather than echo empty ids back (which the API rejects).
                        if id.is_empty() || name.is_empty() {
                            continue;
                        }
                        tool_calls.push(ToolCall {
                            id: id.to_string(),
                            name: name.to_string(),
                            input: block.get("input").cloned().unwrap_or_else(|| json!({})),
                        });
                    }
                    _ => {}
                }
            }
        }

        Ok(ModelStep {
            text: if out_text.is_empty() {
                None
            } else {
                Some(out_text)
            },
            tool_calls,
        })
    }
}

#[async_trait]
impl Provider for AnthropicProvider {
    fn id(&self) -> ProviderId {
        ProviderId::Anthropic
    }
    fn kind(&self) -> ProviderKind {
        ProviderKind::RawModel
    }

    async fn run_agent(&self, task: &AgentTask, progress: &dyn Progress) -> Result<AgentOutcome> {
        self.model()?; // fail fast with a clear message
        let ctx = ToolCtx::new(
            &task.repo_root,
            &task.wiki_dir,
            task.repo_map.clone(),
            task.read_only,
        );
        runner::run_loop(
            self,
            &task.system,
            task.prompt(),
            &ctx,
            task.max_iters,
            progress,
        )
        .await
    }

    async fn auth_status(&self) -> AuthStatus {
        if self.api_key.is_some() {
            AuthStatus::Ok
        } else {
            AuthStatus::MissingKey
        }
    }

    async fn list_models(&self) -> Result<Vec<String>> {
        let resp = self
            .client
            .get(format!("{API}/models"))
            .header("x-api-key", self.key()?)
            .header("anthropic-version", VERSION)
            .send()
            .await
            .context("listing Anthropic models")?;
        let status = resp.status();
        let text = resp.text().await.unwrap_or_default();
        if !status.is_success() {
            bail!(
                "Anthropic API error {status}: {}",
                crate::util::truncate_chars(&text, 300)
            );
        }
        let v: Value = serde_json::from_str(&text)?;
        let ids = v
            .get("data")
            .and_then(|d| d.as_array())
            .map(|arr| {
                arr.iter()
                    .filter_map(|m| m.get("id").and_then(|i| i.as_str()).map(String::from))
                    .collect()
            })
            .unwrap_or_default();
        Ok(ids)
    }
}
