//! LLM Provider trait — 统一的 LLM 调用抽象
//!
//! 支持 OpenAI 兼容 API（覆盖 OpenAI / GLM / DeepSeek / Claude 兼容模式），
//! 含 function calling / tool use 能力。
//!
//! 本文件含 trait + 类型 impl 块（零 IO）。纯类型定义（CompletionRequest /
//! ChatMessage / ContentPart 等 `#[derive]` struct/enum）已拆分到 `types.rs`，
//! 经下方 `pub use crate::types::*` re-export 保持外部路径
//! `df_ai_core::provider::*` / `df_ai::provider::*` 不变（编译期验证）。
//! HTTP impl（OpenAICompatProvider / AnthropicCompatProvider）+ 业务逻辑
//! （ContextManager / AiToolRegistry / build_provider 工厂）留在 df-ai crate。

use async_trait::async_trait;

// 透明 re-export：types.rs 中所有 pub 类型经此回流到 provider::* glob,
// 使 df_ai::provider::*（df-ai/src/provider.rs:15 的 `pub use df_ai_core::provider::*`）
// 与直接 `df_ai_core::provider::Type` 限定路径全部继续可用。
pub use crate::types::*;
use crate::types::{new_message_id, now_millis_i64};

impl ContentPart {
    pub fn text(text: impl Into<String>) -> Self {
        ContentPart::Text { text: text.into() }
    }
    pub fn image_base64(media_type: impl Into<String>, data: impl Into<String>) -> Self {
        ContentPart::Image {
            url: None,
            base64: Some(data.into()),
            media_type: Some(media_type.into()),
            alt: None,
        }
    }
    pub fn image_url(url: impl Into<String>) -> Self {
        ContentPart::Image { url: Some(url.into()), base64: None, media_type: None, alt: None }
    }

    /// 是否图片片
    pub fn is_image(&self) -> bool {
        matches!(self, ContentPart::Image { .. })
    }
}

impl ChatMessage {
    pub fn system(content: impl Into<String>) -> Self {
        Self { id: Some(new_message_id()), role: MessageRole::System, content: content.into(), parts: None, tool_call_id: None, tool_calls: None, model: None, status: None, reasoning_content: None, prompt_tokens: None, completion_tokens: None, timestamp: Some(now_millis_i64()) }
    }
    pub fn user(content: impl Into<String>) -> Self {
        Self { id: Some(new_message_id()), role: MessageRole::User, content: content.into(), parts: None, tool_call_id: None, tool_calls: None, model: None, status: None, reasoning_content: None, prompt_tokens: None, completion_tokens: None, timestamp: Some(now_millis_i64()) }
    }
    pub fn assistant(content: impl Into<String>) -> Self {
        Self { id: Some(new_message_id()), role: MessageRole::Assistant, content: content.into(), parts: None, tool_call_id: None, tool_calls: None, model: None, status: None, reasoning_content: None, prompt_tokens: None, completion_tokens: None, timestamp: Some(now_millis_i64()) }
    }
    pub fn assistant_with_tools(content: impl Into<String>, tool_calls: Vec<ToolCall>) -> Self {
        Self { id: Some(new_message_id()), role: MessageRole::Assistant, content: content.into(), parts: None, tool_call_id: None, tool_calls: Some(tool_calls), model: None, status: None, reasoning_content: None, prompt_tokens: None, completion_tokens: None, timestamp: Some(now_millis_i64()) }
    }
    pub fn tool_result(call_id: impl Into<String>, content: impl Into<String>) -> Self {
        Self { id: Some(new_message_id()), role: MessageRole::Tool, content: content.into(), parts: None, tool_call_id: Some(call_id.into()), tool_calls: None, model: None, status: None, reasoning_content: None, prompt_tokens: None, completion_tokens: None, timestamp: Some(now_millis_i64()) }
    }

    /// 多模态 user 消息：content 文本 + parts（含 Image 片）。
    /// content 作为人类可读文本（也作非 vision 端点降级载荷）；parts 透传给 vision 端点。
    pub fn user_parts(content: impl Into<String>, parts: Vec<ContentPart>) -> Self {
        Self { id: Some(new_message_id()), role: MessageRole::User, content: content.into(), parts: Some(parts), tool_call_id: None, tool_calls: None, model: None, status: None, reasoning_content: None, prompt_tokens: None, completion_tokens: None, timestamp: Some(now_millis_i64()) }
    }

    /// 是否含图片片（供 provider 判定走多模态分支）。
    pub fn has_image(&self) -> bool {
        self.parts.as_ref().map(|ps| ps.iter().any(|p| p.is_image())).unwrap_or(false)
    }

    /// parts 若存在则返回引用，否则 None。
    pub fn parts(&self) -> Option<&[ContentPart]> {
        self.parts.as_deref()
    }

    /// 把 content + parts 拍平为有序 ContentPart 序列：先 content 作 Text 片（非空时），
    /// 再追加 parts（若有）。供 provider 生成 content blocks（保证文本在前、图片在后）。
    pub fn flattened_parts(&self) -> Vec<ContentPart> {
        let mut out: Vec<ContentPart> = Vec::new();
        if !self.content.is_empty() {
            out.push(ContentPart::Text { text: self.content.clone() });
        }
        if let Some(ps) = &self.parts {
            for p in ps {
                out.push(p.clone());
            }
        }
        out
    }

    /// 是否处于 active 态（status 为 None 或 "active"）。其余状态一律 false。
    ///
    /// 正面白名单：仅认 None / "active"，新状态
    /// （如"archived_segment" / "compressed"）自动落入不 active 分支，
    /// 无需每加一个状态就来这里改。当前取值 None/Some("active")/Some("truncated")
    /// 行为与旧反面排除完全等价（None=true / "active"=true / "truncated"=false）。
    pub fn is_active(&self) -> bool {
        matches!(self.status, None | Some(MessageStatus::Active))
    }
}

impl ToolDefinition {
    pub fn function(name: impl Into<String>, description: impl Into<String>, parameters: serde_json::Value) -> Self {
        Self {
            tool_type: ToolType::new("function"),
            function: ToolFunction { name: name.into(), description: description.into(), parameters },
        }
    }
}

impl ToolCall {
    pub fn new(id: impl Into<String>, name: impl Into<String>, arguments: impl Into<String>) -> Self {
        Self {
            id: id.into(),
            call_type: "function".into(),
            function: ToolCallFunction { name: name.into(), arguments: arguments.into() },
        }
    }
}

/// 解析点统一兜底：tool_call.id 空 → 生成唯一 fallback，非空原样。
///
/// 根因（实证会话 01f05167 SenseNova flash-lite）：某些 provider（SenseNova 兼容缺陷）
/// 返回空 `tool_call.id`（""）。OpenAI 协议要求 id 唯一。DevFlow 多 tool_call 按 id
/// 路由结果，id 空时所有结果落到同一 key（`audit/mod.rs:203` 的 `seen_ids` 去重把空 id
/// 视为相同，只留首个 tool_call）→ AI 看到「所有调用同一结果」，工具全失败。
///
/// 兜底在**解析点**生成 fallback id：raw 非空用 raw，空用 `format!("{prefix}_{n}")`
/// （n 取自下方 `FALLBACK_ID_COUNTER` **全局递增计数器**，跨轮跨 assistant 唯一）。
/// 下游（工具执行 / tool 结果回填 tool_call_id）从解析后的 `ToolCall.id` 取，不重复生成，
/// 确保 assistant tool_call.id 与 tool 结果 tool_call_id 匹配（防 sanitize 三元组断裂）。
///
/// 三处解析点共用本 helper（DRY）：OpenAI 同步 `parse_tool_calls`（prefix=`gen_tool`）、
/// OpenAI 流式 chunk（prefix=`gen_stream`）、Anthropic 同步 + 流式（prefix=`gen_anthropic` /
/// `gen_anthropic_stream`）。正常 provider（OpenAI/Claude/GLM id 非空）原样透传零介入。
///
/// # 为何用全局计数器而非单轮 index（实证 af2fab4e）
///
/// 旧实现 fallback 用 `format!("{prefix}_{index}")`，index 是**单轮** tool_call 数组
/// 位置。跨轮（不同 assistant）index 都从 0 起 → `gen_stream_0` 跨轮重复。agentic 的
/// `id_to_name`（`insert(id, name)`）后者覆盖前者 → run_command 的 exit=1 被误标
/// grep::exit=1 → L1 误熔断 grep（冤枉）→ loop 停 → 最后 assistant 空 content tool_calls
/// 没执行（空气泡）。更严重：id 重复 → tool 结果配错 tool_call（三元组配对错位）。
///
/// 全局 `AtomicU64`（SeqCst）跨轮跨 assistant 严格递增，fallback id 永不重复。`index`
/// 参数保留仅为签名兼容（4 处调用点 parse_tool_calls / 流式 chunk / push / agentic 都传），
/// fallback 内部不再使用 index。
///
/// 单测跨进程实例计数器从 0 起；并发场景下两线程拿到的 fallback id 也严格递增（SeqCst），
/// 保证全局唯一。
pub fn tool_call_id_or_fallback(raw: &str, _index: usize, prefix: &str) -> String {
    if !raw.is_empty() {
        raw.to_string()
    } else {
        let n = FALLBACK_ID_COUNTER.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
        format!("{prefix}_{n}")
    }
}

/// fallback id 全局计数器：跨轮跨 assistant 严格递增，保证空 id fallback 永不重复。
///
/// 见 `tool_call_id_or_fallback` 文档说明（实证 af2fab4e 跨轮重复根因）。
static FALLBACK_ID_COUNTER: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);

/// LLM Provider trait
#[async_trait]
pub trait LlmProvider: Send + Sync {
    /// 同步调用
    async fn complete(&self, request: CompletionRequest) -> anyhow::Result<CompletionResponse>;

    /// 流式调用（返回异步流）
    async fn stream(
        &self,
        request: CompletionRequest,
    ) -> anyhow::Result<StreamResult>;

    /// 文本嵌入：批量文本 → 语义向量(供知识库向量检索)
    ///
    /// 默认实现返回 Err(协议不支持)。OpenAI 兼容协议覆盖实现(/v1/embeddings);
    /// Anthropic 无 embedding API,保持默认。
    async fn embed(&self, _model: &str, _texts: Vec<String>) -> anyhow::Result<Vec<Vec<f32>>> {
        anyhow::bail!("该 Provider 不支持 embedding({})", self.name())
    }

    /// Provider 名称
    fn name(&self) -> &str;

    /// 实际请求端点（含 base_url + 关键路径，如 chat completions / messages）。
    /// 默认回落 `name()`，provider 实现覆盖返真实 URL，供 401/网络错误诊断打印
    /// —— 旧路径只能近似打印 provider_type，看不到实际请求端点。
    fn endpoint(&self) -> String {
        self.name().to_string()
    }
}

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

    #[test]
    fn is_active_whitelist() {
        // 正面白名单：仅 None / "active" 为 true，其余一律 false。
        // 零行为变化：None / "active" / "truncated" 与旧反面排除完全等价；
        // "archived_segment" / "compressed" 由白名单 matches! 只认 None/active 自动落入 false。

        // None（构造默认值，向前兼容老 JSON）
        let m = ChatMessage::user("hi");
        assert!(m.is_active(), "None 应 active");

        // "active"
        let mut m = ChatMessage::user("hi");
        m.status = Some(MessageStatus::Active);
        assert!(m.is_active(), "Some(active) 应 active");

        // "truncated" — 当前取值，与旧实现等价（false）
        let mut m = ChatMessage::user("hi");
        m.status = Some(MessageStatus::Truncated);
        assert!(!m.is_active(), "truncated 应不 active");

        // "archived_segment" — 白名单自动隔离
        let mut m = ChatMessage::user("hi");
        m.status = Some(MessageStatus::ArchivedSegment);
        assert!(!m.is_active(), "archived_segment 应不 active（白名单隔离）");

        // "compressed" — 白名单自动隔离
        let mut m = ChatMessage::user("hi");
        m.status = Some(MessageStatus::Compressed);
        assert!(!m.is_active(), "compressed 应不 active（白名单隔离）");
    }

    // ---------- ContentPart ----------

    /// 老 JSON（无 parts 字段）反序列化时 parts 应为 None（向前兼容）
    #[test]
    fn contentpart_legacy_json_no_parts() {
        let json = r#"{"role":"user","content":"hello"}"#;
        let m: ChatMessage = serde_json::from_str(json).expect("老 JSON 应可反序列化");
        assert_eq!(m.content, "hello");
        assert!(m.parts.is_none(), "老 JSON 无 parts 字段 → None");
        assert!(!m.has_image());
    }

    /// 新 JSON 带 parts（含 Image 片）反序列化 round-trip
    #[test]
    fn contentpart_with_image_roundtrip() {
        let m = ChatMessage::user_parts(
            "看这张图",
            vec![
                ContentPart::image_base64("image/png", "iVBORw0KGgo="),
                ContentPart::text("说明"),
            ],
        );
        let json = serde_json::to_string(&m).expect("序列化");
        // parts 顺序：[image_base64, text] → 首元素是 image
        assert!(json.contains(r#""parts":[{"type":"image""#));
        assert!(json.contains(r#""type":"text""#));
        assert!(json.contains(r#""base64":"iVBORw0KGgo=""#));
        assert!(json.contains(r#""media_type":"image/png""#));

        let back: ChatMessage = serde_json::from_str(&json).expect("反序列化 round-trip");
        assert_eq!(back.content, "看这张图");
        assert!(back.has_image());
        let parts = back.parts.expect("parts 应存在");
        assert_eq!(parts.len(), 2);
        assert!(parts[0].is_image());
    }

    /// has_image 判定
    #[test]
    fn contentpart_has_image_detection() {
        assert!(!ChatMessage::user("纯文本").has_image());
        let m = ChatMessage::user_parts("t", vec![ContentPart::text("只文本片")]);
        assert!(!m.has_image(), "仅 Text 片不算 has_image");
        let m = ChatMessage::user_parts(
            "t",
            vec![ContentPart::text("前缀"), ContentPart::image_url("https://x/a.png")],
        );
        assert!(m.has_image());
    }

    /// flattened_parts：content 非空 → 前置 Text 片 + parts 追加
    #[test]
    fn contentpart_flattened_parts() {
        let m = ChatMessage::user("hi");
        let flat = m.flattened_parts();
        assert_eq!(flat.len(), 1);
        assert_eq!(flat[0], ContentPart::Text { text: "hi".into() });

        let m = ChatMessage::user_parts(
            "cap",
            vec![ContentPart::image_url("u"), ContentPart::text("尾")],
        );
        let flat = m.flattened_parts();
        assert_eq!(flat.len(), 3);
        assert_eq!(flat[0], ContentPart::Text { text: "cap".into() });
        assert!(flat[1].is_image());
        assert_eq!(flat[2], ContentPart::Text { text: "尾".into() });

        let mut m = ChatMessage::user("");
        m.parts = Some(vec![ContentPart::text("x")]);
        let flat = m.flattened_parts();
        assert_eq!(flat.len(), 1, "空 content 不应产生空 Text 片");
    }

    /// 向后兼容：旧代码 ChatMessage 字面量构造仍合法（audit/title/commands 零回归）
    #[test]
    fn contentpart_struct_literal_compat() {
        let m = ChatMessage {
            id: None,
            role: MessageRole::User,
            content: "字面量构造".into(),
            parts: None,
            tool_call_id: None,
            tool_calls: None,
            model: None,
            status: None,
            reasoning_content: None,
            timestamp: None,
        };
        assert_eq!(m.content, "字面量构造");
        assert!(m.parts.is_none());
    }

    // ---------- DeepSeek reasoning_content ----------

    /// 有 reasoning_content 时应序列化出来；None 时不出现
    #[test]
    fn completion_request_reasoning_content_serialization() {
        let req = CompletionRequest {
            model: "deepseek-r1".to_string(),
            messages: vec![ChatMessage::user("hello")],
            temperature: None,
            max_tokens: None,
            stream: false,
            tools: None,
            tool_choice: None,
            reasoning_content: Some("let me think...".to_string()),
        };
        let json = serde_json::to_string(&req).unwrap();
        assert!(json.contains("reasoning_content"), "reasoning_content 应出现在 JSON 中, got: {}", json);

        // None 时不应出现（skip_serializing_if）
        let req_none = CompletionRequest { reasoning_content: None, ..req };
        let json_none = serde_json::to_string(&req_none).unwrap();
        assert!(!json_none.contains("reasoning_content"), "None 的 reasoning_content 不应序列化, got: {}", json_none);
    }

    /// 所有便捷构造函数默认 reasoning_content 为 None
    #[test]
    fn chat_message_reasoning_content_constructors() {
        assert!(ChatMessage::system("sys").reasoning_content.is_none());
        assert!(ChatMessage::user("hi").reasoning_content.is_none());
        assert!(ChatMessage::assistant("resp").reasoning_content.is_none());
        assert!(ChatMessage::tool_result("call_1", "result").reasoning_content.is_none());
    }

    /// ChatMessage reasoning_content 序列化 round-trip
    #[test]
    fn chat_message_reasoning_content_roundtrip() {
        let m = ChatMessage {
            id: None,
            role: MessageRole::Assistant,
            content: "answer".to_string(),
            parts: None,
            tool_call_id: None,
            tool_calls: None,
            model: None,
            status: None,
            reasoning_content: Some("thinking process".to_string()),
            timestamp: None,
        };
        let json = serde_json::to_string(&m).unwrap();
        assert!(json.contains("reasoning_content"));

        let deserialized: ChatMessage = serde_json::from_str(&json).unwrap();
        assert_eq!(deserialized.reasoning_content, Some("thinking process".to_string()));
    }

    // ---------- 消息级溯源:id 字段 ----------

    /// 所有便捷构造函数默认生成非 None 的 id(ULID 风格)
    #[test]
    fn chat_message_id_generated_by_constructors() {
        assert!(ChatMessage::system("sys").id.is_some(), "system 应有 id");
        assert!(ChatMessage::user("hi").id.is_some(), "user 应有 id");
        assert!(ChatMessage::assistant("resp").id.is_some(), "assistant 应有 id");
        assert!(
            ChatMessage::assistant_with_tools("r", vec![]).id.is_some(),
            "assistant_with_tools 应有 id"
        );
        assert!(
            ChatMessage::tool_result("call_1", "result").id.is_some(),
            "tool_result 应有 id"
        );
        assert!(
            ChatMessage::user_parts("t", vec![ContentPart::text("x")]).id.is_some(),
            "user_parts 应有 id"
        );
    }

    /// id 全局唯一性:连续构造 100 条不重复(AtomicU64 计数器保证)
    #[test]
    fn chat_message_id_uniqueness() {
        let mut ids = std::collections::HashSet::new();
        for _ in 0..100 {
            let m = ChatMessage::user("x");
            let id = m.id.expect("构造的消息应有 id");
            assert!(ids.insert(id), "100 条消息 id 应全部唯一");
        }
    }

    /// id 序列化 round-trip:有 id 时序列化保留,反序列化回来一致
    #[test]
    fn chat_message_id_roundtrip() {
        let m = ChatMessage {
            id: Some("msg_1718800000000_42".to_string()),
            role: MessageRole::Assistant,
            content: "answer".to_string(),
            parts: None,
            tool_call_id: None,
            tool_calls: None,
            model: None,
            status: None,
            reasoning_content: None,
            timestamp: None,
        };
        let json = serde_json::to_string(&m).unwrap();
        assert!(json.contains(r#""id":"msg_1718800000000_42""#), "id 应序列化, got: {}", json);

        let back: ChatMessage = serde_json::from_str(&json).unwrap();
        assert_eq!(back.id.as_deref(), Some("msg_1718800000000_42"));
    }

    /// 向前兼容:老 JSON 无 id 字段 → 反序列化为 None(serde default)
    #[test]
    fn chat_message_id_legacy_json_no_id() {
        let json = r#"{"role":"user","content":"hello"}"#;
        let m: ChatMessage = serde_json::from_str(json).expect("老 JSON 应可反序列化");
        assert_eq!(m.content, "hello");
        assert!(m.id.is_none(), "老 JSON 无 id 字段 → None");

        // 重新序列化:id=None 时不应出现 id 字段(skip_serializing_if)
        let re = serde_json::to_string(&m).unwrap();
        assert!(!re.contains(r#""id""#), "id=None 不应序列化, got: {}", re);
    }

    /// StreamChunk reasoning_content 序列化
    #[test]
    fn stream_chunk_reasoning_content() {
        let chunk = StreamChunk {
            delta: "text".to_string(),
            finished: false,
            tool_calls: None,
            usage: None,
            error: None,
            reasoning_content: Some("thought".to_string()),
        };
        let json = serde_json::to_string(&chunk).unwrap();
        assert!(json.contains("reasoning_content"));

        let chunk_none = StreamChunk { reasoning_content: None, ..chunk };
        let json_none = serde_json::to_string(&chunk_none).unwrap();
        assert!(!json_none.contains("reasoning_content"));
    }

    /// CompletionResponse reasoning_content 序列化
    #[test]
    fn completion_response_reasoning_content() {
        let resp = CompletionResponse {
            text: "ok".to_string(),
            model: "r1".to_string(),
            usage: TokenUsage { prompt_tokens: 10, completion_tokens: 20, total_tokens: 30 },
            tool_calls: None,
            reasoning_content: Some("r1 thought".to_string()),
        };
        let json = serde_json::to_string(&resp).unwrap();
        assert!(json.contains("reasoning_content"));

        let deserialized: CompletionResponse = serde_json::from_str(&json).unwrap();
        assert_eq!(deserialized.reasoning_content, Some("r1 thought".to_string()));
    }

    /// CR-空 id：tool_call_id_or_fallback 共享 helper —— 空 raw → fallback，非空原样。
    #[test]
    fn tool_call_id_or_fallback_non_empty_passthrough() {
        // 非空 raw 原样透传（provider 真 id 如 call_xxx 保留），与 index/prefix 无关
        assert_eq!(tool_call_id_or_fallback("call_abc", 0, "gen_tool"), "call_abc");
        assert_eq!(tool_call_id_or_fallback("x", 5, "p"), "x");
    }

    #[test]
    fn tool_call_id_or_fallback_empty_starts_with_prefix() {
        // 空 raw → "{prefix}_{n}"，n 取自全局计数器（跨进程实例从 0 起，单测不假设具体值）
        let a = tool_call_id_or_fallback("", 0, "gen_tool");
        assert!(a.starts_with("gen_tool_"), "空 fallback 应以 gen_tool_ 开头, got: {a}");
        let b = tool_call_id_or_fallback("", 7, "gen_stream");
        assert!(b.starts_with("gen_stream_"), "空 fallback 应以 gen_stream_ 开头, got: {b}");
    }

    #[test]
    fn tool_call_id_or_fallback_empty_globally_unique() {
        // 跨轮跨 assistant 唯一：连续两次空 fallback id 必不同（全局计数器递增）。
        // 这是修复 af2fab4e 跨轮重复（旧单轮 index 跨轮都从 0 起 → 重复）的核心断言。
        let a = tool_call_id_or_fallback("", 0, "gen_tool");
        let b = tool_call_id_or_fallback("", 0, "gen_tool");
        assert_ne!(a, b, "两次空 fallback 应不同（全局计数器跨轮唯一）: {a} vs {b}");
        // 即使同 index（模拟跨轮 index 都从 0 起），fallback 也必唯一
        let c = tool_call_id_or_fallback("", 0, "gen_tool");
        let mut set = std::collections::HashSet::new();
        assert!(set.insert(a), "fallback a 应唯一");
        assert!(set.insert(b), "fallback b 应唯一");
        assert!(set.insert(c), "fallback c 应唯一");
    }

    #[test]
    fn tool_call_id_or_fallback_index_unused() {
        // index 参数仅为签名兼容保留（4 处调用点都传），fallback 不再使用 index。
        // 同 prefix + 同 index 连续两次 → 不同 fallback（全局计数器递增，与 index 无关）。
        let a = tool_call_id_or_fallback("", 3, "gen_tool");
        let b = tool_call_id_or_fallback("", 3, "gen_tool");
        assert_ne!(a, b, "同 index 两次空 fallback 应不同: {a} vs {b}");
    }

    #[test]
    fn tool_call_id_or_fallback_prefix_distinguishes_sources() {
        // 不同 prefix 区分来源（同步 gen_tool / 流式 gen_stream / anthropic gen_anthropic）
        // 注意：两次空 fallback 因全局计数器递增 id 不同，故只比 prefix 前缀
        let a = tool_call_id_or_fallback("", 0, "gen_tool");
        let b = tool_call_id_or_fallback("", 0, "gen_stream");
        assert!(a.starts_with("gen_tool_"));
        assert!(b.starts_with("gen_stream_"));
    }
}
