use spool::desktop::tauri::{
    TauriContextCommand, TauriDaemonCommand, TauriImportSessionCommand, TauriMemoryActionCommand,
    TauriMemoryDraftCommand, TauriPromptOptimizeCommand, TauriRecordCommand,
    TauriSessionBrowserCommand, TauriWakeupCommand, TauriWorkbenchCommand,
    desktop_apply_memory_action, desktop_browse_sessions, desktop_build_wakeup,
    desktop_get_history, desktop_get_record, desktop_import_session, desktop_load_workbench,
    desktop_optimize_prompt, desktop_propose_memory, desktop_read_wiki_index, desktop_run_context,
    desktop_wiki_lint,
};
use spool::desktop::{
    DesktopErrorKind, DesktopLifecycleActionDto, DesktopMetadataDto, DesktopWikiIndexRequest,
    DesktopWikiLintRequest,
};
use spool::domain::{MemoryLifecycleState, MemoryScope, OutputFormat, TargetTool, WakeupProfile};
use std::fs;
use tempfile::tempdir;

fn setup_workspace() -> (tempfile::TempDir, std::path::PathBuf, std::path::PathBuf) {
    let temp = tempdir().unwrap();
    let vault_root = temp.path().join("vault");
    let repo_root = temp.path().join("repo");
    fs::create_dir_all(vault_root.join("10-Projects")).unwrap();
    fs::create_dir_all(&repo_root).unwrap();
    fs::write(
        vault_root.join("10-Projects/spool.md"),
        "---\ntitle: spool\nmemory_type: project\n---\n\nDesktop tauri command integration note.\n",
    )
    .unwrap();
    let config_path = temp.path().join("spool.toml");
    fs::write(
        &config_path,
        format!(
            "[vault]\nroot = \"{}\"\n\n[output]\ndefault_format = \"markdown\"\nmax_chars = 4000\nmax_notes = 8\n\n[[projects]]\nid = \"spool\"\nname = \"spool\"\nrepo_paths = [\"{}\"]\nnote_roots = [\"10-Projects\"]\n",
            vault_root.display(),
            repo_root.display(),
        ),
    )
    .unwrap();
    (temp, config_path, repo_root)
}

#[test]
fn tauri_smoke_should_run_context_and_wakeup_commands() {
    let (_temp, config_path, repo_root) = setup_workspace();

    let context = desktop_run_context(TauriContextCommand {
        config_path: config_path.clone(),
        vault_root_override: None,
        cwd: repo_root.clone(),
        task: "summarize current project context".to_string(),
        files: vec!["src/lib.rs".to_string(), "src/main.rs".to_string()],
        target: TargetTool::Claude,
        format: OutputFormat::Markdown,
    })
    .unwrap();
    assert_eq!(context.bundle.route.debug.note_count, 1);

    let wakeup = desktop_build_wakeup(TauriWakeupCommand {
        config_path,
        vault_root_override: None,
        cwd: repo_root,
        task: "prepare restart packet".to_string(),
        files: Vec::new(),
        target: TargetTool::Claude,
        profile: WakeupProfile::Project,
    })
    .unwrap();
    assert_eq!(wakeup.packet.profile, WakeupProfile::Project);
}

#[test]
fn tauri_smoke_should_optimize_prompt_through_shared_orchestrator() {
    let (_temp, config_path, repo_root) = setup_workspace();

    let response = desktop_optimize_prompt(TauriPromptOptimizeCommand {
        config_path,
        vault_root_override: None,
        cwd: repo_root,
        task: "continue desktop integration".to_string(),
        files: vec![
            "src/desktop.rs".to_string(),
            "frontend/desktop.js".to_string(),
        ],
        target: TargetTool::Codex,
        profile: WakeupProfile::Project,
        provider: Some("codex".to_string()),
        session_id: Some("codex:session-1".to_string()),
    })
    .unwrap();

    assert!(response.combined_prompt.contains("Codex"));
    assert!(response.context_prompt.contains("Codex"));
    assert_eq!(response.profile, WakeupProfile::Project);
    assert_eq!(response.provider.as_deref(), Some("codex"));
    assert!(response.runtime_trace.is_none());
}

#[test]
fn tauri_smoke_should_cover_lifecycle_read_and_write_commands() {
    let (_temp, config_path, _repo_root) = setup_workspace();

    let created = desktop_propose_memory(TauriMemoryDraftCommand {
        config_path: config_path.clone(),
        title: "测试偏好".to_string(),
        summary: "先 smoke 再收口".to_string(),
        memory_type: "workflow".to_string(),
        scope: MemoryScope::User,
        source_ref: "session:1".to_string(),
        project_id: None,
        user_id: Some("long".to_string()),
        sensitivity: None,
        metadata: DesktopMetadataDto {
            actor: Some("desktop".to_string()),
            reason: Some("captured from tauri command".to_string()),
            evidence_refs: vec!["session:1".to_string(), "obsidian://workflow".to_string()],
        },
        entities: Vec::new(),
        tags: Vec::new(),
        triggers: Vec::new(),
        related_files: Vec::new(),
        related_records: Vec::new(),
        supersedes: None,
        applies_to: Vec::new(),
        valid_until: None,
    })
    .unwrap();
    assert_eq!(created.entry.record.state, MemoryLifecycleState::Candidate);

    let workbench = desktop_load_workbench(TauriWorkbenchCommand {
        config_path: config_path.clone(),
        daemon: Some(TauriDaemonCommand {
            enabled: false,
            daemon_bin: None,
        }),
    })
    .unwrap();
    assert_eq!(workbench.snapshot.pending_review.len(), 1);

    let record = desktop_get_record(TauriRecordCommand {
        config_path: config_path.clone(),
        record_id: created.entry.record_id.clone(),
        daemon: None,
    })
    .unwrap()
    .unwrap();
    assert_eq!(record.record.record.title, "测试偏好");

    let action = desktop_apply_memory_action(TauriMemoryActionCommand {
        config_path: config_path.clone(),
        record_id: created.entry.record_id.clone(),
        action: DesktopLifecycleActionDto::Accept,
        metadata: DesktopMetadataDto::default(),
    })
    .unwrap();
    assert_eq!(action.entry.record.state, MemoryLifecycleState::Accepted);

    let history = desktop_get_history(TauriRecordCommand {
        config_path,
        record_id: created.entry.record_id,
        daemon: None,
    })
    .unwrap();
    assert_eq!(history.history.len(), 2);
}

#[test]
fn tauri_smoke_should_browse_sessions_from_memory_refs() {
    let (_temp, config_path, _repo_root) = setup_workspace();

    let _created = desktop_propose_memory(TauriMemoryDraftCommand {
        config_path: config_path.clone(),
        title: "会话偏好".to_string(),
        summary: "来自一次桌面会话".to_string(),
        memory_type: "workflow".to_string(),
        scope: MemoryScope::User,
        source_ref: "session:demo-1".to_string(),
        project_id: None,
        user_id: Some("long".to_string()),
        sensitivity: None,
        metadata: DesktopMetadataDto {
            actor: Some("desktop".to_string()),
            reason: Some("captured from session browser smoke".to_string()),
            evidence_refs: vec![
                "session:demo-1".to_string(),
                "obsidian://workflow".to_string(),
            ],
        },
        entities: Vec::new(),
        tags: Vec::new(),
        triggers: Vec::new(),
        related_files: Vec::new(),
        related_records: Vec::new(),
        supersedes: None,
        applies_to: Vec::new(),
        valid_until: None,
    })
    .unwrap();

    // Memory records should NOT appear in the session browser (they belong in lifecycle tab)
    let sessions = desktop_browse_sessions(TauriSessionBrowserCommand {
        config_path: config_path.clone(),
        page: None,
        per_page: None,
        provider: None,
    })
    .unwrap();
    let spool_items: Vec<_> = sessions
        .sessions
        .iter()
        .filter(|session| session.provider == "spool")
        .collect();
    assert_eq!(spool_items.len(), 0);
}

#[test]
fn tauri_smoke_should_paginate_session_browser_without_duplicates() {
    let (_temp, config_path, _repo_root) = setup_workspace();

    for index in 0..12 {
        desktop_propose_memory(TauriMemoryDraftCommand {
            config_path: config_path.clone(),
            title: format!("会话 {index}"),
            summary: format!("分页 smoke {index}"),
            memory_type: "workflow".to_string(),
            scope: MemoryScope::User,
            source_ref: format!("session:page-{index:02}"),
            project_id: None,
            user_id: Some("long".to_string()),
            sensitivity: None,
            metadata: DesktopMetadataDto {
                actor: Some("desktop".to_string()),
                reason: Some("pagination smoke".to_string()),
                evidence_refs: vec![format!("session:page-{index:02}")],
            },
            entities: Vec::new(),
            tags: Vec::new(),
            triggers: Vec::new(),
            related_files: Vec::new(),
            related_records: Vec::new(),
            supersedes: None,
            applies_to: Vec::new(),
            valid_until: None,
        })
        .unwrap();
    }

    // Memory records should NOT appear in session browser even with "spool" filter
    let page_one = desktop_browse_sessions(TauriSessionBrowserCommand {
        config_path: config_path.clone(),
        page: Some(1),
        per_page: Some(10),
        provider: Some("spool".to_string()),
    })
    .unwrap();
    assert_eq!(page_one.total, 0);
    assert_eq!(page_one.sessions.len(), 0);
    assert!(!page_one.has_more);

    // Without filter, also no spool items
    let all = desktop_browse_sessions(TauriSessionBrowserCommand {
        config_path,
        page: Some(1),
        per_page: Some(50),
        provider: None,
    })
    .unwrap();
    let spool_count = all
        .sessions
        .iter()
        .filter(|s| s.provider == "spool")
        .count();
    assert_eq!(spool_count, 0);
}

#[test]
fn tauri_smoke_should_validate_import_session_inputs() {
    let (_temp, config_path, _repo_root) = setup_workspace();

    let missing_session_id = desktop_import_session(TauriImportSessionCommand {
        config_path: config_path.clone(),
        provider: "claude".to_string(),
        session_id: "  ".to_string(),
        apply: false,
        actor: None,
    })
    .expect_err("blank session_id should return input error");
    assert!(matches!(missing_session_id.kind, DesktopErrorKind::Input));

    let bad_provider = desktop_import_session(TauriImportSessionCommand {
        config_path,
        provider: "opencode".to_string(),
        session_id: "abc".to_string(),
        apply: false,
        actor: None,
    })
    .expect_err("unknown provider should return input error");
    assert!(matches!(bad_provider.kind, DesktopErrorKind::Input));
}

#[test]
fn tauri_smoke_should_return_wiki_lint_clean_report_for_empty_ledger() {
    let (_temp, config_path, _repo_root) = setup_workspace();

    let response = desktop_wiki_lint(DesktopWikiLintRequest { config_path })
        .expect("wiki_lint should succeed on a fresh config");

    assert_eq!(response.report.total_active_records, 0);
    assert!(response.report.prune_suggestions.is_empty());
    assert!(response.report.broken_cross_refs.is_empty());
    assert!(response.report.orphan_notes.is_empty());
    assert!(response.markdown.contains("知识库干净"));
    assert!(!response.used_vault_root.as_os_str().is_empty());
}

#[test]
fn tauri_smoke_should_detect_broken_cross_refs_via_wiki_lint() {
    let (_temp, config_path, _repo_root) = setup_workspace();

    desktop_propose_memory(TauriMemoryDraftCommand {
        config_path: config_path.clone(),
        title: "依赖缺失".to_string(),
        summary: "引用了一个不存在的记录".to_string(),
        memory_type: "workflow".to_string(),
        scope: MemoryScope::User,
        source_ref: "session:wiki-lint".to_string(),
        project_id: None,
        user_id: Some("long".to_string()),
        sensitivity: None,
        metadata: DesktopMetadataDto {
            actor: Some("desktop".to_string()),
            reason: Some("broken cross-ref smoke".to_string()),
            evidence_refs: vec!["session:wiki-lint".to_string()],
        },
        entities: Vec::new(),
        tags: Vec::new(),
        triggers: Vec::new(),
        related_files: Vec::new(),
        related_records: vec!["missing-id".to_string()],
        supersedes: None,
        applies_to: Vec::new(),
        valid_until: None,
    })
    .unwrap();

    let created = desktop_propose_memory(TauriMemoryDraftCommand {
        config_path: config_path.clone(),
        title: "有断链的记忆".to_string(),
        summary: "accepted 之后应暴露 broken cross-ref".to_string(),
        memory_type: "workflow".to_string(),
        scope: MemoryScope::User,
        source_ref: "session:wiki-lint-accepted".to_string(),
        project_id: None,
        user_id: Some("long".to_string()),
        sensitivity: None,
        metadata: DesktopMetadataDto {
            actor: Some("desktop".to_string()),
            reason: Some("broken cross-ref accepted smoke".to_string()),
            evidence_refs: vec!["session:wiki-lint-accepted".to_string()],
        },
        entities: Vec::new(),
        tags: Vec::new(),
        triggers: Vec::new(),
        related_files: Vec::new(),
        related_records: vec!["missing-id".to_string()],
        supersedes: None,
        applies_to: Vec::new(),
        valid_until: None,
    })
    .unwrap();

    desktop_apply_memory_action(TauriMemoryActionCommand {
        config_path: config_path.clone(),
        record_id: created.entry.record_id.clone(),
        action: DesktopLifecycleActionDto::Accept,
        metadata: DesktopMetadataDto::default(),
    })
    .unwrap();

    let response = desktop_wiki_lint(DesktopWikiLintRequest { config_path })
        .expect("wiki_lint should succeed with broken cross-ref present");

    assert!(
        !response.report.broken_cross_refs.is_empty(),
        "expected at least one broken cross-ref, got {:?}",
        response.report.broken_cross_refs
    );
    assert!(
        response
            .report
            .broken_cross_refs
            .iter()
            .any(|broken| broken.missing_target == "missing-id"),
        "expected broken cross-ref to mention missing-id"
    );
    assert!(response.markdown.contains("断链"));
}

#[test]
fn tauri_smoke_should_return_none_markdown_when_index_missing() {
    let (_temp, config_path, _repo_root) = setup_workspace();

    let response = desktop_read_wiki_index(DesktopWikiIndexRequest {
        config_path,
        project_id: None,
    })
    .expect("read_wiki_index should succeed even when INDEX.md is missing");

    assert!(response.markdown.is_none());
    assert!(!response.used_vault_root.as_os_str().is_empty());
}
