//! 对抗式评估系统 — 正反方辩论 + AI 分析师
//!
//! 双轨实现：
//! - **启发式**（默认/降级）：基于评分与内容信号生成正反方论点，稳定有区分度。
//! - **LLM**（注入 provider 后）：调一次 `complete()` 让论点由 LLM 生成，失败自动降级启发式。
//!
//! 评估来源由 [`EvaluatedBy`] 三态标记：`Llm`（LLM 深度评估）/ `Heuristic`（主动选启发式，
//! 无 provider）/ `HeuristicFallback`（LLM 调用失败降级）。前端可据此显示评估深度标签。
//!
//! LLM prompt 构造与 JSON 解析在后续接入：[`AdversarialEngine::evaluate_with_llm`]
//! 构造三角色辩论 prompt（正方/反方/分析师），调一次 `complete()` 要求返回对齐结构的 JSON，
//! 解析失败/字段缺失/枚举非法 → `bail` 触发降级（[`AdversarialEngine::evaluate`] 已兜底）。
//!
//! 重构（strategy·自底向上）：类型定义 / 常量 / prompt 构造 / JSON 解析等无副作用逻辑抽至
//! [`adversarial_helpers`]，本文件只保留 [`AdversarialEngine`]（有状态引擎 + evaluate 主入口，
//! 含 ARC-260618-01-e evaluate_with_llm 一致性待决策逻辑，原样保留）。外部已用路径（
//! `df_ideas::adversarial::{AdversarialEval, Recommendation, …}`）经 `pub use` 不变。

use std::sync::Arc;

use anyhow::Result;

use df_ai_core::model::ModelConfig;
use df_ai_core::provider::LlmProvider;
use df_types::types::Priority;
use crate::capture::Idea;
use crate::scoring::IdeaScores;

// 类型 / 常量 / 纯函数自 adversarial_helpers 引回，并 re-export：外部调用方经
// `df_ideas::adversarial::{...}` 路径访问的类型零变更（pub use 同时将符号带入当前
// 命名空间供 impl/tests 使用，无需额外 use）。AdversarialEngine 本身定义在本文件。
pub use crate::adversarial_helpers::{
    AdversarialEval, AnalystAnalysis, Argument, AssessmentLevel, EvaluatedBy, Recommendation,
};
use crate::adversarial_helpers::{
    SYSTEM_PROMPT, action_hint, assessment_desc, build_adversarial_prompt, parse_llm_eval,
    priority_label,
};

/// 对抗评估引擎
pub struct AdversarialEngine {
    /// 可选 LLM provider。Some → 优先 LLM 评估（失败降级启发式）；None → 纯启发式。
    /// 构造注入（与 IdeaPromoter::new(policy) 同一模式），批量评估复用同一 provider。
    provider: Option<Arc<dyn LlmProvider>>,
    /// 候选模型池。非空时 evaluate_with_llm 经 select_model_id 路由选模型;
    /// 空(None provider 或未注入池)→ model 留空由 provider impl 回填自身 default_model
    /// (与接入前行为一致,平稳过渡)。
    model_pool: Vec<ModelConfig>,
}

impl AdversarialEngine {
    /// 注入 LLM provider 构造（provider Some 时走 LLM，调用失败自动降级启发式）
    pub fn new(provider: Arc<dyn LlmProvider>) -> Self {
        Self { provider: Some(provider), model_pool: Vec::new() }
    }

    /// 注入 provider + 候选模型池构造。池非空时 evaluate_with_llm 走路由。
    pub fn with_pool(provider: Arc<dyn LlmProvider>, model_pool: Vec<ModelConfig>) -> Self {
        Self { provider: Some(provider), model_pool }
    }

    /// 纯启发式构造（无 LLM 配置时的默认模式）
    pub fn heuristic() -> Self {
        Self { provider: None, model_pool: Vec::new() }
    }

    /// 执行完整的对抗评估（内部按 provider 有无调度 LLM / 启发式，失败降级）
    pub async fn evaluate(&self, idea: &Idea) -> Result<AdversarialEval> {
        match &self.provider {
            Some(p) => match self.evaluate_with_llm(idea, p).await {
                Ok(mut eval) => {
                    eval.evaluated_by = EvaluatedBy::Llm;
                    Ok(eval)
                }
                Err(e) => {
                    // LLM 调用失败/超时/格式异常 → 自动降级启发式，保证前端结构完整返回
                    tracing::warn!("LLM 对抗评估失败, 降级到启发式: {e}");
                    let mut eval = self.evaluate_heuristic(idea)?;
                    eval.evaluated_by = EvaluatedBy::HeuristicFallback;
                    Ok(eval)
                }
            },
            None => {
                let mut eval = self.evaluate_heuristic(idea)?;
                eval.evaluated_by = EvaluatedBy::Heuristic;
                Ok(eval)
            }
        }
    }

    /// LLM 对抗评估（注入 provider 后走此路）。
    ///
    /// 构造三角色对抗式辩论 prompt（正方/反方/分析师），调一次 `complete()`，要求 LLM
    /// 返回对齐 [`AdversarialEval`] 结构的 JSON。任一环节失败（HTTP / 非 JSON / 字段
    /// 缺失 / 枚举非法 / 数值越界无法修正）→ `bail` 由 [`Self::evaluate`] 捕获降级启发式。
    ///
    /// temperature 取 0.4：低于 0.3 偏机械重复启发式信号，高于 0.5 易发散到无关风险，
    /// 0.4 在「稳定可复现」与「论点多样性」间取得平衡。
    ///
    /// ARC-260618-01-e: evaluate_with_llm 返回值一致性未校验（final_score 与
    /// analyst.final_assessment 自洽性等），待产品决策，当前逻辑原样保留不调整。
    async fn evaluate_with_llm(&self, idea: &Idea, provider: &Arc<dyn LlmProvider>) -> Result<AdversarialEval> {
        let prompt = build_adversarial_prompt(idea);
        // 智能路由 — 对抗评估 TaskRequirements(Standard,无工具)。
        // 池非空 → select_model_id 选最优 model_id;池空/无匹配 → 留空由 provider impl
        // 回填自身 default_model(与接入前行为一致,平稳过渡)。
        let eval_req = df_ai::router::TaskRequirements {
            modalities: vec![df_ai_core::model::Modality::Text],
            needs_tool_use: false,
            estimated_context: 0,
            tier: None,
        };
        let model = df_ai::router::select_model_id(&eval_req, &self.model_pool).unwrap_or_default();
        let request = df_ai_core::provider::CompletionRequest {
            // 路由命中 → 用 model_id;否则留空让 provider impl 回填自身 default_model。
            // (OpenAICompatProvider::convert_request 在 req.model.is_empty() 时回退 default_model)
            model,
            messages: vec![
                df_ai_core::provider::ChatMessage::system(SYSTEM_PROMPT),
                df_ai_core::provider::ChatMessage::user(prompt),
            ],
            temperature: Some(0.4),
            max_tokens: Some(2048),
            stream: false,
            tools: None,
            tool_choice: None,
            reasoning_content: None,
        };

        let resp = provider
            .complete(request)
            .await
            .map_err(|e| anyhow::anyhow!("LLM complete 调用失败: {e}"))?;

        // 数值 clamp 由 parse_llm_eval 单点收口（final_score∈[0,10]、confidence∈[0,1]，
        // 均在 parse 内对所有 Ok 路径完成），此处不再重复 clamp（CR-40-1 去冗余）。
        parse_llm_eval(&resp.text, &idea.id)
    }

    /// 启发式评估（基于评分与内容信号，稳定有区分度）
    fn evaluate_heuristic(&self, idea: &Idea) -> Result<AdversarialEval> {
        // 先做多维评分，作为正反方论点与置信度的依据
        let scores = crate::scoring::ScoringEngine::compute_default(idea);

        let positive = self.generate_positive_argument(idea, &scores)?;
        let negative = self.generate_negative_argument(idea, &scores)?;
        let analyst = self.analyst_analysis(idea, &scores)?;
        let recommendation = self.recommendation_for(&analyst.final_assessment);

        Ok(AdversarialEval {
            idea_id: idea.id.clone(),
            positive,
            negative,
            analyst,
            final_score: scores.overall,
            recommendation,
            // 由 evaluate() 调用方按调度路径覆盖（Heuristic / HeuristicFallback）
            evaluated_by: EvaluatedBy::Heuristic,
        })
    }

    /// 生成正方观点（支持执行）— confidence 由可行性 + 影响力驱动
    /// 注:返回 Result 为后续 LLM 注入失败预留,启发式阶段恒 Ok
    fn generate_positive_argument(&self, idea: &Idea, scores: &IdeaScores) -> Result<Argument> {
        let desc = idea.description.trim();
        let mut evidence = Vec::new();
        evidence.push(format!("优先级：{}", priority_label(&idea.priority)));
        if desc.is_empty() {
            evidence.push("需求待补充（建议补全描述）".to_string());
        } else {
            let head: String = desc.chars().take(60).collect();
            evidence.push(format!("明确需求：{}", head));
        }
        if idea.tags.is_empty() {
            evidence.push("关联领域待界定".to_string());
        } else {
            evidence.push(format!("关联领域：{}", idea.tags.join("、")));
        }
        if scores.impact >= 7.0 {
            evidence.push("业务价值显著，影响面较广".to_string());
        }

        // 正方置信度:可行性+影响力等权折算到 [0.1, 0.95],满分≈0.95 留质疑余地
        let confidence =
            ((scores.feasibility * 0.5 + scores.impact * 0.5) / 10.0).clamp(0.1, 0.95);

        let reasoning = vec![
            format!("可行性评分 {:.1}/10，路径相对清晰", scores.feasibility),
            format!("影响力评分 {:.1}/10，预期回报可观", scores.impact),
            "整体风险可控，适合推进".to_string(),
        ];

        Ok(Argument {
            thesis: format!("「{}」具备明确价值与可行性，建议优先推进", idea.title),
            evidence,
            reasoning,
            confidence,
        })
    }

    /// 生成反方观点（反对或谨慎）— 论点基于想法实际缺陷，confidence 随风险上升
    fn generate_negative_argument(&self, idea: &Idea, scores: &IdeaScores) -> Result<Argument> {
        let desc = idea.description.trim();
        let mut evidence = Vec::new();
        if desc.is_empty() {
            evidence.push("描述过于简略，需求边界不清".to_string());
        } else if desc.chars().count() < 50 {
            evidence.push("描述偏短，实现细节尚未论证".to_string());
        }
        if idea.tags.is_empty() {
            evidence.push("缺少标签，影响范围未界定".to_string());
        }
        if scores.feasibility < 6.0 {
            evidence.push(format!("可行性 {:.1}/10 偏低，实现路径存疑", scores.feasibility));
        }
        if matches!(idea.priority, Priority::Low) {
            evidence.push("优先级偏低，可能非当前关键路径".to_string());
        }
        if evidence.is_empty() {
            evidence.push("机会成本需权衡，可能存在更优替代方案".to_string());
        }

        // 反方强度:feasibility 每降 1 分 +0.04,impact 每降 1 分 +0.03,基线 0.25(满分也保留最低质疑),clamp [0.1, 0.9]
        let confidence = ((10.0 - scores.feasibility) * 0.04 + (10.0 - scores.impact) * 0.03 + 0.25)
            .clamp(0.1, 0.9);

        let reasoning = vec![
            format!("资源投入与当前综合评分 {:.1} 需匹配", scores.overall),
            "ROI 需进一步验证".to_string(),
            "需评估是否存在更优解".to_string(),
        ];

        Ok(Argument {
            thesis: format!("「{}」需谨慎评估，存在风险与机会成本", idea.title),
            evidence,
            reasoning,
            confidence,
        })
    }

    /// AI 分析师综合分析 — 评估等级由综合评分决定，优势/劣势按维度动态生成
    fn analyst_analysis(&self, idea: &Idea, scores: &IdeaScores) -> Result<AnalystAnalysis> {
        let final_assessment = match scores.overall {
            x if x >= 7.5 => AssessmentLevel::StrongGo,
            x if x >= 6.0 => AssessmentLevel::Recommended,
            x if x >= 4.5 => AssessmentLevel::Conditional,
            x if x >= 3.0 => AssessmentLevel::Revised,
            _ => AssessmentLevel::Defer,
        };

        let mut strengths = Vec::new();
        if scores.impact >= 6.0 {
            strengths.push("业务价值明确".to_string());
        }
        if scores.feasibility >= 6.0 {
            strengths.push("技术路径清晰".to_string());
        }
        if scores.urgency >= 7.0 {
            strengths.push("时间窗口合适".to_string());
        }
        if strengths.is_empty() {
            strengths.push("方向值得探索".to_string());
        }

        let mut weaknesses = Vec::new();
        if scores.feasibility < 6.0 {
            weaknesses.push("可行性论证不足".to_string());
        }
        if idea.description.trim().is_empty() {
            weaknesses.push("需求描述缺失".to_string());
        }
        if scores.urgency < 4.0 {
            weaknesses.push("紧急度偏低，易被搁置".to_string());
        }
        if weaknesses.is_empty() {
            weaknesses.push("资源需求待评估".to_string());
        }

        // 启发式占位:固定风险模板,与具体想法无关,接 LLM 后改动态生成
        let risks = vec![
            "技术实现难度可能超出预期".to_string(),
            "优先级与资源争夺".to_string(),
            "需求范围蔓延".to_string(),
        ];

        let opportunities = vec![
            "可能形成可复用能力".to_string(),
            "积累技术资产".to_string(),
        ];

        let summary = format!(
            "「{}」综合评分 {:.1}/10，{}。建议{}。",
            idea.title,
            scores.overall,
            assessment_desc(&final_assessment),
            action_hint(&final_assessment)
        );

        Ok(AnalystAnalysis {
            summary,
            strengths,
            weaknesses,
            risks,
            opportunities,
            final_assessment,
        })
    }

    /// 评估等级 → 最终建议
    fn recommendation_for(&self, level: &AssessmentLevel) -> Recommendation {
        match level {
            AssessmentLevel::StrongGo => Recommendation::ImmediateAction,
            AssessmentLevel::Recommended => Recommendation::Soon,
            AssessmentLevel::Conditional => Recommendation::WithResources,
            AssessmentLevel::Revised => Recommendation::ResearchMore,
            AssessmentLevel::Defer => Recommendation::Monitor,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::capture::Idea;
    use crate::scoring::ScoringEngine;
    use df_types::types::{IdeaStatus, Priority};

    fn make_idea(title: &str, desc: &str, priority: Priority, tags: Vec<&str>) -> Idea {
        Idea {
            id: "test-id".to_string(),
            title: title.to_string(),
            description: desc.to_string(),
            status: IdeaStatus::Draft,
            priority,
            scores: None,
            tags: tags.into_iter().map(String::from).collect(),
            source: None,
            related_ids: Vec::new(),
            created_at: chrono::Utc::now(),
            updated_at: chrono::Utc::now(),
        }
    }

    #[tokio::test]
    async fn a1_high_score_immediate_action() {
        let desc = "面向用户的核心功能，带来显著增长，大幅提升效率。集成成熟方案，复用已有组件。".repeat(3);
        let idea = make_idea("AI增长引擎", &desc, Priority::Critical, vec!["增长", "核心"]);
        let scores = ScoringEngine::compute_default(&idea);
        let eval = AdversarialEngine::heuristic().evaluate(&idea).await.unwrap();
        println!("\n[a1] 高分想法 → 期望 ImmediateAction");
        println!("  scores: feas={:.2} impact={:.2} urg={:.2} overall={:.2}", scores.feasibility, scores.impact, scores.urgency, scores.overall);
        println!("  eval: final_score={:.2} recommendation={:?}", eval.final_score, eval.recommendation);
        println!("  正方 confidence={:.2}  反方 confidence={:.2}", eval.positive.confidence, eval.negative.confidence);
        assert!(eval.final_score >= 7.5, "final_score 应≥7.5, 实际 {:.2}", eval.final_score);
        assert_eq!(eval.recommendation, Recommendation::ImmediateAction);
    }

    #[tokio::test]
    async fn a2_mid_score_soon() {
        let desc = "面向用户的功能，集成已有方案，提升体验".to_string();
        let idea = make_idea("体验优化", &desc, Priority::Medium, vec!["体验"]);
        let scores = ScoringEngine::compute_default(&idea);
        let eval = AdversarialEngine::heuristic().evaluate(&idea).await.unwrap();
        println!("\n[a2] 中分想法 → 期望 Soon");
        println!("  scores overall={:.2}  eval final_score={:.2}  recommendation={:?}", scores.overall, eval.final_score, eval.recommendation);
        assert_eq!(eval.recommendation, Recommendation::Soon);
    }

    #[tokio::test]
    async fn a3_low_score_monitor() {
        let desc = "重构迁移大规模分布式重写从零全新架构高并发底层".to_string();
        let idea = make_idea("过度工程", &desc, Priority::Low, vec![]);
        let scores = ScoringEngine::compute_default(&idea);
        let eval = AdversarialEngine::heuristic().evaluate(&idea).await.unwrap();
        println!("\n[a3] 低分想法 → 期望 Monitor");
        println!("  scores overall={:.2}  eval final_score={:.2}  recommendation={:?}", scores.overall, eval.final_score, eval.recommendation);
        assert!(eval.final_score < 3.0, "final_score 应<3.0, 实际 {:.2}", eval.final_score);
        assert_eq!(eval.recommendation, Recommendation::Monitor);
    }

    #[tokio::test]
    async fn a4_confidence_ranges() {
        let idea = make_idea("普通想法", "一般描述", Priority::Medium, vec!["标签"]);
        let eval = AdversarialEngine::heuristic().evaluate(&idea).await.unwrap();
        println!("\n[a4] confidence 区间校验");
        println!("  正方={:.2} (应∈[0.1, 0.95])   反方={:.2} (应∈[0.1, 0.9])", eval.positive.confidence, eval.negative.confidence);
        assert!(eval.positive.confidence >= 0.1 && eval.positive.confidence <= 0.95);
        assert!(eval.negative.confidence >= 0.1 && eval.negative.confidence <= 0.9);
    }

    #[tokio::test]
    async fn a5_positive_thesis_contains_title() {
        let idea = make_idea("独家创意", "描述内容", Priority::High, vec![]);
        let eval = AdversarialEngine::heuristic().evaluate(&idea).await.unwrap();
        println!("\n[a5] 正方论点含标题");
        println!("  thesis: {}", eval.positive.thesis);
        assert!(eval.positive.thesis.contains("独家创意"), "正方 thesis 应含标题");
    }

    #[tokio::test]
    async fn a6_negative_evidence_nonempty() {
        let idea = make_idea("待质疑想法", "短", Priority::Low, vec![]);
        let eval = AdversarialEngine::heuristic().evaluate(&idea).await.unwrap();
        println!("\n[a6] 反方证据非空 ({} 条)", eval.negative.evidence.len());
        for (i, e) in eval.negative.evidence.iter().enumerate() {
            println!("  证据{}: {}", i + 1, e);
        }
        assert!(!eval.negative.evidence.is_empty(), "反方 evidence 不应为空");
    }

    #[tokio::test]
    async fn a7_final_score_consistency() {
        let desc = "面向用户的核心功能".to_string();
        let idea = make_idea("一致性测试", &desc, Priority::High, vec!["核心"]);
        let scores = ScoringEngine::compute_default(&idea);
        let eval = AdversarialEngine::heuristic().evaluate(&idea).await.unwrap();
        println!("\n[a7] final_score == scores.overall 一致性");
        println!("  scores.overall={:.2}  eval.final_score={:.2}", scores.overall, eval.final_score);
        println!("  analyst.summary: {}", eval.analyst.summary);
        assert!((eval.final_score - scores.overall).abs() < 0.001, "final_score 应等于 overall");
        assert!(eval.analyst.summary.contains("一致性测试"), "summary 应含标题");
    }

    // ────────────────────────────────────────────────────────────
    // LLM 路径测试
    // ────────────────────────────────────────────────────────────

    /// mock LlmProvider：按构造时给定的响应文本回放，仅供 adversarial 单测。
    ///
    /// `complete` 返回预设 `text`（或预设错误），`stream` 不被 adversarial 路径调用，
    /// 返回 Err 即可。不引入 futures 依赖。
    struct MockProvider {
        text: String,
    }

    #[async_trait::async_trait]
    impl LlmProvider for MockProvider {
        async fn complete(
            &self,
            _request: df_ai_core::provider::CompletionRequest,
        ) -> anyhow::Result<df_ai_core::provider::CompletionResponse> {
            Ok(df_ai_core::provider::CompletionResponse {
                text: self.text.clone(),
                model: "mock-model".to_string(),
                usage: df_ai_core::provider::TokenUsage::default(),
                tool_calls: None,
                // 对抗评估路径不消费推理内容,留 None 即可(CompletionResponse 新增字段,
                // 老构造点漏填 → 测试编译失败,回归补齐)。
                reasoning_content: None,
            })
        }

        async fn stream(
            &self,
            _request: df_ai_core::provider::CompletionRequest,
        ) -> anyhow::Result<df_ai_core::provider::StreamResult> {
            anyhow::bail!("MockProvider 不支持 stream（adversarial 路径不调用）")
        }

        fn name(&self) -> &str {
            "mock-model"
        }
    }

    /// LLM mock 返回结构良好的 JSON → 解析正确，evaluated_by=Llm
    #[tokio::test]
    async fn a8_llm_mock_parse_success() {
        let idea = make_idea(
            "AI 增长引擎",
            "面向用户的核心功能，带来显著增长。集成成熟方案。",
            Priority::Critical,
            vec!["增长"],
        );
        // 一份对齐 schema 的合法 LLM 返回（含 ```json 围栏，验证容错）
        let llm_text = r#"```json
{
  "positive": {
    "thesis": "该想法价值高，路径清晰，应立即推进",
    "evidence": ["用户增长是当前关键路径", "已有成熟方案可复用"],
    "reasoning": ["可行性高", "ROI 明显"],
    "confidence": 0.9
  },
  "negative": {
    "thesis": "资源投入与替代方案需权衡",
    "evidence": ["机会成本存在"],
    "reasoning": ["需验证更优解"],
    "confidence": 0.35
  },
  "analyst": {
    "summary": "综合双方，价值明确且路径清晰，建议立即推进",
    "strengths": ["业务价值明确", "技术路径清晰"],
    "weaknesses": ["资源需求待评估"],
    "risks": ["需求范围蔓延"],
    "opportunities": ["可形成可复用能力"],
    "final_assessment": "StrongGo"
  },
  "final_score": 8.5,
  "recommendation": "ImmediateAction"
}
```"#;
        let provider = Arc::new(MockProvider { text: llm_text.to_string() });
        let engine = AdversarialEngine::new(provider);
        let eval = engine.evaluate(&idea).await.unwrap();

        println!("\n[a8] LLM mock 成功路径");
        println!("  evaluated_by = {:?}", eval.evaluated_by);
        println!("  final_score = {:.2}  recommendation = {:?}", eval.final_score, eval.recommendation);
        println!("  positive.confidence = {:.2}  negative.confidence = {:.2}",
            eval.positive.confidence, eval.negative.confidence);
        println!("  analyst.final_assessment = {:?}", eval.analyst.final_assessment);

        assert_eq!(eval.evaluated_by, EvaluatedBy::Llm, "成功路径应为 Llm");
        assert!((eval.final_score - 8.5).abs() < 1e-9);
        assert_eq!(eval.recommendation, Recommendation::ImmediateAction);
        assert_eq!(eval.analyst.final_assessment, AssessmentLevel::StrongGo);
        assert!((eval.positive.confidence - 0.9).abs() < 1e-9);
        assert!((eval.negative.confidence - 0.35).abs() < 1e-9);
        assert_eq!(eval.positive.evidence.len(), 2);
        assert!(eval.positive.thesis.contains("立即推进"));
    }

    /// LLM mock 返回非法 JSON → bail → 降级 HeuristicFallback（evaluated_by 标记）
    #[tokio::test]
    async fn a9_llm_mock_bad_json_fallback() {
        let idea = make_idea("坏想法", "测试降级", Priority::Medium, vec![]);
        let provider = Arc::new(MockProvider { text: "这不是 JSON".to_string() });
        let engine = AdversarialEngine::new(provider);
        let eval = engine.evaluate(&idea).await.unwrap();

        println!("\n[a9] LLM mock 非 JSON → 降级");
        println!("  evaluated_by = {:?} (期望 HeuristicFallback)", eval.evaluated_by);
        assert_eq!(eval.evaluated_by, EvaluatedBy::HeuristicFallback, "非 JSON 应降级");
    }

    /// LLM mock 返回枚举非法值 → bail → 降级
    #[tokio::test]
    async fn a10_llm_mock_bad_enum_fallback() {
        let idea = make_idea("枚举非法", "测试枚举映射", Priority::High, vec![]);
        let llm_text = r#"{
  "positive": {"thesis":"x","evidence":[],"reasoning":[],"confidence":0.5},
  "negative": {"thesis":"y","evidence":[],"reasoning":[],"confidence":0.5},
  "analyst": {"summary":"s","strengths":[],"weaknesses":[],"risks":[],"opportunities":[],"final_assessment":"GoNow"},
  "final_score": 5.0,
  "recommendation": "Soon"
}"#;
        let provider = Arc::new(MockProvider { text: llm_text.to_string() });
        let engine = AdversarialEngine::new(provider);
        let eval = engine.evaluate(&idea).await.unwrap();

        println!("\n[a10] LLM mock 枚举非法 → 降级");
        println!("  evaluated_by = {:?} (期望 HeuristicFallback)", eval.evaluated_by);
        assert_eq!(eval.evaluated_by, EvaluatedBy::HeuristicFallback);
    }

    /// 数值越界 → clamp（confidence>1 / final_score>10）应被截断，不触发降级
    #[tokio::test]
    async fn a11_llm_mock_value_clamp() {
        let idea = make_idea("越界测试", "数值超出区间", Priority::Medium, vec![]);
        let llm_text = r#"{
  "positive": {"thesis":"p","evidence":[],"reasoning":[],"confidence":1.5},
  "negative": {"thesis":"n","evidence":[],"reasoning":[],"confidence":-0.3},
  "analyst": {"summary":"s","strengths":[],"weaknesses":[],"risks":[],"opportunities":[],"final_assessment":"Recommended"},
  "final_score": 99.0,
  "recommendation": "Soon"
}"#;
        let provider = Arc::new(MockProvider { text: llm_text.to_string() });
        let engine = AdversarialEngine::new(provider);
        let eval = engine.evaluate(&idea).await.unwrap();

        println!("\n[a11] LLM mock 数值越界 clamp");
        println!("  positive.confidence={:.2} (原 1.5 → 期望 1.0)", eval.positive.confidence);
        println!("  negative.confidence={:.2} (原 -0.3 → 期望 0.0)", eval.negative.confidence);
        println!("  final_score={:.2} (原 99 → 期望 10.0)", eval.final_score);

        assert_eq!(eval.evaluated_by, EvaluatedBy::Llm, "仅越界不降级");
        assert!((eval.positive.confidence - 1.0).abs() < 1e-9, "confidence>1 应 clamp 到 1.0");
        assert!((eval.negative.confidence - 0.0).abs() < 1e-9, "confidence<0 应 clamp 到 0.0");
        assert!((eval.final_score - 10.0).abs() < 1e-9, "final_score>10 应 clamp 到 10.0");
    }

    /// LLM 返回「前缀文字 + ```json + {...} + ```」（围栏不在开头）→ extract_json
    /// 正则兜底提取首个 JSON 对象 → 解析成功不降级（CR-40-2 兜底路径）。
    #[tokio::test]
    async fn a12_llm_mock_fenced_not_at_start() {
        let idea = make_idea("围栏不在开头", "测试正则兜底", Priority::High, vec![]);
        let llm_text = "好的，以下是我的评估：\n```json\n{\n  \"positive\": {\"thesis\":\"p\",\"evidence\":[],\"reasoning\":[],\"confidence\":0.7},\n  \"negative\": {\"thesis\":\"n\",\"evidence\":[],\"reasoning\":[],\"confidence\":0.4},\n  \"analyst\": {\"summary\":\"s\",\"strengths\":[],\"weaknesses\":[],\"risks\":[],\"opportunities\":[],\"final_assessment\":\"Recommended\"},\n  \"final_score\": 7.0,\n  \"recommendation\": \"Soon\"\n}\n```\n希望对你有帮助。";
        let provider = Arc::new(MockProvider { text: llm_text.to_string() });
        let engine = AdversarialEngine::new(provider);
        let eval = engine.evaluate(&idea).await.unwrap();

        println!("\n[a12] 围栏不在开头 → 正则兜底提取");
        println!("  evaluated_by = {:?} (期望 Llm，不应降级)", eval.evaluated_by);
        assert_eq!(eval.evaluated_by, EvaluatedBy::Llm, "围栏不在开头应正则兜底解析成功");
        assert!((eval.final_score - 7.0).abs() < 1e-9);
        assert_eq!(eval.recommendation, Recommendation::Soon);
    }
}
