//! Anthropic 兼容 Provider — 通过 /v1/messages 端点实现
//!
//! 覆盖: Claude 官方 / GLM 订阅端点 (open.bigmodel.cn/api/anthropic) / 任意 Messages API 网关
//! 支持: 同步调用 + SSE 流式 + Tool Use
//!
//! 与 OpenAI 协议的关键差异由本模块内部完成转换，对外仍暴露统一的 LlmProvider trait，
//! 上层 (Agentic Loop / AiNode) 无需感知协议。
//!
//! 协议数据结构（请求/响应 struct）与 SSE 事件解析纯函数已抽至 `anthropic_helpers`，
//! 本模块仅保留 Provider struct + impl（HTTP 调用），Rust impl 块不可跨文件故作此切分。

use async_trait::async_trait;
use futures::StreamExt;
use reqwest::Client;
use std::time::Duration;
use tracing::{debug, error, warn};

use crate::provider::{
    tool_call_id_or_fallback, CompletionRequest, CompletionResponse, LlmProvider, MessageRole,
    StreamResult, TokenUsage, ToolCall,
};
// ChatMessage 仅单测构造 CompletionRequest 用，避免非 test 构建的 unused import 警告。
#[cfg(test)]
use crate::provider::ChatMessage;
use crate::retry::{
    retry_with_backoff, AttemptOutcome, is_reqwest_error_retryable, is_status_retryable,
};

// 协议数据结构 + SSE 纯解析（apply_anthropic_event / AnthropicRequest 等）抽至 anthropic_helpers，
// 此处 use 引入以保持本模块内引用路径不变（零行为变更搬迁）。
use crate::anthropic_helpers::{
    apply_anthropic_event, AnthropicRequest, AnthropicResponse, AnthropicToolDef,
    ANTHROPIC_VERSION, DEFAULT_MAX_TOKENS,
};

// ============================================================
// Provider 实现
// ============================================================

/// Anthropic 兼容 LLM Provider
pub struct AnthropicCompatProvider {
    client: Client,
    api_key: String,
    base_url: String,
    default_model: String,
}

impl AnthropicCompatProvider {
    /// 创建 Provider
    ///
    /// - `base_url`: 如 `https://api.anthropic.com`、`https://open.bigmodel.cn/api/anthropic`
    /// - `api_key`: API 密钥（Anthropic 用 x-api-key 头，非 Bearer）
    /// - `default_model`: 默认模型名称
    pub fn new(base_url: impl Into<String>, api_key: impl Into<String>, default_model: impl Into<String>) -> Self {
        // SW-260618-10: reqwest Client 构建抽 crate::build_provider_client（与 OpenAI 共用 DRY）。
        let client = crate::build_provider_client();
        Self {
            client,
            api_key: api_key.into(),
            base_url: base_url.into(),
            default_model: default_model.into(),
        }
    }

    /// 构建 messages 端点 URL
    ///
    /// - 已含 `/v1/messages` → 直接用
    /// - 以 `/v1` 结尾 → 补 `/messages`
    /// - 否则（如 `.../api/anthropic`、`api.anthropic.com`）→ 补 `/v1/messages`
    fn messages_url(&self) -> String {
        let base = self.base_url.trim_end_matches('/');
        if base.ends_with("/v1/messages") {
            return base.to_string();
        }
        if base.ends_with("/v1") {
            return format!("{}/messages", base);
        }
        format!("{}/v1/messages", base)
    }

    /// 将统一 CompletionRequest 转换为 Anthropic 请求体
    ///
    /// 转换要点：
    /// - system 消息从 messages 抽离到顶层 system 字段
    /// - assistant 带 tool_calls → content 数组含 text + tool_use 块
    /// - tool_result（role=Tool）→ user 消息含 tool_result 块；连续多个合并为一条 user
    /// - tool_definitions 的 parameters → input_schema
    fn convert_request(&self, req: CompletionRequest) -> AnthropicRequest {
        let model = if req.model.is_empty() {
            self.default_model.clone()
        } else {
            req.model
        };

        // 抽离 system 消息
        let system: Option<String> = {
            let sys: Vec<String> = req
                .messages
                .iter()
                .filter(|m| matches!(m.role, MessageRole::System))
                .map(|m| m.content.clone())
                .collect();
            if sys.is_empty() {
                None
            } else {
                Some(sys.join("\n\n"))
            }
        };

        // 构建非 system 消息（保留顺序，合并连续 tool_result）
        let mut messages: Vec<serde_json::Value> = Vec::new();
        let mut pending_tool_results: Vec<serde_json::Value> = Vec::new();

        for m in req.messages.iter() {
            match m.role {
                MessageRole::System => continue,
                MessageRole::Tool => {
                    // 累积 tool_result 块，遇到非 Tool 消息时 flush。
                    // tool_use_id 为 None 时绝不能发 null（GLM anthropic 端点会 500
                    // 报 'ClaudeContentBlockToolResult' object has no attribute 'id'，
                    // 致会话卡死持续 500），跳过该块并告警。
                    match &m.tool_call_id {
                        Some(tid) if !tid.is_empty() => {
                            pending_tool_results.push(serde_json::json!({
                                "type": "tool_result",
                                "tool_use_id": tid,
                                "content": m.content,
                            }));
                        }
                        _ => {
                            warn!(
                                content_preview = %m.content.chars().take(80).collect::<String>(),
                                "Anthropic tool_result 缺少 tool_use_id，已跳过该块（发 null 会触发 GLM 500）"
                            );
                        }
                    }
                }
                MessageRole::User => {
                    Self::flush_tool_results(&mut messages, &mut pending_tool_results);
                    // 多模态 user 消息 → content blocks 数组（text/image）。
                    // 含图时把 content + parts 拍平成 blocks：Text 片 → {type:text}，
                    // Image 片 → {type:image, source:{type:base64, media_type, data}}。
                    // Anthropic 协议要求 image 必须内嵌 base64（不接受 URL 直传）。
                    // 现状：前端只产 base64 模式图片片，url 模式当前不可达。
                    // 未来若加 url 图片输入，必须在 commands 层补 url→base64 预拉
                    //（provider 不发额外 HTTP），否则下方兜底会发空 data 致 Anthropic 400。
                    // 纯文本消息（无图）保持原字符串简写，与现有端点零回归。
                    if m.has_image() {
                        let blocks: Vec<serde_json::Value> = m
                            .flattened_parts()
                            .into_iter()
                            .map(Self::content_part_to_block)
                            .collect();
                        messages.push(serde_json::json!({ "role": "user", "content": blocks }));
                    } else {
                        messages.push(serde_json::json!({ "role": "user", "content": m.content }));
                    }
                }
                MessageRole::Assistant => {
                    Self::flush_tool_results(&mut messages, &mut pending_tool_results);
                    let mut content: Vec<serde_json::Value> = Vec::new();
                    if !m.content.is_empty() {
                        content.push(serde_json::json!({ "type": "text", "text": m.content }));
                    }
                    if let Some(calls) = &m.tool_calls {
                        for tc in calls {
                            // arguments 非法 JSON(流式中断残留 / ToolCall::new 默认空串)
                            // → 空 object 兜底。Anthropic/GLM 要求 tool_use.input 必为 object,
                            // null 直触发 1214「messages 参数非法」。
                            let input: serde_json::Value = serde_json::from_str(&tc.function.arguments)
                                .unwrap_or_else(|_| serde_json::json!({}));
                            content.push(serde_json::json!({
                                "type": "tool_use",
                                "id": tc.id,
                                "name": tc.function.name,
                                "input": input,
                            }));
                        }
                    }
                    if content.is_empty() {
                        content.push(serde_json::json!({ "type": "text", "text": "" }));
                    }
                    messages.push(serde_json::json!({ "role": "assistant", "content": content }));
                }
            }
        }
        Self::flush_tool_results(&mut messages, &mut pending_tool_results);

        // 合并相邻 user 块。Anthropic 协议要求 user/assistant 严格交替,连续 user
        // 触发 GLM 1214。场景:drainQueue 续发(前一轮以 tool_result 结尾 + 新 user)→ flush 把
        // tool_result 转成 user 后紧跟 push 新 user → 连续两 user。合并成一条 user 含
        // [tool_result..., text] blocks(Anthropic 允许一条 user 多 blocks),打破恶性循环。
        Self::merge_consecutive_users(&mut messages);

        // 保证首条为 user(Anthropic 协议硬性要求 messages[0].role == "user")。
        // 上游绕过 ContextManager::sanitize_messages 的调用方(标题生成 / 知识注入 / 工作流 AI
        // 节点等直接构造 CompletionRequest 的路径)可能传入首条 assistant 的序列——会话恢复、
        // 续发或历史片段截取时,真正的首条 user 已被裁剪/压缩掉,直接发触发 precheck
        // "首条 role=assistant 非法"。
        // 根本解用"补"而非"砍":开头补一条 user 占位,保留全部上下文(assistant 的 tool_use
        // 与其后 user 的 tool_result 配对完整),precheck 必过。砍会丢工具调用历史,且多轮
        // [asst(tu),user(tr),asst(tu),user(tr),...] 逐对砍到空。占位是异常路径轻量噪声
        // (正常会话首条本就是 user),远优于丢弃上下文。
        Self::ensure_leading_user(&mut messages);

        let tools = req.tools.map(|defs| {
            defs.into_iter()
                .map(|d| AnthropicToolDef {
                    name: d.function.name,
                    description: Some(d.function.description).filter(|s| !s.is_empty()),
                    // input_schema 非 object(未来误用)→ 兜底 {"type":"object"},
                    // 防 Anthropic 拒非法 tool schema(当前全走 object_schema 恒 object,纯防御)。
                    input_schema: if d.function.parameters.is_object() {
                        d.function.parameters
                    } else {
                        serde_json::json!({"type": "object"})
                    },
                })
                .collect()
        });

        AnthropicRequest {
            model,
            messages,
            max_tokens: req.max_tokens.unwrap_or(DEFAULT_MAX_TOKENS),
            system,
            temperature: req.temperature,
            stream: req.stream,
            tools,
            tool_choice: req.tool_choice,
        }
    }

    /// 单个 ContentPart → Anthropic content block（text/image）。
    /// - Text 片 → {type:text, text}
    /// - Image 片 → {type:image, source:{type:base64, media_type, data}}
    ///   base64 内嵌；url 模式当前不可达，兜底发空 data + warn（保留原行为）。
    ///   match 取得 p 所有权后直接 move media_type/base64，避免大 base64 clone。
    fn content_part_to_block(p: crate::provider::ContentPart) -> serde_json::Value {
        match p {
            crate::provider::ContentPart::Text { text } => serde_json::json!({
                "type": "text",
                "text": text,
            }),
            crate::provider::ContentPart::Image { url, base64, media_type, alt: _ } => {
                let mt = media_type.unwrap_or_else(|| "image/png".into());
                let data = base64.unwrap_or_else(|| {
                    // 完整性兜底：当前 url 模式不可达（前端只产 base64 图片片）。
                    // 若未来接入 url 图片输入而 commands 层未补 url→base64 预拉，
                    // 此处会发空 data 致 Anthropic 400，warn 留痕但不阻塞（避免静默吞数据）。
                    if url.is_some() {
                        warn!(
                            url = ?url,
                            "Anthropic user 消息含 Image(url) 但 base64 缺失，将发空 data（commands 层未补 url→base64 预拉）"
                        );
                    }
                    String::new()
                });
                serde_json::json!({
                    "type": "image",
                    "source": {
                        "type": "base64",
                        "media_type": mt,
                        "data": data,
                    }
                })
            }
        }
    }

    /// 将累积的 tool_result 块作为一条 user 消息 flush 进消息列表
    fn flush_tool_results(
        messages: &mut Vec<serde_json::Value>,
        pending: &mut Vec<serde_json::Value>,
    ) {
        if pending.is_empty() {
            return;
        }
        let blocks: Vec<serde_json::Value> = pending.drain(..).collect();
        messages.push(serde_json::json!({ "role": "user", "content": blocks }));
    }

    /// 合并相邻 user 消息为一条(content 拼成 blocks 数组)。
    /// Anthropic 协议要求 user/assistant 严格交替,连续 user 触发 1214。
    /// 触发场景:flush_tool_results 把 tool_result 转 user 后紧跟新 user(drainQueue 续发,
    /// 前一轮以 tool_result 结尾)。合并成一条 user 含 [tool_result..., text] blocks,合法。
    /// content 形态:string(简写)或 array(blocks),统一规范化为 array 后拼接。
    fn merge_consecutive_users(messages: &mut Vec<serde_json::Value>) {
        let is_user = |m: &serde_json::Value| m.get("role").and_then(|r| r.as_str()) == Some("user");
        let mut i = 0;
        while i + 1 < messages.len() {
            if is_user(&messages[i]) && is_user(&messages[i + 1]) {
                let next_content = messages[i + 1].get("content").cloned().unwrap_or(serde_json::Value::Null);
                let cur = &mut messages[i];
                // 当前 user content 规范化为 array
                let mut blocks: Vec<serde_json::Value> = match cur.get("content").cloned() {
                    Some(serde_json::Value::String(s)) => vec![serde_json::json!({ "type": "text", "text": s })],
                    Some(serde_json::Value::Array(a)) => a,
                    _ => vec![],
                };
                // 并入下一个 user content
                match next_content {
                    serde_json::Value::String(s) => blocks.push(serde_json::json!({ "type": "text", "text": s })),
                    serde_json::Value::Array(a) => blocks.extend(a),
                    _ => {}
                }
                cur["content"] = serde_json::Value::Array(blocks);
                messages.remove(i + 1);
                // 不增 i:继续合并 i 与新 i+1(可能多个连续 user)
            } else {
                i += 1;
            }
        }
    }

    /// 保证 messages 首条为 user(Anthropic 协议硬性要求 messages[0].role=="user")。
    ///
    /// 上游绕过 `ContextManager::sanitize_messages` 的调用方(标题生成 / 知识注入 / 工作流 AI
    /// 节点等直接构造 CompletionRequest 的路径)可能传入首条 assistant 的序列——会话恢复、续发
    /// 或从历史片段截取时,真正的首条 user 已被裁剪/压缩掉。直接发触发 precheck "首条
    /// role=assistant 非法"。
    ///
    /// **用"补"而非"砍"**:在开头插一条 user 占位消息。
    /// - 砍掉首条 assistant 会丢失有效上下文(其 tool_use 与后续 user 的 tool_result 是完整
    ///   配对),且多轮 [asst(tu),user(tr),asst(tu),user(tr),...] 会被逐对砍到空;
    /// - 补占位则全部上下文保留(占位 user 紧贴原首条 assistant,不破坏 user/assistant 交替),
    ///   tool_use/tool_result 配对完整不动,**无 orphan 产生**(故砍策略那套 orphan 清理在此不需要),
    ///   占位 content 非空过 precheck 的"user content 空"校验。
    ///
    /// 仅异常路径触发(正常会话首条本就是 user),占位文案是轻量噪声,远优于丢弃工具调用历史。
    fn ensure_leading_user(messages: &mut Vec<serde_json::Value>) {
        let first_role = messages
            .first()
            .and_then(|m| m.get("role").and_then(|r| r.as_str()))
            .unwrap_or("");
        if first_role == "user" {
            return;
        }
        // 空 Vec(异常会话经 sanitize 清空)或首条非 user → 补 user 占位:
        // Anthropic 协议要求 messages 至少一条且首条 user,补占位让降级会话能继续(不丢这条兜底)。
        warn!(
            first_role,
            msg_count = messages.len(),
            "ensure_leading_user: 首条非 user(含空),补 user 占位(防 Anthropic 首条 assistant/空 messages 非法)"
        );
        messages.insert(0, serde_json::json!({
            "role": "user",
            "content": "(continued from previous context)",
        }));
    }

    /// 生成 messages 诊断摘要(每条 role + content 形态 + tool 标记),不含敏感数据。
    /// 1214 类错误时随 bail 文案直达前端 raw,定位哪条/字段非法。
    fn summarize_messages(messages: &[serde_json::Value]) -> String {
        let lines: Vec<String> = messages
            .iter()
            .enumerate()
            .map(|(i, m)| {
                let role = m.get("role").and_then(|r| r.as_str()).unwrap_or("?");
                let desc = match m.get("content") {
                    Some(serde_json::Value::String(s)) => format!("text({}B)", s.len()),
                    Some(serde_json::Value::Array(blocks)) => {
                        let parts: Vec<String> = blocks
                            .iter()
                            .map(|b| {
                                let ty = b.get("type").and_then(|t| t.as_str()).unwrap_or("?");
                                match ty {
                                    "text" => format!(
                                        "text({}B)",
                                        b.get("text").and_then(|t| t.as_str()).map(|s| s.len()).unwrap_or(0)
                                    ),
                                    "tool_use" => format!(
                                        "tool_use[id={},input_obj={}]",
                                        b.get("id").and_then(|t| t.as_str()).unwrap_or(""),
                                        b.get("input").map(|v| v.is_object()).unwrap_or(false)
                                    ),
                                    "tool_result" => format!(
                                        "tool_result[tid={}]",
                                        b.get("tool_use_id").and_then(|t| t.as_str()).unwrap_or("")
                                    ),
                                    "image" => "image".to_string(),
                                    _ => ty.to_string(),
                                }
                            })
                            .collect();
                        format!("[{}]", parts.join(","))
                    }
                    _ => "?".to_string(),
                };
                format!("#{}:{} {}", i, role, desc)
            })
            .collect();
        format!("{} msgs: {}", lines.len(), lines.join(" | "))
    }

    /// 协议预检——扫 messages 发现确定非法形态,命中返回原因(仅诊断不修复)。
    /// 覆盖:首条非 user / 连续同 role / tool_use input 非 object / 空 content / orphan tool_result
    /// (tool_use_id 无前置 tool_use,常见于裁剪/过滤后 assistant 被删但 tool_result 留)。
    fn precheck_messages(messages: &[serde_json::Value]) -> Result<(), String> {
        if messages.is_empty() {
            return Err("messages 为空".into());
        }
        let first_role = messages[0].get("role").and_then(|r| r.as_str()).unwrap_or("");
        if first_role != "user" {
            return Err(format!("首条 role={} 非法(须 user)", first_role));
        }
        for w in messages.windows(2) {
            let r0 = w[0].get("role").and_then(|r| r.as_str()).unwrap_or("");
            let r1 = w[1].get("role").and_then(|r| r.as_str()).unwrap_or("");
            if r0 == r1 && (r0 == "user" || r0 == "assistant") {
                return Err(format!("连续同 role={}", r0));
            }
        }
        let mut tool_use_ids: Vec<&str> = Vec::new();
        for (i, m) in messages.iter().enumerate() {
            let role = m.get("role").and_then(|r| r.as_str()).unwrap_or("");
            match m.get("content") {
                Some(serde_json::Value::String(s)) if s.is_empty() && role == "user" => {
                    return Err(format!("#{} user content 空", i));
                }
                Some(serde_json::Value::Array(blocks)) => {
                    if blocks.is_empty() && role == "user" {
                        return Err(format!("#{} user content 空数组", i));
                    }
                    for b in blocks {
                        Self::check_block(b, i, &mut tool_use_ids)?;
                    }
                }
                _ => {}
            }
        }
        Ok(())
    }

    /// 校验单个 content block（precheck_messages 内部用）。
    /// - tool_use: 收集 id,校验 input 为 object
    /// - tool_result: 校验 tool_use_id 有前置 tool_use（非 orphan）
    fn check_block<'a>(
        b: &'a serde_json::Value,
        idx: usize,
        tool_use_ids: &mut Vec<&'a str>,
    ) -> Result<(), String> {
        match b.get("type").and_then(|t| t.as_str()).unwrap_or("") {
            "tool_use" => {
                let id = b.get("id").and_then(|t| t.as_str()).unwrap_or("");
                tool_use_ids.push(id);
                if !b.get("input").map(|v| v.is_object()).unwrap_or(false) {
                    return Err(format!("#{} tool_use input 非 object", idx));
                }
            }
            "tool_result" => {
                let tid = b.get("tool_use_id").and_then(|t| t.as_str()).unwrap_or("");
                if !tid.is_empty() && !tool_use_ids.contains(&tid) {
                    return Err(format!(
                        "#{} orphan tool_result(tid={} 无前置 tool_use)",
                        idx, tid
                    ));
                }
            }
            _ => {}
        }
        Ok(())
    }

    /// 统一鉴权头：x-api-key + anthropic-version
    fn auth_headers(&self, rb: reqwest::RequestBuilder) -> reqwest::RequestBuilder {
        rb.header("x-api-key", &self.api_key)
            .header("anthropic-version", ANTHROPIC_VERSION)
            .header("Content-Type", "application/json")
    }
}

#[async_trait]
impl LlmProvider for AnthropicCompatProvider {
    async fn complete(&self, request: CompletionRequest) -> anyhow::Result<CompletionResponse> {
        let mut req = request;
        req.stream = false;
        let body = self.convert_request(req);

        // 协议预检——命中非法 bail 含 messages 摘要,把 GLM 模糊 1214 转明确诊断
        if let Err(reason) = Self::precheck_messages(&body.messages) {
            let summary = Self::summarize_messages(&body.messages);
            warn!(%reason, %summary, "Anthropic messages 协议预检失败");
            anyhow::bail!("messages 预检失败: {} | 摘要: {}", reason, summary);
        }

        debug!(model = %body.model, "Anthropic 同步调用");

        // 指数退避重试: 包裹 send + 状态码判定。
        // 同时补单请求 timeout(Anthropic 同步路径无 timeout 会 hang),此处加 60s,与 OpenAI 路径对齐。
        let label = format!("Anthropic[{}]", body.model);
        retry_with_backoff(&label, move |_| {
            let client = self.client.clone();
            let url = self.messages_url();
            let api_key = self.api_key.clone();
            let body = body.clone();
            async move {
                // send: 补 60s 单请求 timeout(此前缺失，FR-R4 遗漏)
                let rb = client
                    .post(url)
                    .header("x-api-key", &api_key)
                    .header("anthropic-version", ANTHROPIC_VERSION)
                    .header("Content-Type", "application/json")
                    .timeout(Duration::from_secs(60))
                    .version(reqwest::Version::HTTP_11)
                    .json(&body);
                let resp = match rb.send().await {
                    Ok(r) => r,
                    Err(e) => {
                        // 记 reqwest 错误源因链(is_*/source)。原仅 Display
                        // "error sending request for url" 无法定位 reset/TLS/超时/body 真因。
                        tracing::error!(
                            is_timeout = e.is_timeout(),
                            is_connect = e.is_connect(),
                            is_body = e.is_body(),
                            is_request = e.is_request(),
                            url = ?e.url(),
                            source = ?std::error::Error::source(&e),
                            "Anthropic 同步 send 失败"
                        );
                        if is_reqwest_error_retryable(&e) {
                            return AttemptOutcome::Retryable(format!("请求失败(可重试): {}", e));
                        }
                        return AttemptOutcome::Fatal(format!(
                            "请求失败(不可重试): {} [timeout={} connect={} body={} src={:?}]",
                            e,
                            e.is_timeout(),
                            e.is_connect(),
                            e.is_body(),
                            std::error::Error::source(&e)
                        ));
                    }
                };
                if !resp.status().is_success() {
                    let status = resp.status().as_u16();
                    let text = resp.text().await.unwrap_or_default();
                    let msg = format!("Anthropic API 错误 {}: {}", status, text);
                    if is_status_retryable(status) {
                        warn!(%status, "Anthropic 同步调用可重试状态码");
                        return AttemptOutcome::Retryable(msg);
                    }
                    error!(%status, %text, "Anthropic API 调用失败(不可重试)");
                    return AttemptOutcome::Fatal(msg);
                }
                let resp: AnthropicResponse = match resp.json().await {
                    Ok(r) => r,
                    Err(e) => return AttemptOutcome::Fatal(format!("响应解析失败: {}", e)),
                };
                // content 块中拼接 text，收集 tool_use
                let mut text = String::new();
                let mut tool_calls: Vec<ToolCall> = Vec::new();
                // CR-空 id：按 tool_use 块在数组中的顺序计数（仅 tool_use 递增），用于 fallback index。
                // 用独立计数器而非 for enumerate，避免 text/unknown 块占用 index 致 fallback 编号跳号。
                let mut tool_use_idx: usize = 0;
                for block in resp.content {
                    match block.block_type.as_str() {
                        "text" => {
                            if let Some(t) = block.text {
                                text.push_str(&t);
                            }
                        }
                        "tool_use" => {
                            // CR-空 id：原逻辑空 id 直接 continue 跳过整个块（丢工具调用）。
                            // 改为兜底：id 非空原样，空 → `gen_anthropic_{idx}` fallback（DRY 共用
                            // tool_call_id_or_fallback）。Anthropic 一般非空，此为兼容缺陷兜底。
                            // 不再 warn+continue（continue 会丢工具调用致 LLM 拿不到结果）。
                            let raw_id = block.id.unwrap_or_default();
                            let id = tool_call_id_or_fallback(&raw_id, tool_use_idx, "gen_anthropic");
                            if raw_id.is_empty() {
                                warn!(
                                    fallback_id = %id,
                                    name = ?block.name,
                                    "Anthropic tool_use 块 id 为空，已生成 fallback id（原 continue 跳过会丢工具调用）"
                                );
                            }
                            tool_use_idx += 1;
                            let name = block.name.unwrap_or_default();
                            let args = block
                                .input
                                .map(|v| serde_json::to_string(&v).unwrap_or_default())
                                .unwrap_or_default();
                            tool_calls.push(ToolCall::new(id, name, args));
                        }
                        other => warn!(block_type = other, "Anthropic 响应含未知 content 块类型，已忽略"),
                    }
                }
                let usage = TokenUsage {
                    prompt_tokens: resp.usage.input_tokens,
                    completion_tokens: resp.usage.output_tokens,
                    total_tokens: resp.usage.input_tokens + resp.usage.output_tokens,
                };
                AttemptOutcome::Ok(CompletionResponse {
                    text,
                    model: resp.model,
                    usage,
                    tool_calls: if tool_calls.is_empty() { None } else { Some(tool_calls) },
                    reasoning_content: None,
                })
            }
        })
        .await
    }

    async fn stream(&self, request: CompletionRequest) -> anyhow::Result<StreamResult> {
        let mut req = request;
        req.stream = true;
        let body = self.convert_request(req);

        // 协议预检——命中非法 bail 含 messages 摘要,把 GLM 模糊 1214 转明确诊断
        if let Err(reason) = Self::precheck_messages(&body.messages) {
            let summary = Self::summarize_messages(&body.messages);
            warn!(%reason, %summary, "Anthropic messages 协议预检失败");
            anyhow::bail!("messages 预检失败: {} | 摘要: {}", reason, summary);
        }

        debug!(model = %body.model, "Anthropic 流式调用");

        // send 阶段需 timeout 防 hang(实测 GLM 偶发建连后长时间不返回)。
        // 注意:不能用 reqwest 的 .timeout()——它是整个请求(含 body 读取)的总超时,
        // 流式长生成任务会被误砍。改用 tokio::time::timeout
        // 包裹 send().await,只管建连+首响应头,不管后续 body 读取(后续由 stream_llm idle timeout 兜底)。
        // 60s 选型:正常 send(建连+收 200 headers)<5s,60s 足够宽容。
        let send_future = self
            .auth_headers(self.client.post(self.messages_url()))
            .json(&body)
            .version(reqwest::Version::HTTP_11)
            .send();
        let resp = match tokio::time::timeout(Duration::from_secs(60), send_future).await {
            Ok(Ok(r)) => r,
            Ok(Err(e)) => {
                tracing::error!(
                    is_timeout = e.is_timeout(),
                    is_connect = e.is_connect(),
                    is_body = e.is_body(),
                    is_request = e.is_request(),
                    url = %self.messages_url(),
                    source = ?std::error::Error::source(&e),
                    "Anthropic 流式 send 失败"
                );
                anyhow::bail!(
                    "send 失败: {} [timeout={} connect={} body={} request={} src={:?}]",
                    e,
                    e.is_timeout(),
                    e.is_connect(),
                    e.is_body(),
                    e.is_request(),
                    std::error::Error::source(&e)
                );
            }
            Err(_elapsed) => {
                // send 阶段超时(60s 未返回 HTTP 响应头):GLM 端点可能不可达或极慢
                tracing::error!(
                    url = %self.messages_url(),
                    "Anthropic 流式 send 超时(60s 未返回响应头)"
                );
                anyhow::bail!("流式请求超时(60秒未收到 HTTP 响应，可能服务不可达或被防火墙拦截)");
            }
        };

        if !resp.status().is_success() {
            let status = resp.status();
            let text = resp.text().await.unwrap_or_default();
            error!(%status, %text, "Anthropic 流式 API 调用失败");
            anyhow::bail!("Anthropic 流式 API 错误 {}: {}", status, text);
        }

        // 原生 SSE 解析器替代 eventsource-stream(同 openai_compat)。
        let mut usage_accum: Option<TokenUsage> = None;
        // MidStream error(如 GLM 1214 messages 非法)时附 messages 摘要定位哪条非法。
        let messages_summary = Self::summarize_messages(&body.messages);

        let sse = crate::sse_parser::SseStream::new(resp.bytes_stream());
        let stream = sse.flat_map(move |result: Result<Vec<String>, String>| {
            let mut chunks: Vec<anyhow::Result<crate::provider::StreamChunk>> = Vec::new();
            match result {
                Ok(events) => {
                    for data in events {
                        let mut chunk = apply_anthropic_event(&data, &mut usage_accum);
                        if let Some(err) = chunk.error.as_mut() {
                            *err = format!("{} | messages 摘要: {}", err, messages_summary);
                        }
                        chunks.push(Ok(chunk));
                    }
                }
                Err(e) => {
                    let ctx = format!("Anthropic SSE 错误: {}", e);
                    error!("{}", ctx);
                    chunks.push(Err(anyhow::anyhow!("{}", ctx)));
                }
            }
            futures::stream::iter(chunks)
        });

        Ok(Box::pin(stream))
    }

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

    fn endpoint(&self) -> String {
        self.messages_url()
    }
}

// ============================================================
// 单测（不发真实 HTTP，喂构造的 SSE data 字符串序列）
// ============================================================

#[cfg(test)]
mod tests {
    use super::*;
    // apply_anthropic_event / TokenUsage 经 super::*（含 anthropic_helpers::apply_anthropic_event 的 use）可见。

    /// 辅助：构造 message_start 事件
    fn message_start(input_tokens: u32) -> String {
        format!(
            r#"{{"type":"message_start","message":{{"usage":{{"input_tokens":{},"output_tokens":0}}}}}}"#,
            input_tokens
        )
    }

    /// 辅助：构造 message_delta 事件（output_tokens 为累计值）
    fn message_delta(output_tokens: u32) -> String {
        format!(
            r#"{{"type":"message_delta","delta":{{"stop_reason":"end_turn"}},"usage":{{"output_tokens":{}}}}}"#,
            output_tokens
        )
    }

    /// 辅助：构造文本增量 content_block_delta
    fn text_delta(text: &str) -> String {
        format!(
            r#"{{"type":"content_block_delta","index":0,"delta":{{"type":"text_delta","text":"{}"}}}}"#,
            text
        )
    }

    /// 辅助：构造 message_stop 事件
    fn message_stop() -> &'static str {
        r#"{"type":"message_stop"}"#
    }

    /// 完整流：message_start 初始化 input + 多次 message_delta 累计覆盖 output + message_stop 带出
    #[test]
    fn anthropic_full_stream_accumulates_usage() {
        let mut acc: Option<TokenUsage> = None;

        // 1) message_start：input=42，output=0
        let c = apply_anthropic_event(&message_start(42), &mut acc);
        assert!(!c.finished);
        assert!(c.usage.is_none());
        let a = acc.as_ref().expect("message_start 应初始化累加器");
        assert_eq!(a.prompt_tokens, 42);
        assert_eq!(a.completion_tokens, 0);
        assert_eq!(a.total_tokens, 42);

        // 2) 文本增量不影响 usage
        let c = apply_anthropic_event(&text_delta("Hello"), &mut acc);
        assert_eq!(c.delta, "Hello");
        assert!(!c.finished);
        assert_eq!(acc.as_ref().unwrap().completion_tokens, 0, "文本增量不应改 output");

        // 3) message_delta：output_tokens=10（累计值，覆盖）
        let c = apply_anthropic_event(&message_delta(10), &mut acc);
        assert!(!c.finished);
        let a = acc.as_ref().unwrap();
        assert_eq!(a.prompt_tokens, 42, "input 保持");
        assert_eq!(a.completion_tokens, 10, "output 被覆盖为累计值");
        assert_eq!(a.total_tokens, 52, "total 重算 = input+output");

        // 4) 再次 message_delta：output_tokens=30（更大累计值，再覆盖）
        let _ = apply_anthropic_event(&message_delta(30), &mut acc);
        let a = acc.as_ref().unwrap();
        assert_eq!(a.completion_tokens, 30, "后续累计值覆盖前值");
        assert_eq!(a.total_tokens, 72);

        // 5) message_stop：带出累积 usage，finished=true，累加器清空
        let c = apply_anthropic_event(message_stop(), &mut acc);
        assert!(c.finished);
        let u = c.usage.expect("message_stop 应带出累积 usage");
        assert_eq!(u.prompt_tokens, 42);
        assert_eq!(u.completion_tokens, 30);
        assert_eq!(u.total_tokens, 72);
        assert!(acc.is_none(), "take() 后累加器应清空");
    }

    /// message_delta 在没有 message_start 时也能补全累加器（get_or_insert 兜底）
    #[test]
    fn anthropic_message_delta_without_start_uses_default_input() {
        let mut acc: Option<TokenUsage> = None;
        let _ = apply_anthropic_event(&message_delta(15), &mut acc);
        let a = acc.as_ref().unwrap();
        assert_eq!(a.prompt_tokens, 0, "无 message_start 时 input 兜底为 0");
        assert_eq!(a.completion_tokens, 15);
        assert_eq!(a.total_tokens, 15);
    }

    /// message_delta 的 output_tokens 必须是累计覆盖（非累加）：连续两个 delta 5 和 8，结果应是 8 不是 13
    #[test]
    fn anthropic_message_delta_output_is_cumulative_not_incremental() {
        let mut acc: Option<TokenUsage> = None;
        apply_anthropic_event(&message_start(100), &mut acc);
        apply_anthropic_event(&message_delta(5), &mut acc);
        apply_anthropic_event(&message_delta(8), &mut acc);
        let c = apply_anthropic_event(message_stop(), &mut acc);
        let u = c.usage.unwrap();
        assert_eq!(u.completion_tokens, 8, "output_tokens 是累计值，覆盖而非累加");
        assert_eq!(u.total_tokens, 108);
    }

    /// 无 usage 字段的流：message_stop 时 usage 为 None
    #[test]
    fn anthropic_message_stop_without_any_usage() {
        let mut acc: Option<TokenUsage> = None;
        let _ = apply_anthropic_event(&text_delta("hi"), &mut acc);
        assert!(acc.is_none(), "文本增量不初始化累加器");
        let c = apply_anthropic_event(message_stop(), &mut acc);
        assert!(c.finished);
        assert!(c.usage.is_none(), "无 usage 时 message_stop usage 为 None");
    }

    /// content_block_start (tool_use) 带 id+name
    #[test]
    fn anthropic_content_block_start_tool_use() {
        let mut acc: Option<TokenUsage> = None;
        let data = r#"{"type":"content_block_start","index":1,"content_block":{"type":"tool_use","id":"tool_1","name":"get_weather"}}"#;
        let c = apply_anthropic_event(data, &mut acc);
        assert!(acc.is_none(), "content_block_start 不动 usage");
        let tcs = c.tool_calls.expect("应有 tool_calls");
        assert_eq!(tcs.len(), 1);
        assert_eq!(tcs[0].index, 1);
        assert_eq!(tcs[0].id.as_deref(), Some("tool_1"));
        assert_eq!(tcs[0].function_name.as_deref(), Some("get_weather"));
        assert!(tcs[0].function_arguments.is_none());
        assert!(!c.finished);
    }

    /// content_block_delta (input_json_delta) → 工具入参增量
    #[test]
    fn anthropic_content_block_delta_input_json() {
        let mut acc: Option<TokenUsage> = None;
        let data = r#"{"type":"content_block_delta","index":2,"delta":{"type":"input_json_delta","partial_json":"{\"q\":"}}"#;
        let c = apply_anthropic_event(data, &mut acc);
        let tcs = c.tool_calls.expect("应有 tool_calls 增量");
        assert_eq!(tcs[0].index, 2);
        assert_eq!(tcs[0].function_arguments.as_deref(), Some("{\"q\":"));
        assert!(tcs[0].id.is_none());
        assert_eq!(c.delta, "");
        assert!(!c.finished);
    }

    /// error 事件 → error=Some + finished=false（R-P1-1：避免残缺响应被当正常完成入库，
    /// 由 stream_llm 识别 error 非空发 AiError + 丢弃残缺，与 OpenAI 路径 Err 一致）
    #[test]
    fn anthropic_error_event_yields_error_not_finished() {
        let mut acc: Option<TokenUsage> = None;
        apply_anthropic_event(&message_start(10), &mut acc);
        let c = apply_anthropic_event(r#"{"type":"error","error":{"message":"overloaded"}}"#, &mut acc);
        assert!(!c.finished, "error 不走 finished 完成路径，否则残缺响应会被当正常完成");
        assert_eq!(c.error.as_deref(), Some("overloaded"), "error 事件应携带错误消息");
        assert!(c.usage.is_none(), "error 不带出 usage");
        assert!(c.delta.is_empty(), "error 不带文本增量");
        assert!(acc.is_some(), "error 不应清空已累积的 usage（与原实现一致）");
    }

    /// error 事件无 error.message 字段时兜底为 "stream error"
    #[test]
    fn anthropic_error_event_missing_message_falls_back() {
        let mut acc: Option<TokenUsage> = None;
        let c = apply_anthropic_event(r#"{"type":"error"}"#, &mut acc);
        assert_eq!(c.error.as_deref(), Some("stream error"));
        assert!(!c.finished);
    }

    /// ping / content_block_stop 等事件 → 空且不 finished
    #[test]
    fn anthropic_ping_and_block_stop_yield_empty_chunk() {
        let mut acc: Option<TokenUsage> = None;
        let c = apply_anthropic_event(r#"{"type":"ping"}"#, &mut acc);
        assert!(!c.finished);
        assert_eq!(c.delta, "");
        assert!(acc.is_none());
        let c = apply_anthropic_event(r#"{"type":"content_block_stop","index":0}"#, &mut acc);
        assert!(!c.finished);
        assert_eq!(c.delta, "");
    }

    /// 非法 JSON → 空 chunk，不 panic
    #[test]
    fn anthropic_malformed_json_yields_empty_chunk() {
        let mut acc: Option<TokenUsage> = None;
        let c = apply_anthropic_event("not json", &mut acc);
        assert!(!c.finished);
        assert_eq!(c.delta, "");
        assert!(acc.is_none());
    }

    // ---------- 多模态 convert_request ----------

    /// 含图 user 消息 → content blocks（text + image source.base64）
    #[test]
    fn anthropic_convert_multimodal_user_blocks() {
        let provider = AnthropicCompatProvider::new("https://api.anthropic.com", "k", "claude-3-5-sonnet");
        let req = CompletionRequest {
            model: "claude-3-5-sonnet".into(),
            messages: vec![ChatMessage::user_parts(
                "看图",
                vec![crate::provider::ContentPart::image_base64("image/png", "iVBOR")],
            )],
            temperature: None,
            max_tokens: None,
            stream: false,
            tools: None,
            tool_choice: None,
            reasoning_content: None,
        };
        let body = provider.convert_request(req);
        // user 消息 content 应为数组形态
        let user_msg = body
            .messages
            .iter()
            .find(|m| m.get("role").and_then(|r| r.as_str()) == Some("user"))
            .expect("应有 user 消息");
        let content = user_msg.get("content").and_then(|c| c.as_array()).expect("user content 应为数组");
        // 顺序：content "看图" → text 块，image 片 → image 块
        assert_eq!(content.len(), 2);
        assert_eq!(content[0]["type"], "text");
        assert_eq!(content[0]["text"], "看图");
        assert_eq!(content[1]["type"], "image");
        assert_eq!(content[1]["source"]["type"], "base64");
        assert_eq!(content[1]["source"]["media_type"], "image/png");
        assert_eq!(content[1]["source"]["data"], "iVBOR");
    }

    /// 纯文本 user 消息 → content 仍是字符串简写（无图不数组化）
    #[test]
    fn anthropic_convert_text_only_user_remains_string() {
        let provider = AnthropicCompatProvider::new("https://api.anthropic.com", "k", "claude-3-5-sonnet");
        let req = CompletionRequest {
            model: "claude-3-5-sonnet".into(),
            messages: vec![ChatMessage::user("hello")],
            temperature: None,
            max_tokens: None,
            stream: false,
            tools: None,
            tool_choice: None,
            reasoning_content: None,
        };
        let body = provider.convert_request(req);
        let user_msg = body
            .messages
            .iter()
            .find(|m| m.get("role").and_then(|r| r.as_str()) == Some("user"))
            .expect("应有 user 消息");
        // 纯文本 → 字符串简写（非数组）
        assert_eq!(user_msg.get("content").and_then(|c| c.as_str()), Some("hello"));
    }

    // ---------- ensure_leading_user（首条 assistant → 补 user 占位,保留上下文）----------

    /// 辅助:构造 assistant(tool_use) 消息
    fn msg_assistant_with_tool_use(text: &str, tool_id: &str, tool_name: &str) -> ChatMessage {
        ChatMessage::assistant_with_tools(
            text,
            vec![ToolCall::new(tool_id, tool_name, "{}")],
        )
    }

    /// 精确复现线上场景——多轮 [asst(tool_use), tool_result] 链,首条 assistant。
    /// 补一条 user 占位后:首条 user、tool_use/tool_result 配对完整保留、precheck 通过。
    /// (原"砍"策略会把每对三元组砍掉,多轮砍到空,丢失全部工具调用历史——"补"策略零丢失。)
    #[test]
    fn anthropic_ensure_leading_user_tool_use_chain_preserves_context() {
        let provider = AnthropicCompatProvider::new("https://api.anthropic.com", "k", "claude-3-5-sonnet");
        let req = CompletionRequest {
            model: "claude-3-5-sonnet".into(),
            messages: vec![
                msg_assistant_with_tool_use("我来帮你", "call_f892", "read_file"),
                ChatMessage::tool_result("call_f892", "file content"),
                msg_assistant_with_tool_use("继续", "call_003a", "write_file"),
                ChatMessage::tool_result("call_003a", "done"),
            ],
            temperature: None,
            max_tokens: None,
            stream: false,
            tools: None,
            tool_choice: None,
            reasoning_content: None,
        };
        let body = provider.convert_request(req);

        // 首条必须是 user(补的占位)
        let first_role = body.messages[0].get("role").and_then(|r| r.as_str()).unwrap_or("");
        assert_eq!(first_role, "user", "首条应为 user(补占位)");

        // 上下文零丢失:占位 user + asst(tu) + user(tr) + asst(tu) + user(tr) = 5 条
        assert_eq!(
            body.messages.len(), 5,
            "应保留全部上下文(占位 + 原 4 条),实际 {} 条", body.messages.len()
        );

        assert!(
            AnthropicCompatProvider::precheck_messages(&body.messages).is_ok(),
            "precheck 应通过,实际 messages: {}",
            AnthropicCompatProvider::summarize_messages(&body.messages)
        );
    }

    /// 首条 assistant 无 tool_use → 补占位,首条 user,原上下文保留。
    #[test]
    fn anthropic_ensure_leading_user_plain_assistant() {
        let provider = AnthropicCompatProvider::new("https://api.anthropic.com", "k", "claude-3-5-sonnet");
        let req = CompletionRequest {
            model: "claude-3-5-sonnet".into(),
            messages: vec![
                ChatMessage::assistant("你好"),
                ChatMessage::user("请帮我"),
            ],
            temperature: None,
            max_tokens: None,
            stream: false,
            tools: None,
            tool_choice: None,
            reasoning_content: None,
        };
        let body = provider.convert_request(req);
        assert_eq!(body.messages.len(), 3, "占位 + 原 2 条");
        assert_eq!(
            body.messages[0].get("role").and_then(|r| r.as_str()),
            Some("user"),
        );
        assert!(AnthropicCompatProvider::precheck_messages(&body.messages).is_ok());
    }

    /// 正常序列(user 开头)不补占位——零回归验证。
    #[test]
    fn anthropic_ensure_leading_user_normal_sequence_unchanged() {
        let provider = AnthropicCompatProvider::new("https://api.anthropic.com", "k", "claude-3-5-sonnet");
        let req = CompletionRequest {
            model: "claude-3-5-sonnet".into(),
            messages: vec![
                ChatMessage::user("hello"),
                ChatMessage::assistant("hi there"),
            ],
            temperature: None,
            max_tokens: None,
            stream: false,
            tools: None,
            tool_choice: None,
            reasoning_content: None,
        };
        let body = provider.convert_request(req);
        assert_eq!(body.messages.len(), 2, "正常序列不应补占位");
        assert_eq!(
            body.messages[0].get("role").and_then(|r| r.as_str()),
            Some("user"),
        );
        assert!(AnthropicCompatProvider::precheck_messages(&body.messages).is_ok());
    }

    /// 线上 3 轮工具调用场景(6 条 [asst(tu),tool_result]×3,首条 assistant)。
    /// 补一个 user 占位后全部保留,验证多轮链不丢数据、precheck 通过(原"砍"策略此场景砍到空)。
    #[test]
    fn anthropic_ensure_leading_user_three_round_chain() {
        let provider = AnthropicCompatProvider::new("https://api.anthropic.com", "k", "claude-3-5-sonnet");
        let req = CompletionRequest {
            model: "claude-3-5-sonnet".into(),
            messages: vec![
                msg_assistant_with_tool_use("a1", "call_1", "read_file"),
                ChatMessage::tool_result("call_1", "r1"),
                msg_assistant_with_tool_use("a2", "call_2", "write_file"),
                ChatMessage::tool_result("call_2", "r2"),
                msg_assistant_with_tool_use("a3", "call_3", "list_directory"),
                ChatMessage::tool_result("call_3", "r3"),
            ],
            temperature: None,
            max_tokens: None,
            stream: false,
            tools: None,
            tool_choice: None,
            reasoning_content: None,
        };
        let body = provider.convert_request(req);
        let first_role = body.messages[0].get("role").and_then(|r| r.as_str()).unwrap_or("");
        assert_eq!(first_role, "user", "首条应为 user(补占位)");
        // 占位 + 3×[asst(tu),user(tr)] = 7 条,全部保留
        assert_eq!(
            body.messages.len(), 7,
            "3 轮链应全保留(占位 + 原 6 条),实际 {} 条", body.messages.len()
        );
        assert!(
            AnthropicCompatProvider::precheck_messages(&body.messages).is_ok(),
            "3 轮链补占位后 precheck 应通过,实际: {}",
            AnthropicCompatProvider::summarize_messages(&body.messages)
        );
    }

    /// 空 messages(异常会话经 sanitize 清空)→ convert 补 1 条 user 占位,
    /// 避免发空 messages 触发 precheck "messages 为空"(降级让会话能继续)。
    #[test]
    fn anthropic_ensure_leading_user_empty_messages_gets_placeholder() {
        let provider = AnthropicCompatProvider::new("https://api.anthropic.com", "k", "claude-3-5-sonnet");
        let req = CompletionRequest {
            model: "claude-3-5-sonnet".into(),
            messages: vec![],
            temperature: None,
            max_tokens: None,
            stream: false,
            tools: None,
            tool_choice: None,
            reasoning_content: None,
        };
        let body = provider.convert_request(req);
        assert_eq!(body.messages.len(), 1, "空 messages 应补 1 条 user 占位");
        assert_eq!(
            body.messages[0].get("role").and_then(|r| r.as_str()),
            Some("user"),
        );
        assert!(
            AnthropicCompatProvider::precheck_messages(&body.messages).is_ok(),
            "补占位后 precheck 应通过"
        );
    }
}
