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

#[test]
fn test_stats_markdown_output() {
    let temp_dir = setup_test_workspace();
    let output = run_stats_command(&temp_dir, &[]);

    assert!(output.status.success(), "Command should succeed");

    let stdout = String::from_utf8(output.stdout).expect("Valid UTF-8 output");

    assert!(
        stdout.contains("# Graph Statistics"),
        "Should contain title"
    );
}

#[test]
fn test_stats_csv_output() {
    let temp_dir = setup_test_workspace();
    let output = run_stats_command(&temp_dir, &["--format", "csv"]);

    assert!(output.status.success(), "Command should succeed");

    let stdout = String::from_utf8(output.stdout).expect("Valid UTF-8 output");

    let lines: Vec<&str> = stdout.lines().collect();
    assert!(
        lines.len() >= 3,
        "Should have header + at least 2 data rows"
    );

    let header = lines[0];
    let expected_headers = vec![
        "key",
        "title",
        "sections",
        "paragraphs",
        "lines",
        "words",
        "includedByCount",
        "referencedByCount",
        "incomingEdgesCount",
        "includesCount",
        "referencesCount",
        "totalEdgesCount",
        "bulletLists",
        "orderedLists",
        "codeBlocks",
        "tables",
        "quotes",
    ];

    for expected in &expected_headers {
        assert!(
            header.contains(expected),
            "Header should contain '{}', but got: {}",
            expected,
            header
        );
    }

    assert!(stdout.contains("test,"), "Should contain test document");
    assert!(
        stdout.contains("related,"),
        "Should contain related document"
    );
}

#[test]
fn test_stats_with_various_elements() {
    let temp_dir = setup_test_workspace_with_elements();
    let output = run_stats_command(&temp_dir, &["--format", "csv"]);

    assert!(output.status.success(), "Command should succeed");

    let stdout = String::from_utf8(output.stdout).expect("Valid UTF-8 output");
    let lines: Vec<&str> = stdout.lines().collect();

    let complex_line = lines
        .iter()
        .find(|line| line.starts_with("complex,"))
        .expect("Should find complex document in CSV");

    let parts: Vec<&str> = complex_line.split(',').collect();

    let bullet_lists = parts[12]
        .parse::<usize>()
        .expect("bullet_lists should be a number");
    let ordered_lists = parts[13]
        .parse::<usize>()
        .expect("ordered_lists should be a number");
    let code_blocks = parts[14]
        .parse::<usize>()
        .expect("code_blocks should be a number");
    let tables = parts[15]
        .parse::<usize>()
        .expect("tables should be a number");
    let quotes = parts[16]
        .parse::<usize>()
        .expect("quotes should be a number");

    assert!(bullet_lists > 0, "Should count bullet lists");
    assert!(ordered_lists > 0, "Should count ordered lists");
    assert!(code_blocks > 0, "Should count code blocks");
    assert!(tables > 0, "Should count tables");
    assert!(quotes > 0, "Should count quotes");
}

#[test]
fn test_stats_broken_links_local_included() {
    let temp_dir = setup_test_workspace_with_broken_links();
    let output = run_stats_command(&temp_dir, &[]);

    assert!(output.status.success(), "Command should succeed");

    let stdout = String::from_utf8(output.stdout).expect("Valid UTF-8 output");

    assert!(
        stdout.contains("## Broken Links"),
        "Should contain Broken Links section"
    );
    assert!(
        stdout.contains("-> nonexistent"),
        "Should report broken link to nonexistent document"
    );
    assert!(
        stdout.contains("-> also-missing"),
        "Should report broken block reference to also-missing document"
    );
}

#[test]
fn test_stats_broken_links_external_excluded() {
    let temp_dir = setup_test_workspace_with_broken_links();
    let output = run_stats_command(&temp_dir, &[]);

    assert!(output.status.success(), "Command should succeed");

    let stdout = String::from_utf8(output.stdout).expect("Valid UTF-8 output");

    assert!(
        !stdout.contains("https://example.com"),
        "Should not report external links as broken"
    );
    assert!(
        !stdout.contains("http://example.org"),
        "Should not report external links as broken"
    );
}

fn setup_test_workspace_with_broken_links() -> TempDir {
    let temp_dir = TempDir::new().expect("Failed to create temp directory");
    let temp_path = temp_dir.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 to TOML");
    write(temp_path.join(".iwe/config.toml"), config_content).expect("Failed to write config file");

    let doc_with_broken_inline = indoc! {"
        # Document With Links

        This has a [broken link](nonexistent) and an [external link](https://example.com).

        Also links to [another external](http://example.org) site.
    "};

    write(temp_path.join("doc-with-links.md"), doc_with_broken_inline)
        .expect("Failed to write doc file");

    let doc_with_broken_block = indoc! {"
        # Document With Block Ref

        Some content here.

        [also-missing](also-missing)
    "};

    write(
        temp_path.join("doc-with-block-ref.md"),
        doc_with_broken_block,
    )
    .expect("Failed to write doc file");

    temp_dir
}

fn setup_test_workspace() -> TempDir {
    let temp_dir = TempDir::new().expect("Failed to create temp directory");
    let temp_path = temp_dir.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 to TOML");
    write(temp_path.join(".iwe/config.toml"), config_content).expect("Failed to write config file");

    let test_content = indoc! {"
        # Test Document

        This is a test document with multiple sections.

        ## Section 1

        Some content here with a [link to related](related).

        ## Section 2

        More content with another paragraph.

        ### Subsection 2.1

        Nested content.
    "};

    write(temp_path.join("test.md"), test_content).expect("Failed to write test file");

    let related_content = indoc! {"
        # Related Document

        This document is related to the [Test Document](test).

        ## Details

        Some additional details here.
    "};

    write(temp_path.join("related.md"), related_content).expect("Failed to write related file");

    temp_dir
}

fn setup_test_workspace_with_elements() -> TempDir {
    let temp_dir = TempDir::new().expect("Failed to create temp directory");
    let temp_path = temp_dir.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 to TOML");
    write(temp_path.join(".iwe/config.toml"), config_content).expect("Failed to write config file");

    let complex_content = indoc! {"
        # Complex Document

        This document contains various markdown elements.

        ## Lists Section

        Here's a bullet list:

        - Item 1
        - Item 2
        - Item 3

        And an ordered list:

        1. First
        2. Second
        3. Third

        ## Code Section

        Here's some code:

        ```rust
        fn main() {
            println!(\"Hello, world!\");
        }
        ```

        ## Table Section

        | Column 1 | Column 2 |
        |----------|----------|
        | Data 1   | Data 2   |
        | Data 3   | Data 4   |

        ## Quote Section

        > This is a quote
        > with multiple lines
    "};

    write(temp_path.join("complex.md"), complex_content).expect("Failed to write complex file");

    temp_dir
}

#[test]
fn test_stats_broken_inline_links_in_table() {
    let temp_dir = TempDir::new().expect("Failed to create temp directory");
    let temp_path = temp_dir.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 to TOML");
    write(temp_path.join(".iwe/config.toml"), config_content).expect("Failed to write config file");

    let doc = indoc! {"
        # Document With Table

        | Name | Link |
        |------|------|
        | A    | [missing](nonexistent) |
        | B    | [valid](existing) |

        This paragraph has a [broken link](also-missing) after the table.
    "};

    write(temp_path.join("doc.md"), doc).expect("Failed to write doc file");
    write(temp_path.join("existing.md"), "# Existing\n").expect("Failed to write existing file");

    let output = run_stats_command(&temp_dir, &[]);
    assert!(output.status.success(), "Command should succeed");

    let stdout = String::from_utf8(output.stdout).expect("Valid UTF-8 output");

    assert!(
        stdout.contains("-> nonexistent"),
        "Should detect broken inline link inside table cell"
    );
    assert!(
        stdout.contains("-> also-missing"),
        "Should detect broken inline link after the table"
    );
    assert!(
        !stdout.contains("-> existing"),
        "Should not report valid link as broken"
    );
}

#[test]
fn test_stats_per_doc_default_format_outputs_markdown() {
    let temp_dir = setup_test_workspace();
    let output = run_stats_command(&temp_dir, &["-k", "test"]);

    assert!(
        output.status.success(),
        "Should succeed with default format"
    );
    let stdout = String::from_utf8(output.stdout).expect("Valid UTF-8 output");
    let expected = indoc! {"
        # Test Document

        - **Key:** test
        - **Sections:** 4
        - **Paragraphs:** 4
        - **Lines:** 15
        - **Words:** 35
        - **Included by:** 0
        - **Referenced by:** 1
        - **Incoming edges:** 1
        - **Includes:** 0
        - **References:** 1
        - **Total edges:** 2
        - **Bullet lists:** 0
        - **Ordered lists:** 0
        - **Code blocks:** 0
        - **Tables:** 0
        - **Quotes:** 0
    "};
    assert_eq!(stdout, expected);
}

#[test]
fn test_stats_per_doc_csv_format() {
    let temp_dir = setup_test_workspace();
    let output = run_stats_command(&temp_dir, &["-k", "test", "-f", "csv"]);

    assert!(output.status.success(), "Should succeed with csv format");
    let stdout = String::from_utf8(output.stdout).expect("Valid UTF-8 output");
    let expected = "key,title,sections,paragraphs,lines,words,includedByCount,referencedByCount,incomingEdgesCount,includesCount,referencesCount,totalEdgesCount,bulletLists,orderedLists,codeBlocks,tables,quotes\ntest,Test Document,4,4,15,35,0,1,1,0,1,2,0,0,0,0,0\n";
    assert_eq!(stdout, expected);
}

fn run_stats_command(temp_dir: &TempDir, args: &[&str]) -> std::process::Output {
    let binary_path = crate::common::get_iwe_binary_path();

    let mut cmd = Command::new(binary_path);
    cmd.current_dir(temp_dir.path()).arg("stats");

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

    cmd.output().expect("Failed to execute stats command")
}
