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_tree_default_format_is_markdown() {
    let temp_dir = setup_workspace_with_linked_documents();
    let temp_path = temp_dir.path();

    let output = run_tree_command(temp_path, &[]);
    assert!(output.status.success(), "Tree command should succeed");

    let stdout = String::from_utf8(output.stdout).expect("Valid UTF-8 output");
    let expected = indoc! {"
        - [Main Document](main)
          - [Child Document](child)
    "};

    assert_eq!(stdout, expected);
}

#[test]
fn test_tree_markdown_format() {
    let temp_dir = setup_workspace_with_linked_documents();
    let temp_path = temp_dir.path();

    let output = run_tree_command(temp_path, &["-f", "markdown"]);
    assert!(output.status.success(), "Tree command should succeed");

    let stdout = String::from_utf8(output.stdout).expect("Valid UTF-8 output");
    let expected = indoc! {"
        - [Main Document](main)
          - [Child Document](child)
    "};

    assert_eq!(stdout, expected);
}

#[test]
fn test_tree_json_format() {
    let temp_dir = setup_workspace_with_linked_documents();
    let temp_path = temp_dir.path();

    let output = run_tree_command(temp_path, &["-f", "json"]);
    assert!(output.status.success(), "Tree command should succeed");

    let stdout = String::from_utf8(output.stdout).expect("Valid UTF-8 output");
    let expected = indoc! {r#"
        [
          {
            "key": "main",
            "title": "Main Document",
            "children": [
              {
                "key": "child",
                "title": "Child Document",
                "children": []
              }
            ]
          }
        ]
    "#};

    assert_eq!(stdout, expected);
}

#[test]
fn test_tree_yaml_format() {
    let temp_dir = setup_workspace_with_linked_documents();
    let temp_path = temp_dir.path();

    let output = run_tree_command(temp_path, &["-f", "yaml"]);
    assert!(output.status.success(), "Tree command should succeed");

    let stdout = String::from_utf8(output.stdout).expect("Valid UTF-8 output");
    let expected = indoc! {"
        - key: main
          title: Main Document
          children:
          - key: child
            title: Child Document
            children: []
    "};

    assert_eq!(stdout, expected);
}

#[test]
fn test_tree_keys_format() {
    let temp_dir = setup_workspace_with_linked_documents();
    let temp_path = temp_dir.path();

    let output = run_tree_command(temp_path, &["-f", "keys"]);
    assert!(output.status.success(), "Tree command should succeed");

    let stdout = String::from_utf8(output.stdout).expect("Valid UTF-8 output");
    let expected = indoc! {"
        main
        	child
    "};

    assert_eq!(stdout, expected);
}

#[test]
fn test_tree_with_key_filter() {
    let temp_dir = setup_workspace_with_multiple_roots();
    let temp_path = temp_dir.path();

    let output = run_tree_command(temp_path, &["-k", "root-a", "-f", "keys"]);
    assert!(output.status.success(), "Tree command should succeed");

    let stdout = String::from_utf8(output.stdout).expect("Valid UTF-8 output");
    let expected = indoc! {"
        root-a
    "};

    assert_eq!(stdout, expected);
}

#[test]
fn test_tree_with_multiple_key_filters() {
    let temp_dir = setup_workspace_with_multiple_roots();
    let temp_path = temp_dir.path();

    let output = run_tree_command(temp_path, &["-k", "root-a", "-k", "root-b", "-f", "keys"]);
    assert!(output.status.success(), "Tree command should succeed");

    let stdout = String::from_utf8(output.stdout).expect("Valid UTF-8 output");
    let expected = indoc! {"
        root-a
        root-b
    "};

    assert_eq!(stdout, expected);
}

#[test]
fn test_tree_with_depth_limit() {
    let temp_dir = setup_workspace_with_deep_nesting();
    let temp_path = temp_dir.path();

    let output = run_tree_command(temp_path, &["--depth", "2", "-f", "keys"]);
    assert!(output.status.success(), "Tree command should succeed");

    let stdout = String::from_utf8(output.stdout).expect("Valid UTF-8 output");
    let expected = indoc! {"
        level1
        	level2
    "};

    assert_eq!(stdout, expected);
}

#[test]
fn test_tree_invalid_key() {
    let temp_dir = setup_workspace_with_linked_documents();
    let temp_path = temp_dir.path();

    let output = run_tree_command(temp_path, &["-k", "nonexistent-doc"]);

    assert!(
        !output.status.success(),
        "Tree should fail with invalid key"
    );

    let stderr = String::from_utf8(output.stderr).expect("Valid UTF-8 stderr");
    let expected = "Error: Document 'nonexistent-doc' not found\n";

    assert_eq!(stderr, expected);
}

#[test]
fn test_tree_empty_workspace() {
    let temp_dir = setup_empty_workspace();
    let temp_path = temp_dir.path();

    let output = run_tree_command(temp_path, &[]);
    assert!(
        output.status.success(),
        "Tree should succeed with empty workspace"
    );

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

    assert_eq!(stdout, "");
}

#[test]
fn test_tree_multiple_roots() {
    let temp_dir = setup_workspace_with_multiple_roots();
    let temp_path = temp_dir.path();

    let output = run_tree_command(temp_path, &["-f", "markdown"]);
    assert!(output.status.success(), "Tree command should succeed");

    let stdout = String::from_utf8(output.stdout).expect("Valid UTF-8 output");
    let expected = indoc! {"
        - [Root A](root-a)
        - [Root B](root-b)
    "};

    assert_eq!(stdout, expected);
}

#[test]
fn test_tree_deep_nesting() {
    let temp_dir = setup_workspace_with_deep_nesting();
    let temp_path = temp_dir.path();

    let output = run_tree_command(temp_path, &["-f", "markdown"]);
    assert!(output.status.success(), "Tree command should succeed");

    let stdout = String::from_utf8(output.stdout).expect("Valid UTF-8 output");
    let expected = indoc! {"
        - [Level 1](level1)
          - [Level 2](level2)
            - [Level 3](level3)
              - [Level 4](level4)
    "};

    assert_eq!(stdout, expected);
}

#[test]
fn test_tree_shared_child_multiple_parents() {
    let temp_dir = setup_workspace_with_shared_child();
    let temp_path = temp_dir.path();

    let output = run_tree_command(temp_path, &["-f", "markdown"]);
    assert!(output.status.success(), "Tree command should succeed");

    let stdout = String::from_utf8(output.stdout).expect("Valid UTF-8 output");
    let expected = indoc! {"
        - [Parent A](parent-a)
          - [Shared Child](shared-child)
        - [Parent B](parent-b)
          - [Shared Child](shared-child)
    "};

    assert_eq!(stdout, expected);
}

#[test]
fn test_tree_circular_inclusion_no_roots() {
    let temp_dir = setup_workspace_with_circular_inclusion();
    let temp_path = temp_dir.path();

    let output = run_tree_command(temp_path, &["-f", "keys"]);
    assert!(output.status.success(), "Tree command should succeed");

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

    assert_eq!(stdout, expected);
}

#[test]
fn test_tree_circular_inclusion_with_key_filter() {
    let temp_dir = setup_workspace_with_circular_inclusion();
    let temp_path = temp_dir.path();

    let output = run_tree_command(temp_path, &["-k", "doc-a", "-f", "keys"]);
    assert!(output.status.success(), "Tree command should succeed");

    let stdout = String::from_utf8(output.stdout).expect("Valid UTF-8 output");
    let expected = indoc! {"
        doc-a
        	doc-b
        		doc-c
        			doc-a
    "};

    assert_eq!(stdout, expected);
}

#[test]
fn test_tree_self_referencing_document() {
    let temp_dir = setup_workspace_with_self_reference();
    let temp_path = temp_dir.path();

    let output = run_tree_command(temp_path, &["-f", "keys"]);
    assert!(output.status.success(), "Tree command should succeed");

    let stdout = String::from_utf8(output.stdout).expect("Valid UTF-8 output");
    let expected = indoc! {"
        self-ref
    "};

    assert_eq!(stdout, expected);
}

#[test]
fn test_tree_project_user_frontmatter() {
    let temp_dir = TempDir::new().expect("Failed to create temp directory");
    let temp_path = temp_dir.path();

    setup_iwe_config(temp_path);

    let parent = indoc! {"
        ---
        pillar: ai-memory
        status: published
        ---

        # Parent

        [child](child)
    "};
    let child = indoc! {"
        ---
        pillar: ai-memory
        status: draft
        ---

        # Child

        Child content.
    "};

    write(temp_path.join("parent.md"), parent).expect("Should write parent");
    write(temp_path.join("child.md"), child).expect("Should write child");

    let output = run_tree_command(
        temp_path,
        &["-k", "parent", "--project", "pillar,status", "-f", "json"],
    );
    assert!(output.status.success(), "Tree command should succeed");

    let stdout = String::from_utf8(output.stdout).expect("Valid UTF-8 output");
    let parsed: serde_json::Value = serde_json::from_str(&stdout).expect("valid JSON");
    let parent_node = &parsed[0];
    assert_eq!(parent_node["key"], "parent");
    assert_eq!(parent_node["title"], "Parent");
    assert_eq!(parent_node["pillar"], "ai-memory");
    assert_eq!(parent_node["status"], "published");
    let children = parent_node["children"]
        .as_array()
        .expect("children is array");
    assert_eq!(children.len(), 1);
    let child_node = &children[0];
    assert_eq!(child_node["pillar"], "ai-memory");
    assert_eq!(child_node["status"], "draft");
    assert!(child_node["children"].is_array(), "children always present");
}

#[test]
fn test_tree_project_pseudo_content() {
    let temp_dir = TempDir::new().expect("Failed to create temp directory");
    let temp_path = temp_dir.path();

    setup_iwe_config(temp_path);

    write(
        temp_path.join("root.md"),
        indoc! {"
            # Root

            [child](child)
        "},
    )
    .unwrap();
    write(
        temp_path.join("child.md"),
        indoc! {"
            # Child

            Child body.
        "},
    )
    .unwrap();

    let output = run_tree_command(
        temp_path,
        &["-k", "root", "--project", "body=$content", "-f", "json"],
    );
    assert!(output.status.success(), "Tree command should succeed");

    let stdout = String::from_utf8(output.stdout).expect("Valid UTF-8 output");
    let parsed: serde_json::Value = serde_json::from_str(&stdout).expect("valid JSON");
    let root = &parsed[0];
    assert_eq!(root["key"], "root");
    assert_eq!(root["title"], "Root");
    assert!(
        root["body"].as_str().unwrap_or_default().contains("# Root"),
        "expected body to contain root content, got: {:?}",
        root["body"]
    );
    assert!(root["children"].is_array());
    let child = &root["children"][0];
    assert!(child["body"]
        .as_str()
        .unwrap_or_default()
        .contains("# Child"));
}

#[test]
fn test_tree_add_fields_extends_default_user_fm() {
    let temp_dir = TempDir::new().expect("Failed to create temp directory");
    let temp_path = temp_dir.path();

    setup_iwe_config(temp_path);

    write(
        temp_path.join("doc.md"),
        indoc! {"
            ---
            status: draft
            ---
            # Doc
        "},
    )
    .unwrap();

    let output = run_tree_command(
        temp_path,
        &["-k", "doc", "--add-fields", "status", "-f", "json"],
    );
    assert!(output.status.success(), "Tree command should succeed");

    let stdout = String::from_utf8(output.stdout).expect("Valid UTF-8 output");
    let parsed: serde_json::Value = serde_json::from_str(&stdout).expect("valid JSON");
    assert_eq!(parsed[0]["key"], "doc");
    assert_eq!(parsed[0]["title"], "Doc");
    assert_eq!(parsed[0]["status"], "draft");
}

#[test]
fn test_tree_add_fields_edges_yaml() {
    let temp_dir = setup_workspace_with_linked_documents();
    let temp_path = temp_dir.path();

    let output = run_tree_command(
        temp_path,
        &[
            "-k",
            "child",
            "--add-fields",
            "parents=$includedBy",
            "-f",
            "yaml",
        ],
    );
    assert!(output.status.success(), "Tree command should succeed");

    let stdout = String::from_utf8(output.stdout).expect("Valid UTF-8 output");
    let expected = indoc! {"
        - key: child
          title: Child Document
          references: []
          includes: []
          referencedBy: []
          includedBy:
          - key: main
            title: Main Document
            sectionPath: []
          parents:
          - key: main
            title: Main Document
            sectionPath: []
          children: []
    "};

    assert_eq!(stdout, expected);
}

#[test]
fn test_tree_add_fields_cannot_overwrite_system_title() {
    let temp_dir = TempDir::new().expect("Failed to create temp directory");
    let temp_path = temp_dir.path();

    setup_iwe_config(temp_path);

    write(temp_path.join("doc.md"), "# Heading Title\n").unwrap();

    let output = run_tree_command(
        temp_path,
        &["-k", "doc", "--add-fields", "title=$key", "-f", "json"],
    );
    assert!(output.status.success(), "Tree command should succeed");

    let stdout = String::from_utf8(output.stdout).expect("Valid UTF-8 output");
    let expected = indoc! {r#"
        [
          {
            "key": "doc",
            "title": "Heading Title",
            "references": [],
            "includes": [],
            "referencedBy": [],
            "includedBy": [],
            "children": []
          }
        ]
    "#};

    assert_eq!(stdout, expected);
}

#[test]
fn test_tree_project_and_add_fields_conflict() {
    let temp_dir = TempDir::new().expect("Failed to create temp directory");
    let temp_path = temp_dir.path();

    setup_iwe_config(temp_path);

    write(temp_path.join("doc.md"), "# Doc").unwrap();

    let output = run_tree_command(
        temp_path,
        &["-k", "doc", "--project", "key", "--add-fields", "status"],
    );
    let stderr = String::from_utf8(output.stderr).expect("Valid UTF-8");
    assert!(!output.status.success(), "expected conflict error");
    assert!(
        stderr.contains("cannot be used with"),
        "unexpected stderr: {}",
        stderr
    );
}

fn setup_workspace_with_self_reference() -> TempDir {
    let temp_dir = TempDir::new().expect("Failed to create temp directory");
    let temp_path = temp_dir.path();

    setup_iwe_config(temp_path);

    let self_ref = indoc! {"
        # Self Reference

        This document references itself: [Self Reference](self-ref)
    "};

    write(temp_path.join("self-ref.md"), self_ref).expect("Should write self-ref");

    temp_dir
}

fn setup_workspace_with_circular_inclusion() -> TempDir {
    let temp_dir = TempDir::new().expect("Failed to create temp directory");
    let temp_path = temp_dir.path();

    setup_iwe_config(temp_path);

    let doc_a = indoc! {"
        # Doc A

        [Doc B](doc-b)
    "};

    let doc_b = indoc! {"
        # Doc B

        [Doc C](doc-c)
    "};

    let doc_c = indoc! {"
        # Doc C

        [Doc A](doc-a)
    "};

    write(temp_path.join("doc-a.md"), doc_a).expect("Should write doc-a");
    write(temp_path.join("doc-b.md"), doc_b).expect("Should write doc-b");
    write(temp_path.join("doc-c.md"), doc_c).expect("Should write doc-c");

    temp_dir
}

fn setup_workspace_with_shared_child() -> TempDir {
    let temp_dir = TempDir::new().expect("Failed to create temp directory");
    let temp_path = temp_dir.path();

    setup_iwe_config(temp_path);

    let parent_a = indoc! {"
        # Parent A

        [Shared Child](shared-child)
    "};

    let parent_b = indoc! {"
        # Parent B

        [Shared Child](shared-child)
    "};

    let shared_child = indoc! {"
        # Shared Child

        This document has multiple parents.
    "};

    write(temp_path.join("parent-a.md"), parent_a).expect("Should write parent-a");
    write(temp_path.join("parent-b.md"), parent_b).expect("Should write parent-b");
    write(temp_path.join("shared-child.md"), shared_child).expect("Should write shared-child");

    temp_dir
}

fn setup_workspace_with_linked_documents() -> TempDir {
    let temp_dir = TempDir::new().expect("Failed to create temp directory");
    let temp_path = temp_dir.path();

    setup_iwe_config(temp_path);

    let main_content = indoc! {"
        # Main Document

        Some content here.

        [Child Document](child)
    "};

    let child_content = indoc! {"
        # Child Document

        Child content here.
    "};

    write(temp_path.join("main.md"), main_content).expect("Should write main");
    write(temp_path.join("child.md"), child_content).expect("Should write child");

    temp_dir
}

fn setup_workspace_with_multiple_roots() -> TempDir {
    let temp_dir = TempDir::new().expect("Failed to create temp directory");
    let temp_path = temp_dir.path();

    setup_iwe_config(temp_path);

    let root_a = indoc! {"
        # Root A

        Content A.
    "};

    let root_b = indoc! {"
        # Root B

        Content B.
    "};

    write(temp_path.join("root-a.md"), root_a).expect("Should write root-a");
    write(temp_path.join("root-b.md"), root_b).expect("Should write root-b");

    temp_dir
}

fn setup_workspace_with_deep_nesting() -> TempDir {
    let temp_dir = TempDir::new().expect("Failed to create temp directory");
    let temp_path = temp_dir.path();

    setup_iwe_config(temp_path);

    let level1 = indoc! {"
        # Level 1

        [Level 2](level2)
    "};

    let level2 = indoc! {"
        # Level 2

        [Level 3](level3)
    "};

    let level3 = indoc! {"
        # Level 3

        [Level 4](level4)
    "};

    let level4 = indoc! {"
        # Level 4

        Final level.
    "};

    write(temp_path.join("level1.md"), level1).expect("Should write level1");
    write(temp_path.join("level2.md"), level2).expect("Should write level2");
    write(temp_path.join("level3.md"), level3).expect("Should write level3");
    write(temp_path.join("level4.md"), level4).expect("Should write level4");

    temp_dir
}

fn setup_empty_workspace() -> TempDir {
    let temp_dir = TempDir::new().expect("Failed to create temp directory");
    let temp_path = temp_dir.path();

    setup_iwe_config(temp_path);

    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 to TOML");

    write(temp_path.join(".iwe").join("config.toml"), config_content)
        .expect("Should write config file");
}

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

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

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