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).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).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).expect("Failed to parse");
    let pipeline =
        cui_cui::resolve::resolve_pipeline(raw, &cui_cui::resolve::GitContext::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 -euo 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).expect("Failed to parse");
    let pipeline =
        cui_cui::resolve::resolve_pipeline(raw, &cui_cui::resolve::GitContext::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())
        .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())
            .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).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).expect("Failed to parse");
    let pipeline =
        cui_cui::resolve::resolve_pipeline(raw, &cui_cui::resolve::GitContext::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).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).expect("Failed to parse");
    let pipeline =
        cui_cui::resolve::resolve_pipeline(raw, &cui_cui::resolve::GitContext::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).expect("Failed to parse");
    let pipeline =
        cui_cui::resolve::resolve_pipeline(raw, &cui_cui::resolve::GitContext::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).expect("Failed to parse");
    let pipeline =
        cui_cui::resolve::resolve_pipeline(raw, &cui_cui::resolve::GitContext::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).expect("Failed to parse");
    let pipeline =
        cui_cui::resolve::resolve_pipeline(raw, &cui_cui::resolve::GitContext::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).expect("Failed to parse");
    let pipeline =
        cui_cui::resolve::resolve_pipeline(raw, &cui_cui::resolve::GitContext::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).expect("Failed to parse");
    let pipeline =
        cui_cui::resolve::resolve_pipeline(raw, &cui_cui::resolve::GitContext::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 -euo 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).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).expect("Failed to parse");
    let git_context = cui_cui::resolve::GitContext::default();
    let pipeline =
        cui_cui::resolve::resolve_pipeline(raw, &git_context).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() {
    // A GitLab job with no image and no runs-on tags should return None
    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![],
        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_when: "on_success".to_string(),
        timeout: None,
        resource_group: None,
        coverage: None,
        resolved_yaml: String::new(),
        trigger_file: 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,
    );

    assert!(config.is_none());
}

#[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).expect("Failed to parse");
    let git_context = cui_cui::resolve::GitContext::default();
    let pipeline =
        cui_cui::resolve::resolve_pipeline(raw, &git_context).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).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, "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())
        .expect("Failed to parse mesa CI");
    let pipeline =
        cui_cui::resolve::resolve_pipeline(raw, &cui_cui::resolve::GitContext::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).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).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).expect("Failed to parse");
    let git = cui_cui::resolve::GitContext::default();
    let pipeline = cui_cui::resolve::resolve_pipeline(raw, &git).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).expect("Failed to parse");
    let git = cui_cui::resolve::GitContext::default();
    let pipeline = cui_cui::resolve::resolve_pipeline(raw, &git).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\""]);
}
