use std::fs;

use llmcc::{run_main, LlmccOptions};
use llmcc_rust::LangRust;
use tempfile::tempdir;

fn fixture_source() -> &'static str {
    r#"
        fn leaf() {}

        fn middle() {
            leaf();
        }

        fn root() {
            middle();
        }
    "#
}

fn write_fixture() -> (tempfile::TempDir, String) {
    let dir = tempdir().expect("tempdir");
    let file_path = dir.path().join("fixture.rs");
    fs::write(&file_path, fixture_source()).expect("write fixture");
    (dir, file_path.display().to_string())
}

fn base_options(file: String) -> LlmccOptions {
    LlmccOptions {
        files: vec![file],
        dirs: Vec::new(),
        print_ir: false,
        print_block: false,
        design_graph: false,
        pagerank: false,
        top_k: None,
        query: None,
        depends: false,
        dependents: false,
        recursive: false,
        summary: false,
    }
}

#[test]
fn depends_recursive_expands_results() {
    let (_dir, file) = write_fixture();

    let mut opts = base_options(file.clone());
    opts.query = Some("root".to_string());
    opts.depends = true;
    opts.recursive = false;

    let direct = run_main::<LangRust>(&opts)
        .expect("direct depends run")
        .expect("direct depends output");
    assert!(
        direct.contains("fn middle()"),
        "direct depends missing middle: {direct}"
    );
    assert!(
        !direct.contains("fn leaf()"),
        "direct depends should not include leaf: {direct}"
    );

    opts.recursive = true;
    let recursive = run_main::<LangRust>(&opts)
        .expect("recursive depends run")
        .expect("recursive depends output");
    assert!(
        recursive.contains("fn middle()"),
        "recursive depends missing middle: {recursive}"
    );
    assert!(
        recursive.contains("fn leaf()"),
        "recursive depends missing leaf: {recursive}"
    );
}

#[test]
fn dependents_recursive_expands_results() {
    let (_dir, file) = write_fixture();

    let mut opts = base_options(file);
    opts.query = Some("leaf".to_string());
    opts.dependents = true;
    opts.recursive = false;

    let direct = run_main::<LangRust>(&opts)
        .expect("direct dependents run")
        .expect("direct dependents output");
    assert!(
        direct.contains("fn middle()"),
        "direct dependents missing middle: {direct}"
    );
    assert!(
        !direct.contains("fn root()"),
        "direct dependents should not include root: {direct}"
    );

    opts.recursive = true;
    let recursive = run_main::<LangRust>(&opts)
        .expect("recursive dependents run")
        .expect("recursive dependents output");
    assert!(
        recursive.contains("fn middle()"),
        "recursive dependents missing middle: {recursive}"
    );
    assert!(
        recursive.contains("fn root()"),
        "recursive dependents missing root: {recursive}"
    );
}

#[test]
fn files_and_dirs_conflict() {
    let (dir, file) = write_fixture();
    let dir_path = dir.path().display().to_string();

    let mut opts = base_options(file);
    opts.dirs = vec![dir_path];

    let err = run_main::<LangRust>(&opts).expect_err("files and dirs should conflict");
    assert!(
        err.to_string().contains("--file") && err.to_string().contains("--dir"),
        "unexpected error message: {err}"
    );
}

#[test]
fn summary_output_omits_source_code() {
    let (_dir, file) = write_fixture();

    let mut opts = base_options(file);
    opts.query = Some("leaf".to_string());
    opts.dependents = true;
    opts.summary = true;

    let output = run_main::<LangRust>(&opts)
        .expect("summary query run")
        .expect("summary output");

    assert!(
        output.contains("DEPENDENTS"),
        "missing dependents header: {output}"
    );
    assert!(output.contains("leaf"), "missing symbol name: {output}");
    assert!(
        output.contains(".rs:"),
        "expected file path with line info: {output}"
    );
    assert!(
        !output.contains("┌─"),
        "summary should not include block rendering: {output}"
    );
}

#[test]
fn pagerank_requires_design_graph() {
    let (dir, file) = write_fixture();
    let dir_path = dir.path().display().to_string();

    let mut opts = base_options(file);
    opts.files.clear();
    opts.dirs = vec![dir_path];
    opts.pagerank = true;

    let err =
        run_main::<LangRust>(&opts).expect_err("--pagerank without --design-graph should fail");
    assert!(
        err.to_string()
            .contains("--pagerank requires --design-graph"),
        "unexpected error: {err}"
    );
}
