//! 灵感相关命令

use std::sync::Arc;

use serde::Deserialize;
use tauri::State;

use df_ai::provider::LlmProvider;
use df_types::types::{new_id, IdeaStatus, Priority, ProjectStatus};
use df_ideas::capture::Idea;
use df_storage::crud::{is_unique_constraint_err, IdeaQuery};
use df_storage::models::{IdeaEvaluationRecord, IdeaRecord, ProjectEventRecord, ProjectRecord};

use crate::state::AppState;

use super::{err_str, now_millis};

/// 创建灵感入参
#[derive(Debug, Deserialize)]
pub struct CreateIdeaInput {
    pub title: String,
    #[serde(default)]
    pub description: String,
    #[serde(default = "default_priority")]
    pub priority: i32,
    /// 标签 JSON 数组字符串
    pub tags: Option<String>,
    pub source: Option<String>,
}

fn default_priority() -> i32 {
    1
}

/// 列出灵感。
///
/// **双路径向后兼容**(F-260621-02):
/// - 旧调用方仅传 `status`(`ideaApi.list(status)`)→ 转 IdeaQuery 仅带 status,走
///   `list_by_query`(白名单 status 列 WHERE),与原 `query("status", s)` 等价。
/// - 新调用方传 `query`(`ideaApi.list(query)`)→ 多条件(status/keyword/order_by/limit/offset)。
/// - 两者都不传 → 等价全量(`list_by_query` 空 query 走默认 created_at DESC,与 list_all 等价)。
///
/// `query` 优先于 `status`(二者同传时以 query 为准,避免重复过滤语义冲突)。
#[tauri::command]
pub async fn list_ideas(
    state: State<'_, AppState>,
    status: Option<String>,
    query: Option<IdeaQuery>,
) -> Result<Vec<IdeaRecord>, String> {
    let q = match query {
        Some(q) => q,
        None => IdeaQuery {
            status,
            ..Default::default()
        },
    };
    state.ideas.list_by_query(&q).await.map_err(err_str)
}

/// 列出指定灵感的评估历史(version DESC,最新版本在前)。
/// IdeaEvaluationRecord 已 Serialize,直接返回前端供历史面板渲染。
#[tauri::command]
pub async fn list_idea_evaluations(
    state: State<'_, AppState>,
    idea_id: String,
) -> Result<Vec<IdeaEvaluationRecord>, String> {
    state.idea_evaluations.list_by_idea(&idea_id).await.map_err(err_str)
}

/// 创建灵感，返回完整记录
#[tauri::command]
pub async fn create_idea(
    state: State<'_, AppState>,
    input: CreateIdeaInput,
) -> Result<IdeaRecord, String> {
    // IDEA-FIX-03: priority 值域校验 ∈ 0..=3 (对标 tasks B-260615-15)。
    // priority_from_i32 对越界值兜底归并(>=3→Critical),但 IPC 入口应显式拒非法值,
    // 防 LLM/前端传 99 等被静默吞为 Critical。
    if !(0..=3).contains(&input.priority) {
        return Err(format!(
            "priority 必须在 0..=3 (0=critical/1=high/2=medium/3=low,对齐前端约定 api/types.ts:141),收到 {}",
            input.priority
        ));
    }
    let now = now_millis();
    let record = IdeaRecord {
        id: new_id(),
        title: input.title,
        description: input.description,
        status: IdeaStatus::Draft,
        priority: input.priority,
        score: None,
        tags: input.tags,
        source: input.source,
        promoted_to: None,
        ai_analysis: None,
        scores: None,
        related_ids: None,
        created_at: now.clone(),
        updated_at: now,
    };
    state
        .ideas
        .insert(record.clone())
        .await
        .map_err(err_str)?;
    // 知识图谱 Phase 2(对标设计 §2.4):idea_created 事件**暂不埋点**。
    // 原因:project_events.project_id 是 NOT NULL + FK(PRAGMA foreign_keys=ON),
    // 而 idea 无 project_id(立项前不属于任何项目)。设计 §2.5 计划用系统初始化创建的
    // Inbox 项目作为无主 idea 的归属,但 Inbox 项目尚未实现(独立任务)。
    // 写 NULL 会违反 NOT NULL,写不存在的 project_id 会违反 FK——两者都会让 best-effort
    // 退化成「写失败 warn」,无实际价值且噪音。Inbox 项目落地后此处补 idea_created 埋点。
    Ok(record)
}

/// 更新灵感单个字段（字段名走 df-storage 白名单校验）
#[tauri::command]
pub async fn update_idea(
    state: State<'_, AppState>,
    id: String,
    field: String,
    value: String,
) -> Result<bool, String> {
    state
        .ideas
        .update_field(&id, &field, &value)
        .await
        .map_err(err_str)
}

/// 删除灵感(软删 → 回收站,可恢复)。对标 delete_task(SET deleted_at=now)。
#[tauri::command]
pub async fn delete_idea(state: State<'_, AppState>, id: String) -> Result<bool, String> {
    state.ideas.soft_delete(&id).await.map_err(err_str)
}

/// 恢复灵感(从回收站还原,清 deleted_at)。对标 restore_task / restore_project。
#[tauri::command]
pub async fn restore_idea(state: State<'_, AppState>, id: String) -> Result<bool, String> {
    state.ideas.restore(&id).await.map_err(err_str)
}

/// 双向同步关联关系:原子地设置主体灵感的关联目标列表,并自动添加/移除反向关联。
/// 主体灵感 + 所有受影响的关联目标在同一 SQLite 事务中更新,保证原子性。
#[tauri::command]
pub async fn relate_ideas(
    state: State<'_, AppState>,
    subject_id: String,
    target_ids: Vec<String>,
) -> Result<(), String> {
    state
        .ideas
        .sync_related_ids(&subject_id, &target_ids)
        .await
        .map_err(err_str)
}

/// 列出回收站灵感(deleted_at IS NOT NULL,按更新时间降序)。对标 list_deleted_projects。
#[tauri::command]
pub async fn list_deleted_ideas(state: State<'_, AppState>) -> Result<Vec<IdeaRecord>, String> {
    state.ideas.list_deleted().await.map_err(err_str)
}

/// 将灵感晋升为项目 — 复用 df-project 领域逻辑创建项目，回写灵感 status=promoted/promoted_to
#[tauri::command]
pub async fn promote_idea(
    state: State<'_, AppState>,
    id: String,
) -> Result<df_ideas::promotion::PromotionResult, String> {
    let record = state
        .ideas
        .get_by_id(&id)
        .await
        .map_err(err_str)?
        .ok_or_else(|| format!("灵感不存在: {id}"))?;

    if let Some(promoted_to) = &record.promoted_to {
        return Err(format!("灵感已立项: {}", promoted_to));
    }

    // 复用 df-project 领域逻辑构造项目实体（create_from_idea）。
    // create_from_idea 返回 Result(任务 #16: 名称空校验下沉领域层)。
    let project = df_project::manager::ProjectManager::create_from_idea(
        record.title.clone(),
        record.description.clone(),
        id.clone(),
    ).map_err(|e| e.to_string())?;
    let project_id = project.id.clone();
    let now = now_millis();
    let project_record = ProjectRecord {
        id: project_id.clone(),
        name: project.name,
        description: project.description,
        status: ProjectStatus::Planning,
        idea_id: Some(id.clone()),
        path: None,
        stack: None,
        created_at: now.clone(),
        updated_at: now.clone(),
    };
    state
        .projects
        .insert(project_record)
        .await
        .map_err(err_str)?;

    // 回写灵感：status=promoted + promoted_to（update_full 单事务覆盖可变字段）
    // 补偿删除:第二步失败时回滚第一步已建的 project,保证最终一致性(非原子,但防项目存留而
    // 灵感状态未变的数据不一致)。Repository 方法各自持锁不支持跨 repo 共享事务对象,故选补偿
    // 删除而非真事务(改动最小,工程投入产出比最高)。
    let updated = IdeaRecord {
        status: IdeaStatus::Promoted,
        promoted_to: Some(project_id.clone()),
        updated_at: now,
        ..record
    };
    if let Err(e) = state.ideas.update_full(&updated).await {
        // 回写失败:补偿软删已建项目(进回收站,可恢复),避免悬空项目(idea.promoted_to 仍空,可重试立项)
        tracing::error!("灵感 {id} 回写失败,补偿删除已建项目 {project_id}: {e}");
        if let Err(del_err) = state.projects.soft_delete(&project_id).await {
            tracing::error!("补偿软删项目 {project_id} 也失败(需人工清理): {del_err}");
        }
        return Err(format!("灵感立项回写失败(已回滚项目创建): {}", e));
    }

    // 知识图谱 Phase 2(对标设计 §2.4 hook/after):idea_promoted 事件。best-effort 不阻断。
    // 灵感此时已立项为新项目(project_id 存在,FK 满足),事件挂在 project_id 下,
    // entity 指向 idea,from_state=draft→to_state=promoted。
    let event = ProjectEventRecord {
        id: new_id(),
        project_id: project_id.clone(),
        event_type: "idea_promoted".to_string(),
        entity_type: Some("idea".to_string()),
        entity_id: Some(id.clone()),
        from_state: Some("draft".to_string()),
        to_state: Some("promoted".to_string()),
        context_json: None,
        source: Some("human".to_string()),
        conversation_id: None,
        created_at: now_millis(),
    };
    if let Err(e) = state.project_events.insert(event).await {
        tracing::warn!(
            idea_id = %id,
            project_id = %project_id,
            error = %e,
            "[事件流] idea_promoted 埋点写入失败(不阻断立项)"
        );
    }

    Ok(df_ideas::promotion::PromotionResult {
        idea_id: id,
        project_id: project_id,
        promoted: true,
        reason: "手动立项".to_string(),
    })
}

// ============================================================
// 灵感评估 — 多维评分 + 对抗式评估
// ============================================================

/// 评估灵感：多维评分 + 对抗式评估，结果写回 scores/score/ai_analysis，状态置 pending_review，返回更新后的记录
#[tauri::command]
pub async fn evaluate_idea(
    state: State<'_, AppState>,
    id: String,
) -> Result<IdeaRecord, String> {
    // 取出灵感
    let record = state
        .ideas
        .get_by_id(&id)
        .await
        .map_err(err_str)?
        .ok_or_else(|| format!("灵感不存在: {id}"))?;

    let idea = record_to_idea(&record);

    // 多维评分（0-10，IPC 层 *10 缩放为 0-100）
    let scores = df_ideas::scoring::ScoringEngine::compute_default(&idea);

    // 对抗式评估（构造注入：从 DB 读默认 provider 装配 LLM，无 provider/构造失败 → 启发式兜底）
    // F-01 阶段5: 透传 model_configs 池,evaluate_with_llm 经路由选模型(池空兜底 default_model)。
    let provider = build_default_provider(&state).await;
    let engine = match provider {
        Some((p, pool)) => {
            df_ideas::adversarial::AdversarialEngine::with_pool(Arc::from(p), pool)
        }
        None => df_ideas::adversarial::AdversarialEngine::heuristic(),
    };
    let eval = engine.evaluate(&idea).await.map_err(err_str)?;

    // 组装前端扁平结构（与 Ideas.vue 的 AdversarialEval interface 对齐）
    let positive_strength = eval.positive.confidence;
    let negative_strength = eval.negative.confidence;
    let net_sentiment = positive_strength - negative_strength;
    let recommendation = recommendation_str(&eval.recommendation).to_string();
    let final_score = eval.final_score;
    let analyst_summary = eval.analyst.summary.clone();
    let action_items = action_items_for(&eval.recommendation);
    let positive = serde_json::json!({
        "thesis": eval.positive.thesis,
        "evidence": eval.positive.evidence,
    });
    let negative = serde_json::json!({
        "thesis": eval.negative.thesis,
        "evidence": eval.negative.evidence,
    });

    let ai_analysis = serde_json::json!({
        "positive_strength": positive_strength,
        "negative_strength": negative_strength,
        "net_sentiment": net_sentiment,
        "recommendation": recommendation,
        "evaluated_by": eval.evaluated_by,
        "final_score": final_score,
        "summary": analyst_summary,
        "action_items": action_items,
        "positive": positive,
        "negative": negative,
        "analyst": { "summary": analyst_summary },
    })
    .to_string();

    // scores JSON：中文维度 key + 0-100 值（前端雷达图直接当百分比用）
    let scores_json = serde_json::json!({
        "可行性": (scores.feasibility * 10.0).round() as i64,
        "影响力": (scores.impact * 10.0).round() as i64,
        "紧急度": (scores.urgency * 10.0).round() as i64,
        "综合": (scores.overall * 10.0).round() as i64,
    })
    .to_string();

    let score_value = (scores.overall * 10.0).round() as i64;

    // 构造完整记录后单次原子写回（update_full 保留 id 与 created_at）。
    // ai_analysis/scores_json 按值 move 进主表记录后,下方历史快照仍需复用 → 此处 clone 保留绑定。
    let updated = IdeaRecord {
        scores: Some(scores_json.clone()),
        ai_analysis: Some(ai_analysis.clone()),
        score: Some(score_value as f64),
        status: IdeaStatus::PendingReview,
        updated_at: now_millis(),
        ..record
    };
    state
        .ideas
        .update_full(&updated)
        .await
        .map_err(err_str)?;

    // 追加一条评估历史快照(idea_evaluations 审计表,version 单调递增)。
    // 主表 update_full 成功后再追加,保证主表先落;历史表为额外冗余列(evaluated_by
    // 独立冗余,ai_analysis JSON 内的 evaluated_by 字段保留不删)。
    //
    // version 并发重复兜底(V25 唯一约束 + 重试):version 此前由
    // `list_by_idea().first().version + 1` 算出,读-改-写非原子,并发评估同一灵感
    // 可能写出相同 version。V25 在 idea_evaluations(idea_id, version) 上加了唯一索引,
    // 此处捕获唯一约束冲突 → 重新查最新 version 重算并重试(上限 3 次防死循环)。
    // 单用户桌面应用并发概率极低,但唯一约束 + 重试是数据完整性兜底,值得做。
    let mut attempt = 0;
    let max_attempts = 3;
    loop {
        attempt += 1;
        let version = state
            .idea_evaluations
            .list_by_idea(&id)
            .await
            .map_err(err_str)?
            .first()
            .map(|r| r.version + 1)
            .unwrap_or(1);
        let eval_record = IdeaEvaluationRecord {
            id: new_id(),
            idea_id: id.clone(),
            version,
            ai_analysis: Some(ai_analysis.clone()),
            scores: Some(scores_json.clone()),
            score: Some(score_value as f64),
            evaluated_by: Some(evaluated_by_str(&eval.evaluated_by).to_string()),
            evaluated_at: now_millis(),
        };
        match state.idea_evaluations.insert(eval_record).await {
            Ok(_) => break,
            Err(e) => {
                // 唯一约束冲突(SQLite extended code 2067 / SQLITE_CONSTRAINT_UNIQUE)
                // → version 并发重复,命中且未达上限则重试(重新查 version);否则向上抛错。
                // 检测逻辑收口到 df_storage::crud::is_unique_constraint_err,集中维护、
                // 大小写不敏感,不再散落脆弱的英文文案 contains。
                if is_unique_constraint_err(&e) && attempt < max_attempts {
                    tracing::warn!(
                        "灵感 {id} 评估历史 version 唯一约束冲突,重试 {}/{}",
                        attempt,
                        max_attempts
                    );
                    continue;
                }
                return Err(e.to_string());
            }
        }
    }

    Ok(updated)
}

/// 从 DB 读取默认 provider 配置（is_default 优先，否则首个）+ build_provider 构造实例。
///
/// 返回 `None` 的两种情况（统一走启发式评估兜底）：
/// - DB 未配置任何 provider（`list_all` 空或全无 is_default 且无首条）
/// - provider 密钥不可用（keyring 无记录 / 纯空白），`build_provider_for` 返 Err
///
/// 复用 `commands::ai::secret::build_provider_for`（resolve→ensure→build 三步），
/// 与 AI Chat / 项目扫描的 provider 构造路径统一（FR-S1 密钥解析一致）。
///
/// 返回 (provider, model_pool):model_pool = 选中 provider 的 model_configs(F-01 阶段5,
/// 供对抗评估路由)。池空(用户未拉取)→ 调用方兜底 default_model。
async fn build_default_provider(
    state: &State<'_, AppState>,
) -> Option<(Box<dyn LlmProvider>, Vec<df_ai::df_ai_core::model::ModelConfig>)> {
    let providers = state.ai_providers.list_all().await.ok()?;
    let pc = providers
        .iter()
        .find(|p| p.is_default)
        .cloned()
        .or_else(|| providers.into_iter().next())?;
    match crate::commands::ai::secret::build_provider_for(&pc) {
        Ok(p) => Some((p, pc.model_configs.clone())),
        Err(e) => {
            // 密钥不可用:启发式兜底,不阻断评估（与 evaluate_idea LLM 失败降级语义一致）
            tracing::warn!("默认 provider 密钥不可用,对抗评估走启发式: {e}");
            None
        }
    }
}

/// IdeaRecord → df_ideas::Idea（评估用，status/time 不影响评分）
fn record_to_idea(record: &IdeaRecord) -> Idea {
    let tags: Vec<String> = match record.tags.as_deref() {
        Some(t) => match serde_json::from_str::<Vec<String>>(t) {
            Ok(v) => v,
            Err(e) => {
                tracing::warn!(error = %e, idea_id = %record.id, "[ideas] tags JSON 解析失败,降级空 tags 继续评估");
                Vec::new()
            }
        },
        None => Vec::new(),
    };
    // IDEA-FIX-05: 读真实 related_ids(原硬编码 Vec::new() 丢关联上下文)。解析同 tags 模式。
    let related_ids: Vec<String> = match record.related_ids.as_deref() {
        Some(r) => match serde_json::from_str::<Vec<String>>(r) {
            Ok(v) => v,
            Err(e) => {
                tracing::warn!(error = %e, idea_id = %record.id, "[ideas] related_ids JSON 解析失败,降级空");
                Vec::new()
            }
        },
        None => Vec::new(),
    };
    Idea {
        id: record.id.clone(),
        title: record.title.clone(),
        description: record.description.clone(),
        // IDEA-FIX-05: 读真实 status(原硬编码 Draft 丢真实状态,评估上下文完整性)
        status: status_from_str(record.status.as_str()),
        priority: priority_from_i32(record.priority),
        scores: None,
        tags,
        source: record.source.clone(),
        related_ids,
        created_at: chrono::Utc::now(),
        updated_at: chrono::Utc::now(),
    }
}

/// i32 优先级 → Priority 枚举(对齐**前端约定** 0=critical/1=high/2=medium/3=low,
/// 见 api/types.ts:141 + Tasks.vue option + constants/project.ts PRIORITY_CLASS)。
/// 注:df-types Priority 枚举 discriminant 是 Low=0/Critical=3(历史定义,与前端相反),
/// 故本函数手动映射对齐前端语义,不靠 discriminant。Priority as_str 序列化("low"/"critical")
/// 不受影响。verify agent 发现的预存跨层 bug(父⑤⑤.2 收尾揪出):原 0=>Low 致 Ideas
/// 表单选 Critical(P0) 存 Low,标签与实际存储/评分相反。
fn priority_from_i32(p: i32) -> Priority {
    match p {
        0 => Priority::Critical,
        1 => Priority::High,
        2 => Priority::Medium,
        _ => Priority::Low,
    }
}

/// IDEA-FIX-05: idea status 字符串(DB snake_case)→ IdeaStatus 枚举。
/// record_to_idea 读真实 status 用(原硬编码 Draft 丢真实状态)。未知值 fallback Draft(同原行为)。
fn status_from_str(s: &str) -> df_types::types::IdeaStatus {
    use df_types::types::IdeaStatus;
    match s {
        "draft" => IdeaStatus::Draft,
        "pending_review" => IdeaStatus::PendingReview,
        "approved" => IdeaStatus::Approved,
        "rejected" => IdeaStatus::Rejected,
        "promoted" => IdeaStatus::Promoted,
        "archived" => IdeaStatus::Archived,
        _ => IdeaStatus::Draft,
    }
}

/// Recommendation → 前端 assessmentLabel 期望的全小写空格分隔（匹配 map key）
fn recommendation_str(r: &df_ideas::adversarial::Recommendation) -> &'static str {
    use df_ideas::adversarial::Recommendation::*;
    match r {
        ImmediateAction => "immediate action",
        Soon => "soon",
        WithResources => "with resources",
        ResearchMore => "research more",
        Monitor => "monitor",
    }
}

/// 行动建议 — 按推荐等级返回
fn action_items_for(r: &df_ideas::adversarial::Recommendation) -> Vec<String> {
    use df_ideas::adversarial::Recommendation::*;
    match r {
        ImmediateAction => vec!["立即组建项目团队".into(), "制定详细执行计划".into(), "分配必要资源".into()],
        Soon => vec!["下周启动项目".into(), "准备资源需求".into(), "制定时间表".into()],
        WithResources => vec!["确认资源预算".into(), "评估 ROI".into(), "制定风险预案".into()],
        ResearchMore => vec!["进行市场调研".into(), "收集用户反馈".into(), "验证技术可行性".into()],
        Monitor => vec!["持续跟踪相关指标".into(), "定期评估进展".into(), "等待更好时机".into()],
    }
}

/// EvaluatedBy 枚举 → 评估历史表 evaluated_by 列的字符串冗余值。
/// (ai_analysis JSON 内的 evaluated_by 字段保留不删;此处为历史表独立冗余列,
/// 便于不解析 JSON 即可直接按评估来源过滤/统计历史。)
fn evaluated_by_str(e: &df_ideas::adversarial::EvaluatedBy) -> &'static str {
    use df_ideas::adversarial::EvaluatedBy::*;
    match e {
        Llm => "Llm",
        Heuristic => "Heuristic",
        HeuristicFallback => "HeuristicFallback",
    }
}
