// SPDX-License-Identifier: Apache-2.0
//! Repository verification proof surface.

use std::{path::Path, time::Instant};

use anyhow::{Result, anyhow};
use heddle_core::{
    RepositoryVerificationState, VerificationCheck, VerifyOptions, VerifyReport,
    verify as core_verify,
};
use repo::Repository;

use super::{
    RecoveryAdvice,
    action_line::print_next,
};
use crate::{
    cli::{Cli, should_output_json, style},
    config::UserConfig,
    perf::{ProfileField, ProfileMode, emit_profile, profile_enabled, profile_mode},
};

pub fn cmd_verify(cli: &Cli, verbose: bool) -> Result<()> {
    let body_start = Instant::now();
    let cwd = std::env::current_dir()?;
    let start = cli.repo.as_ref().unwrap_or(&cwd).to_path_buf();
    let ctx = verify_execution_context_from_cli(cli, &start)?;
    let output = core_verify(&ctx, VerifyOptions::new().with_start_path(start.clone()))?;
    if profile_enabled() {
        let fields = [
            ProfileField::millis("plain_git_probe_ms", output.profile.plain_git_probe_ms),
            ProfileField::millis("repo_open_ms", output.profile.repo_open_ms),
            ProfileField::millis("verification_ms", output.profile.verification_ms),
            ProfileField::duration("command_body_ms", body_start.elapsed()),
        ];
        match profile_mode() {
            ProfileMode::Off => {}
            ProfileMode::Human => emit_profile("verify phases", &fields),
            ProfileMode::Jsonl => {
                emit_profile(
                    "verify plain git probe",
                    &[ProfileField::millis(
                        "plain_git_probe_ms",
                        output.profile.plain_git_probe_ms,
                    )],
                );
                emit_profile(
                    "verify repo open",
                    &[ProfileField::millis(
                        "repo_open_ms",
                        output.profile.repo_open_ms,
                    )],
                );
                emit_profile(
                    "verify repository checks",
                    &[ProfileField::millis(
                        "verification_ms",
                        output.profile.verification_ms,
                    )],
                );
                emit_profile(
                    "verify command body",
                    &[ProfileField::duration(
                        "command_body_ms",
                        body_start.elapsed(),
                    )],
                );
            }
        }
    }
    let repo_config = output
        .trust
        .heddle_initialized
        .then(|| {
            Repository::open(&start)
                .ok()
                .map(|repo| repo.config().clone())
        })
        .flatten();
    let as_json = should_output_json(cli, repo_config.as_ref());
    if !output.clean && as_json {
        return Err(anyhow!(verify_failed_advice(&output.trust)));
    }
    render_verify(&output, verbose, as_json)?;
    if !output.clean {
        return Err(anyhow!(verify_failed_advice(&output.trust)));
    }
    Ok(())
}

fn verify_execution_context_from_cli(
    cli: &Cli,
    start: &Path,
) -> Result<heddle_core::ExecutionContext> {
    let config = UserConfig::load_default()?;
    let verbosity = if cli.quiet {
        heddle_core::Verbosity::Quiet
    } else if cli.verbose > 0 {
        heddle_core::Verbosity::Verbose
    } else {
        heddle_core::Verbosity::Normal
    };
    let mut builder = heddle_core::ExecutionContext::builder()
        .start_path(start.to_path_buf())
        .config(config)
        .verbosity(verbosity)
        .progress(std::sync::Arc::new(heddle_core::NoopProgress))
        .warnings(std::sync::Arc::new(heddle_core::NoopWarnings));

    if let Some(op_id) = crate::operation_id::resolve_operation_id(cli)? {
        builder = builder.op_id(op_id.to_string());
    }

    Ok(builder.build())
}

fn render_verify(output: &VerifyReport, verbose: bool, as_json: bool) -> Result<()> {
    if as_json {
        crate::cli::render::write_json_stdout(output)?;
        return Ok(());
    }
    if !verbose {
        return render_compact_verify(output);
    }

    println!("{}", style::bold("Heddle verify"));
    println!("Repository: {}", output.repository_label);
    render_verify_repository_context(output);
    render_verify_observe_only_note();
    let trust_label = verify_status_label(output);
    let status = human_verify_status(output);
    println!(
        "{trust_label}: {}",
        if output.trust.verified {
            style::accent(&status)
        } else {
            style::warn(&status)
        }
    );
    println!();
    for (row, label) in [
        ("Git", "Git"),
        ("Heddle", "Heddle"),
        ("Mapping", "Mapping"),
        ("Worktree", "Worktree"),
        ("Remote", "Remote"),
        ("Operation", "Operation"),
        ("Workflow", "Workflow"),
        ("Machine contract", "Machine contract"),
        ("Clone", "Checkout"),
    ] {
        let check = output
            .trust
            .checks
            .iter()
            .find(|check| check.name.eq_ignore_ascii_case(row));
        let summary = check.map(|check| check.summary.as_str());
        match check {
            Some(check) => {
                let (status, summary) = (
                    if check.status == "not_applicable" {
                        style::dim("n/a")
                    } else if check.clean && check.status != "clean" {
                        style::accent(&check.status)
                    } else if check.clean {
                        style::accent("ok")
                    } else {
                        style::warn(&human_check_status(check))
                    },
                    human_summary(check, summary, verbose),
                );
                println!("{:<18} {} {}", label, status, style::dim(&summary));
            }
            None => println!("{:<18} {}", label, style::dim("not checked")),
        }
        if verbose
            && let Some(check) = check
            && !check.details.is_empty()
        {
            let details = check
                .details
                .iter()
                .map(|(key, value)| format!("{key}={value}"))
                .collect::<Vec<_>>()
                .join(", ");
            println!("{:<18} {}", "", style::dim(&details));
        }
    }
    if verbose {
        println!();
        println!("Repository mode: {}", output.trust.repository_mode);
        if let Some(branch) = &output.trust.git_branch {
            println!("Git branch: {branch}");
        }
        if let Some(thread) = &output.trust.heddle_thread {
            println!("Heddle thread: {thread}");
        }
    }
    if !output.trust.recommended_action.is_empty() {
        println!();
        print_next(&output.trust.recommended_action);
    }
    if !output.trust.recovery_commands.is_empty() && verbose {
        for command in &output.trust.recovery_commands {
            println!("Recovery: {}", style::bold(command));
        }
    }
    Ok(())
}

fn render_compact_verify(output: &VerifyReport) -> Result<()> {
    println!("{}", style::bold("Heddle verify"));
    println!("Repository: {}", output.repository_label);
    render_verify_repository_context(output);
    render_verify_observe_only_note();
    let trust_label = "Workspace";
    let status = compact_verify_status(output);
    println!(
        "{trust_label}: {}",
        if output.trust.verified {
            style::accent(&status)
        } else {
            style::warn(&status)
        }
    );
    let summary = human_output_summary(output);
    let blocker = output.trust.checks.iter().find(|check| !check.clean);
    let blocker_summary = blocker.map(|check| human_summary(check, None, false));
    if !summary.is_empty() && blocker_summary.as_deref() != Some(summary.as_str()) {
        println!("{}", style::dim(&summary));
    }
    if render_compact_setup_needed(output) {
        // Setup wording is deliberately plain here; verbose/JSON still expose
        // the individual Heddle and mapping checks for diagnostic callers.
    } else if let Some(blocker) = blocker {
        println!();
        if is_worktree_save_blocker(blocker) {
            println!(
                "Changes to save: {}",
                style::dim(&human_summary(blocker, None, false))
            );
        } else if is_import_setup_blocker(blocker) {
            println!(
                "Setup needed: {}",
                style::dim(&human_summary(blocker, None, false))
            );
        } else if is_checkpoint_blocker(blocker) {
            println!(
                "Saved in Heddle: {}",
                style::dim(&human_summary(blocker, None, false))
            );
        } else {
            println!(
                "Blocked: {}",
                style::dim(&human_summary(blocker, None, false))
            );
        }
    } else if summary.is_empty() {
        println!("{}", style::dim("All checks agree."));
    }
    if !output.trust.recommended_action.is_empty() {
        println!();
        print_next(&output.trust.recommended_action);
    }
    println!();
    println!("Proof: {}", style::bold("heddle verify --verbose"));
    Ok(())
}

fn render_verify_observe_only_note() {
    println!(
        "Mode: {}",
        style::dim("observe-only; no refs, objects, index, or worktree files are changed")
    );
}

fn render_verify_repository_context(output: &VerifyReport) {
    let Some(context) = &output.repository_context else {
        return;
    };
    if let Some(parent_repository) = &context.parent_repository {
        println!("Parent repo: {}", parent_repository);
    }
    if let Some(target_thread) = &context.target_thread {
        println!("Target thread: {}", target_thread);
    }
    if let Some(parent_thread) = &context.parent_thread {
        println!("Parent thread: {}", parent_thread);
    }
}

fn human_verify_status(output: &VerifyReport) -> String {
    if let Some(check) = output.trust.checks.iter().find(|check| !check.clean) {
        if is_worktree_save_blocker(check) {
            return "changes to save".to_string();
        }
        if is_import_setup_blocker(check) {
            return "setup needed".to_string();
        }
        if is_checkpoint_blocker(check) {
            return "saved in Heddle; checkpoint needed".to_string();
        }
    }
    output.trust.status.clone()
}

fn compact_verify_status(output: &VerifyReport) -> String {
    if output.trust.verified {
        "verified".to_string()
    } else {
        human_verify_status(output)
    }
}

fn human_check_status(check: &VerificationCheck) -> String {
    if is_worktree_save_blocker(check) {
        "changes".to_string()
    } else if is_import_setup_blocker(check) {
        "setup needed".to_string()
    } else if is_checkpoint_blocker(check) {
        "checkpoint needed".to_string()
    } else {
        check.status.clone()
    }
}

fn human_output_summary(output: &VerifyReport) -> String {
    if let Some(check) = output.trust.checks.iter().find(|check| !check.clean) {
        if setup_needed_guidance(output).is_some() {
            return String::new();
        }
        if is_worktree_save_blocker(check)
            || is_import_setup_blocker(check)
            || is_checkpoint_blocker(check)
        {
            return human_summary(check, None, false);
        }
    }
    human_clean_summary(output).to_string()
}

fn is_worktree_save_blocker(check: &VerificationCheck) -> bool {
    check.name == "Worktree" && matches!(check.status.as_str(), "dirty_worktree" | "uncaptured")
}

fn is_import_setup_blocker(check: &VerificationCheck) -> bool {
    matches!(check.name.as_str(), "Mapping" | "Heddle")
        && matches!(check.status.as_str(), "needs_import" | "needs_init")
}

fn is_checkpoint_blocker(check: &VerificationCheck) -> bool {
    check.name == "Worktree" && check.status == "needs_checkpoint"
}

fn verify_status_label(output: &VerifyReport) -> &'static str {
    if output.trust.repository_mode == "git-overlay" || output.trust.repository_mode == "plain-git"
    {
        "Git and Heddle"
    } else {
        "Repository verification"
    }
}

fn verify_failed_advice(verification: &RepositoryVerificationState) -> RecoveryAdvice {
    let primary_command = if verification.recommended_action.trim().is_empty() {
        "heddle status".to_string()
    } else {
        verification.recommended_action.clone()
    };
    let mut recovery_commands = verification.recovery_commands.clone();
    if recovery_commands.is_empty() || recovery_commands[0] != primary_command {
        recovery_commands.insert(0, primary_command.clone());
    }
    let mut advice = RecoveryAdvice::safety_refusal(
        "verify_failed",
        format!("Repository is not verified: {}", verification.status),
        format!("Run `{primary_command}` to clear the primary verification blocker."),
        verification.summary.clone(),
        "`heddle verify` is a strict proof gate and returns nonzero until every verification check is clean",
        "verify is observe-only; repository objects, refs, index, and worktree files were left unchanged",
        primary_command,
        recovery_commands,
    );
    if let Ok(value) = serde_json::to_value(verification) {
        advice
            .extra_json_fields
            .insert("verification".to_string(), value);
    }
    advice
}

fn human_clean_summary(output: &VerifyReport) -> &str {
    if output.trust.summary == "Git overlay and Heddle agree" {
        if output.trust.recommended_action.is_empty() {
            "Nothing to do. Workspace verified."
        } else if output.trust.recommended_action.contains("push") {
            "Local work is ready to publish."
        } else if output.trust.recommended_action.contains("land") {
            "Thread is ready to land."
        } else {
            "Workspace verified."
        }
    } else {
        &output.trust.summary
    }
}

fn human_summary(
    check: &VerificationCheck,
    override_summary: Option<&str>,
    _verbose: bool,
) -> String {
    if is_worktree_save_blocker(check) {
        let count = check
            .details
            .get("dirty_path_count")
            .and_then(|count| count.parse::<usize>().ok());
        return match count {
            Some(1) => "1 path has unsaved changes".to_string(),
            Some(count) => format!("{count} paths have unsaved changes"),
            None => "worktree has unsaved changes".to_string(),
        };
    }
    if is_import_setup_blocker(check) {
        return override_summary
            .unwrap_or("import this branch tip before comparing Heddle state")
            .replace("still need Heddle import", "need Heddle setup");
    }
    if is_checkpoint_blocker(check) {
        return override_summary
            .unwrap_or("saved in Heddle; run checkpoint or commit to write Git")
            .replace(
                "captured in Heddle but not checkpointed to Git",
                "saved in Heddle and ready to checkpoint to Git",
            );
    }
    override_summary.unwrap_or(&check.summary).to_string()
}

fn render_compact_setup_needed(output: &VerifyReport) -> bool {
    let Some(setup) = setup_needed_guidance(output) else {
        return false;
    };
    println!();
    println!("Setup needed: {}", style::warn(&setup.setup_line));
    println!("{}", style::dim(&setup.effect));
    true
}

fn setup_needed_guidance(output: &VerifyReport) -> Option<VerifySetupGuidance> {
    let blocker = output.trust.checks.iter().find(|check| !check.clean)?;
    if !is_import_setup_blocker(blocker) {
        return None;
    }
    repository_setup_guidance(&output.trust)
}

#[derive(Debug, Clone)]
struct VerifySetupGuidance {
    setup_line: String,
    effect: String,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum RepositorySetupActionKind {
    Init,
    Adopt,
    BridgeImport,
    Other,
}

fn repository_setup_guidance(trust: &RepositoryVerificationState) -> Option<VerifySetupGuidance> {
    if !matches!(trust.status.as_str(), "needs_init" | "needs_import") {
        return None;
    }
    let action = trust.recommended_action.trim();
    if action.is_empty() {
        return None;
    }
    let kind = repository_setup_action_kind(action);
    let setup_line = match kind {
        RepositorySetupActionKind::Init => {
            format!("Git repo detected; initialize Heddle with {action}")
        }
        RepositorySetupActionKind::Adopt => {
            format!("Git repo detected; connect this branch with {action}")
        }
        RepositorySetupActionKind::BridgeImport => {
            format!("Git history not imported; import it with {action}")
        }
        RepositorySetupActionKind::Other => {
            format!("Run {action} to clear the primary setup blocker")
        }
    };
    let worktree_tail = if trust.worktree_state == "clean" {
        "and the Git worktree stays clean"
    } else {
        "and existing Git worktree changes stay untouched"
    };
    let effect = match kind {
        RepositorySetupActionKind::Init => format!(
            ".heddle metadata will be created; Git commits stay in Git storage, {worktree_tail}."
        ),
        RepositorySetupActionKind::Adopt
            if trust.repository_mode == "plain-git" && !trust.heddle_initialized =>
        {
            format!(".heddle metadata will be created, Git history imported, {worktree_tail}.")
        }
        RepositorySetupActionKind::Adopt => {
            format!(".heddle metadata is present; adoption imports Git history {worktree_tail}.")
        }
        RepositorySetupActionKind::BridgeImport => {
            format!(".heddle metadata is present; Git history import runs {worktree_tail}.")
        }
        RepositorySetupActionKind::Other => {
            format!("The recommended setup command runs {worktree_tail}.")
        }
    };
    Some(VerifySetupGuidance { setup_line, effect })
}

fn repository_setup_action_kind(action: &str) -> RepositorySetupActionKind {
    if action == "heddle init" {
        RepositorySetupActionKind::Init
    } else if action.starts_with("heddle adopt") {
        RepositorySetupActionKind::Adopt
    } else if action.starts_with("heddle bridge git import") {
        RepositorySetupActionKind::BridgeImport
    } else {
        RepositorySetupActionKind::Other
    }
}
