use indoc::indoc;
use liwe::model::config::{Configuration, LibraryOptions, MarkdownOptions};
use std::fs::{create_dir_all, read_dir, read_to_string, write};
use std::process::Command;
use tempfile::TempDir;

#[test]
fn test_extract_list_sections() {
    let temp_dir = setup_workspace_with_docs(vec![(
        "main",
        indoc! {"
            # Main Title

            ## Section A

            Content A

            ## Section B

            Content B
        "},
    )]);
    let temp_path = temp_dir.path();

    let output = run_extract_command(temp_path, &["main", "--list"]);
    assert!(output.status.success());

    let stdout = String::from_utf8(output.stdout).unwrap();
    assert_eq!(
        stdout,
        indoc! {"
            1: Main Title
            2: Section A
            3: Section B
        "}
    );
}

#[test]
fn test_extract_by_section_title() {
    let temp_dir = setup_workspace_with_docs(vec![(
        "main",
        indoc! {"
            # Main Title

            ## Section A

            Content A

            ## Section B

            Content B
        "},
    )]);
    let temp_path = temp_dir.path();

    let output = run_extract_command(temp_path, &["main", "--section", "Section A"]);
    assert!(output.status.success(), "Extract command should succeed");

    let main_content = read_to_string(temp_path.join("main.md")).unwrap();
    assert_eq!(
        main_content,
        indoc! {"
            # Main Title

            [Section A](section-a)

            ## Section B

            Content B
        "}
    );

    let extracted_file = find_extracted_file(temp_path);
    assert!(extracted_file.is_some(), "Extracted file should exist");

    let extracted_content = read_to_string(extracted_file.unwrap()).unwrap();
    assert_eq!(
        extracted_content,
        indoc! {"
            # Section A

            Content A
        "}
    );
}

#[test]
fn test_extract_by_block_number() {
    let temp_dir = setup_workspace_with_docs(vec![(
        "main",
        indoc! {"
            # Main Title

            ## Section A

            Content A

            ## Section B

            Content B
        "},
    )]);
    let temp_path = temp_dir.path();

    let output = run_extract_command(temp_path, &["main", "--block", "3"]);
    assert!(output.status.success());

    let main_content = read_to_string(temp_path.join("main.md")).unwrap();
    assert_eq!(
        main_content,
        indoc! {"
            # Main Title

            [Section B](section-b)

            ## Section A

            Content A
        "}
    );
}

#[test]
fn test_extract_ambiguous_section_fails() {
    let temp_dir = setup_workspace_with_docs(vec![(
        "main",
        indoc! {"
            # Main

            ## Notes

            First notes

            ## Notes

            Second notes
        "},
    )]);
    let temp_path = temp_dir.path();

    let output = run_extract_command(temp_path, &["main", "--section", "Notes"]);
    assert!(
        !output.status.success(),
        "Should fail with ambiguous section"
    );

    let stderr = String::from_utf8(output.stderr).unwrap();
    assert_eq!(
        stderr,
        indoc! {"
            Error: Multiple sections match 'Notes':
              2: Notes
              3: Notes
            Use --block <n> to select a specific section.
        "}
    );
}

#[test]
fn test_extract_top_level_not_allowed() {
    let temp_dir = setup_workspace_with_docs(vec![(
        "main",
        indoc! {"
            # Main Title

            Content
        "},
    )]);
    let temp_path = temp_dir.path();

    let output = run_extract_command(temp_path, &["main", "--block", "1"]);
    assert!(
        !output.status.success(),
        "Should fail for top-level section"
    );

    let stderr = String::from_utf8(output.stderr).unwrap();
    assert_eq!(stderr, "Error: Cannot extract top-level document section\n");
}

#[test]
fn test_extract_nonexistent_key() {
    let temp_dir = setup_workspace_with_docs(vec![("a", "# Doc A")]);
    let temp_path = temp_dir.path();

    let output = run_extract_command(temp_path, &["nonexistent", "--block", "1"]);
    assert!(!output.status.success());

    let stderr = String::from_utf8(output.stderr).unwrap();
    assert_eq!(stderr, "Error: Document 'nonexistent' not found\n");
}

#[test]
fn test_extract_section_not_found() {
    let temp_dir = setup_workspace_with_docs(vec![(
        "main",
        indoc! {"
            # Main

            ## Section A
        "},
    )]);
    let temp_path = temp_dir.path();

    let output = run_extract_command(temp_path, &["main", "--section", "Nonexistent"]);
    assert!(!output.status.success());

    let stderr = String::from_utf8(output.stderr).unwrap();
    assert_eq!(stderr, "Error: No section matches 'Nonexistent'\n");
}

#[test]
fn test_extract_block_out_of_range() {
    let temp_dir = setup_workspace_with_docs(vec![(
        "main",
        indoc! {"
            # Main

            ## Section A
        "},
    )]);
    let temp_path = temp_dir.path();

    let output = run_extract_command(temp_path, &["main", "--block", "10"]);
    assert!(!output.status.success());

    let stderr = String::from_utf8(output.stderr).unwrap();
    assert_eq!(stderr, "Error: Block number 10 out of range (1-2)\n");
}

#[test]
fn test_extract_dry_run() {
    let temp_dir = setup_workspace_with_docs(vec![(
        "main",
        indoc! {"
            # Main

            ## Section A

            Content A
        "},
    )]);
    let temp_path = temp_dir.path();

    let original_content = read_to_string(temp_path.join("main.md")).unwrap();

    let output = run_extract_command(temp_path, &["main", "--section", "Section A", "--dry-run"]);
    assert!(output.status.success());

    let after_content = read_to_string(temp_path.join("main.md")).unwrap();
    assert_eq!(
        original_content, after_content,
        "File should not be modified"
    );

    let file_count = read_dir(temp_path)
        .unwrap()
        .filter(|e| {
            e.as_ref()
                .unwrap()
                .path()
                .extension()
                .map(|s| s == "md")
                .unwrap_or(false)
        })
        .count();
    assert_eq!(file_count, 1, "No new files should be created");
}

#[test]
fn test_extract_keys_output() {
    let temp_dir = setup_workspace_with_docs(vec![(
        "main",
        indoc! {"
            # Main

            ## Section A
        "},
    )]);
    let temp_path = temp_dir.path();

    let output = run_extract_command(
        temp_path,
        &["main", "--section", "Section A", "--keys", "--dry-run"],
    );
    assert!(output.status.success());

    let stdout = String::from_utf8(output.stdout).unwrap();
    assert_eq!(stdout, "section-a\nmain\n");
}

#[test]
fn test_extract_quiet_mode() {
    let temp_dir = setup_workspace_with_docs(vec![(
        "main",
        indoc! {"
            # Main

            ## Section A
        "},
    )]);
    let temp_path = temp_dir.path();

    let output = run_extract_command(temp_path, &["main", "--section", "Section A", "--quiet"]);
    assert!(output.status.success());

    let stdout = String::from_utf8(output.stdout).unwrap();
    assert!(
        stdout.trim().is_empty(),
        "Quiet mode should suppress output"
    );
}

#[test]
fn test_extract_preserves_subsections() {
    let temp_dir = setup_workspace_with_docs(vec![(
        "main",
        indoc! {"
            # Main

            ## Section A

            Content A

            ### Subsection A1

            Subsection content
        "},
    )]);
    let temp_path = temp_dir.path();

    let output = run_extract_command(temp_path, &["main", "--block", "2"]);
    assert!(output.status.success());

    let extracted_file = find_extracted_file(temp_path);
    let extracted_content = read_to_string(extracted_file.unwrap()).unwrap();

    assert_eq!(
        extracted_content,
        indoc! {"
            # Section A

            Content A

            ## Subsection A1

            Subsection content
        "}
    );
}

fn setup_workspace_with_docs(docs: Vec<(&str, &str)>) -> TempDir {
    let temp_dir = TempDir::new().expect("Failed to create temp directory");
    let temp_path = temp_dir.path();

    setup_iwe_config(temp_path);

    for (key, content) in docs {
        write(temp_path.join(format!("{}.md", key)), content).expect("Should write file");
    }

    temp_dir
}

fn setup_iwe_config(temp_path: &std::path::Path) {
    create_dir_all(temp_path.join(".iwe")).expect("Failed to create .iwe directory");

    let config = Configuration {
        library: LibraryOptions {
            path: "".to_string(),
            ..Default::default()
        },
        markdown: MarkdownOptions {
            refs_extension: "".to_string(),
            ..Default::default()
        },
        ..Default::default()
    };

    let config_content = toml::to_string(&config).expect("Failed to serialize config");
    write(temp_path.join(".iwe").join("config.toml"), config_content).expect("Should write config");
}

#[test]
fn test_extract_section_and_block_conflict() {
    let temp_dir = setup_workspace_with_docs(vec![(
        "main",
        indoc! {"
            # Main

            ## Section A

            Content A
        "},
    )]);
    let temp_path = temp_dir.path();

    let output = run_extract_command(
        temp_path,
        &["main", "--section", "Section A", "--block", "2"],
    );
    assert!(
        !output.status.success(),
        "Should fail with conflicting flags"
    );

    let stderr = String::from_utf8(output.stderr).unwrap();
    assert!(
        stderr.contains("cannot be used with"),
        "Should report conflict: {}",
        stderr
    );
}

fn run_extract_command(work_dir: &std::path::Path, args: &[&str]) -> std::process::Output {
    let mut command = Command::new(crate::common::get_iwe_binary_path());
    command.arg("extract").current_dir(work_dir);

    for arg in args {
        command.arg(arg);
    }

    command.output().expect("Failed to execute iwe extract")
}

fn find_extracted_file(temp_path: &std::path::Path) -> Option<std::path::PathBuf> {
    read_dir(temp_path)
        .unwrap()
        .filter_map(|e| e.ok())
        .map(|e| e.path())
        .filter(|p| {
            p.extension().map(|s| s == "md").unwrap_or(false)
                && p.file_stem()
                    .and_then(|s| s.to_str())
                    .map(|s| s != "main")
                    .unwrap_or(false)
        })
        .next()
}
