use indexmap::IndexMap;
use std::path::Path;

// Access the library modules via the binary crate's structure
// For tests, we'll re-implement the parse+resolve flow directly

#[test]
fn test_parse_and_resolve_sample() {
    let path = Path::new("examples/sample.gitlab-ci.yml");
    assert!(path.exists(), "Sample file must exist");

    let raw = cui_cui::parser::parse_pipeline_file(path, None, false).expect("Failed to parse");

    // Check stages
    assert_eq!(raw.stages, vec!["build", "test", "deploy"]);

    // Check we found all jobs (including hidden ones)
    assert!(raw.jobs.contains_key(".base_job"));
    assert!(raw.jobs.contains_key(".test_base"));
    assert!(raw.jobs.contains_key("compile"));
    assert!(raw.jobs.contains_key("unit_tests"));
    assert!(raw.jobs.contains_key("deploy_production"));

    // Resolve
    let git = cui_cui::resolve::GitContext::default();
    let pipeline = cui_cui::resolve::resolve_pipeline(raw, &git, &Default::default())
        .expect("Failed to resolve");

    // Hidden jobs should be filtered out
    assert!(!pipeline.jobs.contains_key(".base_job"));
    assert!(!pipeline.jobs.contains_key(".test_base"));

    // Visible jobs should exist
    assert!(pipeline.jobs.contains_key("compile"));
    assert!(pipeline.jobs.contains_key("unit_tests"));
    assert!(pipeline.jobs.contains_key("integration_tests"));
    assert!(pipeline.jobs.contains_key("deploy_staging"));
    assert!(pipeline.jobs.contains_key("deploy_production"));

    // Check extends resolution: unit_tests should have inherited image from .base_job
    let unit_tests = &pipeline.jobs["unit_tests"];
    assert!(
        unit_tests.image.as_ref().unwrap().contains("rust:"),
        "unit_tests should inherit image from .base_job via .test_base"
    );

    // Check stage assignment
    assert_eq!(unit_tests.stage, "test");

    // Check needs
    assert_eq!(unit_tests.need_names(), vec!["compile"]);

    // Check variable inheritance
    assert_eq!(
        unit_tests
            .variables
            .get("RUST_BACKTRACE")
            .map(|s| s.as_str()),
        Some("1"),
        "unit_tests should inherit RUST_BACKTRACE from .test_base"
    );

    // Check deploy_production is manual
    let deploy_prod = &pipeline.jobs["deploy_production"];
    assert!(deploy_prod.is_manual());
    assert!(!deploy_prod.allow_failure); // explicitly set to false

    // Check stages
    assert_eq!(pipeline.stages.len(), 3);
    assert_eq!(pipeline.stages[0].name, "build");
    assert_eq!(pipeline.stages[1].name, "test");
    assert_eq!(pipeline.stages[2].name, "deploy");

    // Check stage assignments
    assert!(
        pipeline.stages[0]
            .job_names
            .contains(&"compile".to_string())
    );
    assert!(pipeline.stages[0].job_names.contains(&"lint".to_string()));
    assert!(
        pipeline.stages[1]
            .job_names
            .contains(&"unit_tests".to_string())
    );
}

#[test]
fn test_build_local_script() {
    let path = Path::new("examples/sample.gitlab-ci.yml");
    let raw = cui_cui::parser::parse_pipeline_file(path, None, false).expect("Failed to parse");
    let pipeline = cui_cui::resolve::resolve_pipeline(
        raw,
        &cui_cui::resolve::GitContext::default(),
        &Default::default(),
    )
    .expect("Failed to resolve");

    let compile = &pipeline.jobs["compile"];
    let script = compile.build_local_script(cui_cui::model::CiSystem::GitLab);

    assert!(script.contains("#!/usr/bin/env bash"));
    assert!(script.contains("set -eo pipefail"));
    assert!(script.contains("cargo build --release"));
    assert!(script.contains("rustup show")); // from before_script via extends
}

#[test]
fn test_graph_construction() {
    let path = Path::new("examples/sample.gitlab-ci.yml");
    let raw = cui_cui::parser::parse_pipeline_file(path, None, false).expect("Failed to parse");
    let pipeline = cui_cui::resolve::resolve_pipeline(
        raw,
        &cui_cui::resolve::GitContext::default(),
        &Default::default(),
    )
    .expect("Failed to resolve");

    let layout = cui_cui::graph::compute_layout(&pipeline);

    // Should have nodes for all visible jobs
    assert_eq!(layout.nodes.len(), pipeline.jobs.len());

    // Should have edges
    assert!(!layout.edges.is_empty());

    // deploy_staging should depend on unit_tests and integration_tests
    let staging_edges: Vec<_> = layout
        .edges
        .iter()
        .filter(|e| e.to == "deploy_staging")
        .collect();
    let staging_deps: Vec<&str> = staging_edges.iter().map(|e| e.from.as_str()).collect();
    assert!(staging_deps.contains(&"unit_tests"));
    assert!(staging_deps.contains(&"integration_tests"));
}

#[test]
fn test_remote_include_parsing() {
    let base = std::env::var("CUICUI_TEST_HELM_REPO")
        .unwrap_or_else(|_| "../helm-gitlab-deployment".to_string());
    let path = std::path::PathBuf::from(base).join(".gitlab-ci.yml");
    let path = path.as_path();
    if !path.exists() {
        eprintln!(
            "Skipping: {} not found (set CUICUI_TEST_HELM_REPO to override)",
            path.display()
        );
        return;
    }

    let config = cui_cui::parser::gitlab_fetch::GitlabConfig::detect(path, None);
    assert!(
        config.is_some(),
        "Should detect GitLab instance from git remote"
    );

    let raw = cui_cui::parser::parse_pipeline_file(path, config.as_ref(), false)
        .expect("Failed to parse with remote includes");

    println!("Jobs found ({}):", raw.jobs.len());
    for name in raw.jobs.keys() {
        println!("  {}", name);
    }

    // The remote include should have brought in .fdo.container-build@alpine
    assert!(
        raw.jobs.contains_key(".fdo.container-build@alpine"),
        "Should have .fdo.container-build@alpine from remote include"
    );

    // Now try to resolve
    let pipeline = cui_cui::resolve::resolve_pipeline(
        raw,
        &cui_cui::resolve::GitContext::default(),
        &Default::default(),
    )
    .expect("Failed to resolve pipeline with remote includes");

    assert!(pipeline.jobs.contains_key("container-prep"));
    assert!(pipeline.jobs.contains_key("lint"));
}

#[test]
fn test_github_workflow_parse_and_resolve() {
    let path = Path::new("examples/sample-github-workflow.yml");
    assert!(path.exists(), "Sample GitHub workflow must exist");

    let raw = cui_cui::parser::github::parse_workflow_file(path).expect("Failed to parse workflow");

    // Check jobs were found
    assert_eq!(raw.jobs.len(), 5);
    assert!(raw.jobs.contains_key("check"));
    assert!(raw.jobs.contains_key("build"));
    assert!(raw.jobs.contains_key("test"));
    assert!(raw.jobs.contains_key("coverage"));
    assert!(raw.jobs.contains_key("deploy"));

    // Check global variables
    assert_eq!(raw.variables["CARGO_TERM_COLOR"].value(), "always");

    // Resolve
    let pipeline = cui_cui::resolve::github::resolve_pipeline(raw, &IndexMap::new())
        .expect("Failed to resolve workflow");

    // Check stages (computed from dependency graph)
    // check, build → stage 0; test, coverage → stage 1; deploy → stage 2
    assert_eq!(pipeline.stages.len(), 3);

    // Check job stage assignments
    assert_eq!(pipeline.jobs["check"].stage, "0");
    assert_eq!(pipeline.jobs["build"].stage, "0");
    assert_eq!(pipeline.jobs["test"].stage, "1");
    assert_eq!(pipeline.jobs["coverage"].stage, "1");
    assert_eq!(pipeline.jobs["deploy"].stage, "2");

    // Check needs
    assert_eq!(pipeline.jobs["test"].need_names(), vec!["build"]);
    assert_eq!(pipeline.jobs["coverage"].need_names(), vec!["build"]);
    let mut deploy_needs = pipeline.jobs["deploy"].need_names();
    deploy_needs.sort();
    assert_eq!(deploy_needs, vec!["coverage", "test"]);

    // Check container image
    assert_eq!(pipeline.jobs["build"].image.as_deref(), Some("rust:1.80"));

    // Check variables merged (global + job)
    assert_eq!(
        pipeline.jobs["build"].variables["CARGO_TERM_COLOR"],
        "always"
    );
    assert_eq!(pipeline.jobs["build"].variables["RUSTFLAGS"], "-D warnings");

    // Check tags from runs-on
    assert_eq!(pipeline.jobs["check"].tags, vec!["ubuntu-latest"]);

    // Check script has content from steps
    assert!(!pipeline.jobs["build"].script.is_empty());

    // Check graph construction works
    let layout = cui_cui::graph::compute_layout(&pipeline);
    assert_eq!(layout.nodes.len(), 5);
    assert!(!layout.edges.is_empty());
}

// --- Tests for headless CLI flag code paths ---

#[test]
fn test_list_jobs_gitlab() {
    let path = Path::new("examples/sample.gitlab-ci.yml");
    let raw = cui_cui::parser::parse_pipeline_file(path, None, false).expect("Failed to parse");
    let pipeline = cui_cui::resolve::resolve_pipeline(
        raw,
        &cui_cui::resolve::GitContext::default(),
        &Default::default(),
    )
    .expect("Failed to resolve");

    // Verify stages and job names match what --list-jobs would print
    assert_eq!(pipeline.stages[0].name, "build");
    assert!(
        pipeline.stages[0]
            .job_names
            .contains(&"compile".to_string())
    );
    assert!(pipeline.stages[0].job_names.contains(&"lint".to_string()));
    assert_eq!(pipeline.stages[1].name, "test");
    assert!(
        pipeline.stages[1]
            .job_names
            .contains(&"unit_tests".to_string())
    );
    assert_eq!(pipeline.stages[2].name, "deploy");
    assert!(
        pipeline.stages[2]
            .job_names
            .contains(&"deploy_production".to_string())
    );
}

#[test]
fn test_list_jobs_github() {
    let path = Path::new("examples/sample-github-workflow.yml");
    let raw = cui_cui::parser::github::parse_workflow_file(path).expect("Failed to parse");
    let pipeline = cui_cui::resolve::github::resolve_pipeline(raw, &IndexMap::new())
        .expect("Failed to resolve workflow");

    // GitHub stages are numbered; verify jobs are grouped correctly
    assert!(!pipeline.stages.is_empty());
    let all_job_names: Vec<&str> = pipeline
        .stages
        .iter()
        .flat_map(|s| s.job_names.iter().map(|n| n.as_str()))
        .collect();
    assert!(all_job_names.contains(&"check"));
    assert!(all_job_names.contains(&"build"));
    assert!(all_job_names.contains(&"deploy"));
}

#[test]
fn test_find_job_exact_match() {
    let path = Path::new("examples/sample.gitlab-ci.yml");
    let raw = cui_cui::parser::parse_pipeline_file(path, None, false).expect("Failed to parse");
    let pipeline = cui_cui::resolve::resolve_pipeline(
        raw,
        &cui_cui::resolve::GitContext::default(),
        &Default::default(),
    )
    .expect("Failed to resolve");

    assert_eq!(pipeline.find_job("compile"), Ok("compile"));
    assert_eq!(pipeline.find_job("unit_tests"), Ok("unit_tests"));
}

#[test]
fn test_find_job_case_insensitive() {
    let path = Path::new("examples/sample.gitlab-ci.yml");
    let raw = cui_cui::parser::parse_pipeline_file(path, None, false).expect("Failed to parse");
    let pipeline = cui_cui::resolve::resolve_pipeline(
        raw,
        &cui_cui::resolve::GitContext::default(),
        &Default::default(),
    )
    .expect("Failed to resolve");

    assert_eq!(pipeline.find_job("COMPILE"), Ok("compile"));
    assert_eq!(pipeline.find_job("Unit_Tests"), Ok("unit_tests"));
}

#[test]
fn test_find_job_substring() {
    let path = Path::new("examples/sample.gitlab-ci.yml");
    let raw = cui_cui::parser::parse_pipeline_file(path, None, false).expect("Failed to parse");
    let pipeline = cui_cui::resolve::resolve_pipeline(
        raw,
        &cui_cui::resolve::GitContext::default(),
        &Default::default(),
    )
    .expect("Failed to resolve");

    // "lint" is a unique substring
    assert_eq!(pipeline.find_job("lint"), Ok("lint"));
}

#[test]
fn test_find_job_ambiguous() {
    let path = Path::new("examples/sample.gitlab-ci.yml");
    let raw = cui_cui::parser::parse_pipeline_file(path, None, false).expect("Failed to parse");
    let pipeline = cui_cui::resolve::resolve_pipeline(
        raw,
        &cui_cui::resolve::GitContext::default(),
        &Default::default(),
    )
    .expect("Failed to resolve");

    // "deploy" matches both deploy_staging and deploy_production
    let err = pipeline.find_job("deploy").unwrap_err();
    assert!(err.contains("Ambiguous"));
    assert!(err.contains("deploy_staging"));
    assert!(err.contains("deploy_production"));
}

#[test]
fn test_find_job_not_found() {
    let path = Path::new("examples/sample.gitlab-ci.yml");
    let raw = cui_cui::parser::parse_pipeline_file(path, None, false).expect("Failed to parse");
    let pipeline = cui_cui::resolve::resolve_pipeline(
        raw,
        &cui_cui::resolve::GitContext::default(),
        &Default::default(),
    )
    .expect("Failed to resolve");

    let err = pipeline.find_job("nonexistent").unwrap_err();
    assert!(err.contains("not found"));
}

#[test]
fn test_dump_script_gitlab() {
    let path = Path::new("examples/sample.gitlab-ci.yml");
    let raw = cui_cui::parser::parse_pipeline_file(path, None, false).expect("Failed to parse");
    let pipeline = cui_cui::resolve::resolve_pipeline(
        raw,
        &cui_cui::resolve::GitContext::default(),
        &Default::default(),
    )
    .expect("Failed to resolve");

    let job_name = pipeline.find_job("compile").unwrap();
    let job = &pipeline.jobs[job_name];
    let script = job.build_local_script(cui_cui::model::CiSystem::GitLab);

    assert!(script.starts_with("#!/usr/bin/env bash"));
    assert!(script.contains("set -eo pipefail"));
    assert!(script.contains("cargo build --release"));
}

#[test]
fn test_dump_script_github() {
    let path = Path::new("examples/sample-github-workflow.yml");
    let raw = cui_cui::parser::github::parse_workflow_file(path).expect("Failed to parse");
    let pipeline = cui_cui::resolve::github::resolve_pipeline(raw, &IndexMap::new())
        .expect("Failed to resolve workflow");

    let job_name = pipeline.find_job("check").unwrap();
    let job = &pipeline.jobs[job_name];
    let script = job.build_local_script(cui_cui::model::CiSystem::GitHub);

    assert!(script.starts_with("#!/usr/bin/env bash"));
    assert!(script.contains("set -eo pipefail"));
}

#[test]
fn test_build_config_for_job_with_image() {
    let path = Path::new("examples/sample.gitlab-ci.yml");
    let raw = cui_cui::parser::parse_pipeline_file(path, None, false).expect("Failed to parse");
    let git_context = cui_cui::resolve::GitContext::default();
    let pipeline = cui_cui::resolve::resolve_pipeline(raw, &git_context, &Default::default())
        .expect("Failed to resolve");

    let job_name = pipeline.find_job("compile").unwrap();
    let job = &pipeline.jobs[job_name];
    let config = cui_cui::container::build_config_for_job(
        job,
        cui_cui::model::CiSystem::GitLab,
        path,
        None,
        &[],
        false,
        false,
        &git_context,
        cui_cui::container::DEFAULT_GIT_IMAGE,
    );

    assert!(config.is_some());
    let config = config.unwrap();
    assert!(config.image.contains("rust:"));
    assert_eq!(config.job_name, "compile");
    assert!(!config.privileged);
    assert!(!config.no_overlay);
}

#[test]
fn test_build_config_for_job_no_image_uses_default() {
    // A GitLab job with no image should get the default image
    let job = cui_cui::model::Job {
        name: "no-image-job".to_string(),
        stage: "test".to_string(),
        image: None,
        services: vec![],
        variables: indexmap::IndexMap::new(),
        before_script: vec![],
        script: vec!["echo hi".to_string()],
        after_script: vec![],
        needs: vec![],
        artifact_sources: Some(vec![]),
        tags: vec![],
        when_val: "on_success".to_string(),
        allow_failure: false,
        rules_never: false,
        unresolved_extends: vec![],
        rules_summary: vec![],
        artifacts_paths: vec![],
        artifacts_exclude: vec![],
        artifacts_untracked: false,
        artifacts_when: "on_success".to_string(),
        dotenv_report: None,
        timeout: None,
        resource_group: None,
        coverage: None,
        resolved_yaml: String::new(),
        trigger_file: None,
        build_context: None,
        docker_entrypoint: None,
        step_id: None,
        steps: vec![],
        debug_info: cui_cui::model::JobDebugInfo::default(),
    };
    let git_context = cui_cui::resolve::GitContext::default();
    let config = cui_cui::container::build_config_for_job(
        &job,
        cui_cui::model::CiSystem::GitLab,
        Path::new("examples/sample.gitlab-ci.yml"),
        None,
        &[],
        false,
        false,
        &git_context,
        cui_cui::container::DEFAULT_GIT_IMAGE,
    );

    let config = config.unwrap();
    assert_eq!(config.image, cui_cui::container::DEFAULT_IMAGE);
}

#[test]
fn test_build_config_for_job_privileged() {
    let path = Path::new("examples/sample.gitlab-ci.yml");
    let raw = cui_cui::parser::parse_pipeline_file(path, None, false).expect("Failed to parse");
    let git_context = cui_cui::resolve::GitContext::default();
    let pipeline = cui_cui::resolve::resolve_pipeline(raw, &git_context, &Default::default())
        .expect("Failed to resolve");

    let job = &pipeline.jobs["compile"];
    let config = cui_cui::container::build_config_for_job(
        job,
        cui_cui::model::CiSystem::GitLab,
        path,
        None,
        &[],
        false,
        true,
        &git_context,
        cui_cui::container::DEFAULT_GIT_IMAGE,
    );

    assert!(config.unwrap().privileged);
}

#[test]
fn test_build_config_for_job_github_runs_on() {
    let path = Path::new("examples/sample-github-workflow.yml");
    let raw = cui_cui::parser::github::parse_workflow_file(path).expect("Failed to parse");
    let git_context = cui_cui::resolve::GitContext::default();
    let pipeline = cui_cui::resolve::github::resolve_pipeline(raw, &IndexMap::new())
        .expect("Failed to resolve workflow");

    // "check" uses runs-on: ubuntu-latest (no explicit container image)
    let job = &pipeline.jobs["check"];
    let config = cui_cui::container::build_config_for_job(
        job,
        cui_cui::model::CiSystem::GitHub,
        path,
        None,
        &[],
        false,
        false,
        &git_context,
        cui_cui::container::DEFAULT_GIT_IMAGE,
    );

    assert!(config.is_some());
    let config = config.unwrap();
    assert_eq!(config.image, "docker.io/catthehacker/ubuntu:act-latest");
}

#[test]
fn test_mesa_pipeline() {
    let base = std::env::var("CUICUI_TEST_MESA_REPO").unwrap_or_else(|_| "../mesa".to_string());
    let path = std::path::PathBuf::from(base).join(".gitlab-ci.yml");
    let path = path.as_path();
    if !path.exists() {
        eprintln!(
            "Skipping: {} not found (set CUICUI_TEST_MESA_REPO to override)",
            path.display()
        );
        return;
    }
    let config = cui_cui::parser::gitlab_fetch::GitlabConfig::detect(path, None);
    let raw = cui_cui::parser::parse_pipeline_file(path, config.as_ref(), false)
        .expect("Failed to parse mesa CI");
    let pipeline = cui_cui::resolve::resolve_pipeline(
        raw,
        &cui_cui::resolve::GitContext::default(),
        &Default::default(),
    )
    .expect("Failed to resolve mesa CI");
    eprintln!(
        "Mesa: {} jobs, {} stages",
        pipeline.jobs.len(),
        pipeline.stages.len()
    );
    let layout = cui_cui::graph::compute_layout(&pipeline);
    eprintln!(
        "Layout: {} nodes, {} edges, {} cols",
        layout.nodes.len(),
        layout.edges.len(),
        layout.num_cols
    );
}

#[test]
fn test_top_level_image_inherited_by_jobs() {
    let path = Path::new("examples/top-level-image.gitlab-ci.yml");
    let raw = cui_cui::parser::parse_pipeline_file(path, None, false).expect("Failed to parse");

    // Top-level image should be folded into default
    assert!(raw.default.is_some());
    assert_eq!(
        raw.default.as_ref().unwrap().image.as_ref().unwrap().name(),
        "rust:1.85-bullseye"
    );

    let git_context = cui_cui::resolve::GitContext::default();
    let pipeline = cui_cui::resolve::resolve_pipeline(raw, &git_context, &Default::default())
        .expect("Failed to resolve");

    let job = &pipeline.jobs["test"];
    assert_eq!(
        job.image.as_deref(),
        Some("rust:1.85-bullseye"),
        "Job should inherit top-level image"
    );
    assert!(
        !job.before_script.is_empty(),
        "Job should inherit top-level before_script"
    );

    let config = cui_cui::container::build_config_for_job(
        job,
        cui_cui::model::CiSystem::GitLab,
        path,
        None,
        &[],
        false,
        false,
        &git_context,
        cui_cui::container::DEFAULT_GIT_IMAGE,
    );
    assert!(config.is_some(), "build_config_for_job should succeed");
    assert_eq!(config.unwrap().image, "rust:1.85-bullseye");
}

#[test]
fn test_include_with_inputs_caller_values() {
    let path = Path::new("examples/inputs-root.gitlab-ci.yml");
    assert!(path.exists());

    let raw = cui_cui::parser::parse_pipeline_file(path, None, false).expect("Failed to parse");
    let git = cui_cui::resolve::GitContext::default();
    let pipeline = cui_cui::resolve::resolve_pipeline(raw, &git, &Default::default())
        .expect("Failed to resolve");

    assert!(pipeline.jobs.contains_key("build-job"));
    assert!(pipeline.jobs.contains_key("deploy-component"));

    let deploy = &pipeline.jobs["deploy-component"];
    // job-stage input was set to "deploy" by the caller
    assert_eq!(deploy.stage, "deploy");
    // image input was not provided — should use default "alpine:latest"
    assert_eq!(deploy.image.as_deref(), Some("alpine:latest"));
    // environment was set to "production"
    assert_eq!(deploy.script, vec!["echo \"Deploying to production\""]);
}

#[test]
fn test_include_with_inputs_defaults_only() {
    let path = Path::new("examples/inputs-defaults.gitlab-ci.yml");
    assert!(path.exists());

    let raw = cui_cui::parser::parse_pipeline_file(path, None, false).expect("Failed to parse");
    let git = cui_cui::resolve::GitContext::default();
    let pipeline = cui_cui::resolve::resolve_pipeline(raw, &git, &Default::default())
        .expect("Failed to resolve");

    let deploy = &pipeline.jobs["deploy-component"];
    // job-stage not provided — default is "test"
    assert_eq!(deploy.stage, "test");
    // image not provided — default is "alpine:latest"
    assert_eq!(deploy.image.as_deref(), Some("alpine:latest"));
    // environment was set to "staging"
    assert_eq!(deploy.script, vec!["echo \"Deploying to staging\""]);
}

#[test]
fn test_gitlab_url_overrides_server_variables() {
    let path = Path::new("examples/sample.gitlab-ci.yml");
    let raw = cui_cui::parser::parse_pipeline_file(path, None, false).expect("Failed to parse");

    let mut git = cui_cui::resolve::GitContext::default();
    git.server_url = cui_cui::resolve::GitContext::normalize_server_url("gitlab.freedesktop.org");
    git.project_path = "bentiss/hid".to_string();

    let pipeline = cui_cui::resolve::resolve_pipeline(raw, &git, &Default::default())
        .expect("Failed to resolve");

    let job = pipeline.jobs.values().next().expect("at least one job");
    assert_eq!(
        job.variables.get("CI_SERVER_URL").map(|v| v.as_str()),
        Some("https://gitlab.freedesktop.org"),
    );
    assert_eq!(
        job.variables.get("CI_SERVER_HOST").map(|v| v.as_str()),
        Some("gitlab.freedesktop.org"),
    );
    assert_eq!(
        job.variables.get("CI_REGISTRY").map(|v| v.as_str()),
        Some("registry.freedesktop.org"),
    );
    assert_eq!(
        job.variables.get("CI_REGISTRY_IMAGE").map(|v| v.as_str()),
        Some("registry.freedesktop.org/bentiss/hid"),
    );
}

// --- Child pipeline resolution tests ---
//
// These test the --dump-script integration with child pipeline job resolution
// (trigger: include: local:), using the examples/child-pipeline fixture.

/// Helper: run cui-cui with args and return (stdout, stderr, exit_code)
fn run_cui_cui(args: &[&str]) -> (String, String, i32) {
    let output = std::process::Command::new(env!("CARGO_BIN_EXE_cui-cui"))
        .args(args)
        .output()
        .expect("failed to execute cui-cui");
    (
        String::from_utf8_lossy(&output.stdout).to_string(),
        String::from_utf8_lossy(&output.stderr).to_string(),
        output.status.code().unwrap_or(-1),
    )
}

fn run_dump_script(dir: &str, query: &str) -> (String, String, i32) {
    run_cui_cui(&[dir, "--dump-script", query])
}

#[test]
fn test_child_pipeline_auto_search() {
    let (stdout, _, code) = run_dump_script("examples/child-pipeline", "build_a");
    assert_eq!(
        code, 0,
        "should find build_a in child pipeline via auto-search"
    );
    assert!(stdout.contains("building A"));
}

#[test]
fn test_child_pipeline_slash_syntax() {
    let (stdout, _, code) = run_dump_script("examples/child-pipeline", "child_a/test_a");
    assert_eq!(code, 0, "should find test_a via child_a/test_a");
    assert!(stdout.contains("echo \"testing A\""));
}

#[test]
fn test_child_pipeline_slash_disambiguate() {
    let (stdout_a, _, code_a) = run_dump_script("examples/child-pipeline", "child_a/shared_name");
    assert_eq!(code_a, 0);
    assert!(stdout_a.contains("echo \"shared from A\""));

    let (stdout_b, _, code_b) = run_dump_script("examples/child-pipeline", "child_b/shared_name");
    assert_eq!(code_b, 0);
    assert!(stdout_b.contains("echo \"shared from B\""));
}

#[test]
fn test_child_pipeline_ambiguous_error() {
    let (_, stderr, code) = run_dump_script("examples/child-pipeline", "shared_name");
    assert_ne!(code, 0, "should fail for ambiguous name across children");
    assert!(stderr.contains("Ambiguous job"));
    assert!(stderr.contains("child_a/shared_name"));
    assert!(stderr.contains("child_b/shared_name"));
}

#[test]
fn test_child_pipeline_not_found() {
    let (_, stderr, code) = run_dump_script("examples/child-pipeline", "nonexistent");
    assert_ne!(code, 0);
    assert!(stderr.contains("not found"));
    assert!(stderr.contains("child_a"));
    assert!(stderr.contains("child_b"));
}

#[test]
fn test_child_pipeline_non_trigger_slash() {
    let (_, stderr, code) = run_dump_script("examples/child-pipeline", "compile/build_a");
    assert_ne!(code, 0, "compile is not a trigger job");
    assert!(stderr.contains("not a trigger job"));
}

#[test]
fn test_child_pipeline_root_job_still_works() {
    let (stdout, _, code) = run_dump_script("examples/child-pipeline", "compile");
    assert_eq!(code, 0, "root pipeline jobs should still resolve");
    assert!(stdout.contains("compiling"));
}

#[test]
fn test_child_pipeline_inherits_env_overrides() {
    let (stdout, _, code) = run_cui_cui(&[
        "examples/child-pipeline",
        "--dump-script",
        "build_a",
        "-e",
        "TEST_OVERRIDE=injected_value",
    ]);
    assert_eq!(code, 0);
    assert!(
        stdout.contains("export TEST_OVERRIDE=injected_value"),
        "child pipeline job should inherit -e variable overrides"
    );
}

#[test]
fn test_env_override_beats_job_level_variable() {
    // Root pipeline job: -e should override job-level variable
    let (stdout, _, code) = run_cui_cui(&[
        "examples/child-pipeline",
        "--dump-script",
        "compile",
        "-e",
        "COMPILE_FLAGS=-O0 -g",
    ]);
    assert_eq!(code, 0);
    assert!(
        stdout.contains("export COMPILE_FLAGS='-O0 -g'"),
        "-e override should beat root job-level variable, got: {}",
        stdout
    );

    // Child pipeline job: -e should override job-level variable
    let (stdout, _, code) = run_cui_cui(&[
        "examples/child-pipeline",
        "--dump-script",
        "build_a",
        "-e",
        "BUILD_MODE=custom",
    ]);
    assert_eq!(code, 0);
    assert!(
        stdout.contains("export BUILD_MODE=custom"),
        "-e override should beat child job-level variable, got: {}",
        stdout
    );
}

// --- Force variable override tests ---

#[test]
fn test_tolerant_resolver_forwards_force_vars() {
    let path = Path::new("examples/force-vars-tolerant.gitlab-ci.yml");
    let raw = cui_cui::parser::parse_pipeline_file(path, None, true).expect("Failed to parse");
    let git = cui_cui::resolve::GitContext::default();

    let overrides = indexmap::indexmap! { "OVERRIDE_ME".to_string() => "from_cli".to_string() };

    let pipeline = cui_cui::resolve::resolve_pipeline_tolerant(raw, &git, &overrides)
        .expect("Failed to resolve tolerant");

    let job = &pipeline.jobs["test_with_missing_extends"];
    assert_eq!(
        job.variables.get("OVERRIDE_ME").map(|v| v.as_str()),
        Some("from_cli"),
        "resolve_pipeline_tolerant must forward force_overrides to override job variables"
    );
}

#[test]
fn test_force_vars_override_rules_variables() {
    let path = Path::new("examples/force-vars.gitlab-ci.yml");
    let raw = cui_cui::parser::parse_pipeline_file(path, None, false).expect("Failed to parse");
    let git = cui_cui::resolve::GitContext::default();

    // Without force_vars, the rule should set OVERRIDE_ME to "from_rule"
    let pipeline_no_override = cui_cui::resolve::resolve_pipeline(raw, &git, &Default::default())
        .expect("Failed to resolve");
    let job = &pipeline_no_override.jobs["test_with_rules"];
    assert_eq!(
        job.variables.get("OVERRIDE_ME").map(|v| v.as_str()),
        Some("from_rule"),
        "Without -e, rules should set OVERRIDE_ME"
    );

    // With force_overrides, -e should win over rule variables
    let raw = cui_cui::parser::parse_pipeline_file(path, None, false).expect("Failed to parse");
    let overrides = indexmap::indexmap! { "OVERRIDE_ME".to_string() => "from_cli".to_string() };

    let pipeline =
        cui_cui::resolve::resolve_pipeline(raw, &git, &overrides).expect("Failed to resolve");
    let job = &pipeline.jobs["test_with_rules"];
    assert_eq!(
        job.variables.get("OVERRIDE_ME").map(|v| v.as_str()),
        Some("from_cli"),
        "-e override must take precedence over rules: variables"
    );
}

#[test]
fn test_force_vars_use_override_origin() {
    use cui_cui::model::VarOrigin;

    let path = Path::new("examples/force-vars.gitlab-ci.yml");
    let raw = cui_cui::parser::parse_pipeline_file(path, None, false).expect("Failed to parse");
    let git = cui_cui::resolve::GitContext::default();

    let overrides = indexmap::indexmap! { "OVERRIDE_ME".to_string() => "from_cli".to_string() };

    let pipeline =
        cui_cui::resolve::resolve_pipeline(raw, &git, &overrides).expect("Failed to resolve");

    // Check that force vars are labeled Override, not Global
    let job = &pipeline.jobs["build"];
    assert_eq!(
        job.debug_info.variable_origins.get("OVERRIDE_ME"),
        Some(&VarOrigin::Override),
        "Force vars should have VarOrigin::Override, not Global"
    );

    // Non-overridden global vars should still be Global
    assert_eq!(
        job.debug_info.variable_origins.get("GLOBAL_VAR"),
        Some(&VarOrigin::Global),
        "Non-overridden global vars should remain VarOrigin::Global"
    );

    // Force vars must keep Override origin even when a rules:variables block
    // also sets the same key (the re-application after rules must update origins).
    let job = &pipeline.jobs["test_with_rules"];
    assert_eq!(
        job.debug_info.variable_origins.get("OVERRIDE_ME"),
        Some(&VarOrigin::Override),
        "Force vars must keep VarOrigin::Override even after rules:variables re-application"
    );
}

#[test]
fn test_app_reload_applies_var_overrides() {
    let path = std::path::PathBuf::from("examples/force-vars.gitlab-ci.yml");
    let raw = cui_cui::parser::parse_pipeline_file(&path, None, false).expect("Failed to parse");
    let git = cui_cui::resolve::GitContext::default();
    let pipeline = cui_cui::resolve::resolve_pipeline(raw, &git, &Default::default()).unwrap();

    let mut app =
        cui_cui::app::App::new(path, cui_cui::model::CiSystem::GitLab, None, git, pipeline);
    app.var_overrides
        .insert("OVERRIDE_ME".to_string(), "from_cli".to_string());

    app.reload().expect("reload should succeed");

    let job = &app.current_tab().pipeline.jobs["test_with_job_var"];
    assert_eq!(
        job.variables.get("OVERRIDE_ME").map(|v| v.as_str()),
        Some("from_cli"),
        "App::reload() must apply var_overrides"
    );
}

#[test]
fn test_app_reload_uses_tolerant_resolver() {
    let path = std::path::PathBuf::from("examples/force-vars-tolerant.gitlab-ci.yml");
    let raw = cui_cui::parser::parse_pipeline_file(&path, None, true).expect("Failed to parse");
    let git = cui_cui::resolve::GitContext::default();

    let overrides = indexmap::indexmap! { "OVERRIDE_ME".to_string() => "from_cli".to_string() };
    let pipeline = cui_cui::resolve::resolve_pipeline_tolerant(raw, &git, &overrides).unwrap();

    let mut app =
        cui_cui::app::App::new(path, cui_cui::model::CiSystem::GitLab, None, git, pipeline);
    app.allow_unresolved = true;
    app.var_overrides
        .insert("OVERRIDE_ME".to_string(), "from_cli".to_string());

    app.reload()
        .expect("reload with allow_unresolved should succeed");

    let job = &app.current_tab().pipeline.jobs["test_with_missing_extends"];
    assert_eq!(
        job.variables.get("OVERRIDE_ME").map(|v| v.as_str()),
        Some("from_cli"),
        "App::reload() with allow_unresolved must apply var_overrides via tolerant resolver"
    );
}
