use std::sync::Once;

use indoc::indoc;
use liwe::graph::Graph;
use liwe::markdown::MarkdownReader;
use liwe::model::config::{FormattingOptions, LineBreakStyle, MarkdownOptions};
use pretty_assertions::assert_str_eq;

#[test]
fn default_emphasis_uses_asterisk() {
    compare(
        indoc! {"
        *italic*
        "},
        "*italic*",
        FormattingOptions::default(),
    );
}

#[test]
fn underscore_emphasis() {
    compare(
        indoc! {"
        _italic_
        "},
        "*italic*",
        FormattingOptions {
            emphasis_token: Some("_".into()),
            ..Default::default()
        },
    );
}

#[test]
fn default_strong_uses_double_asterisk() {
    compare(
        indoc! {"
        **bold**
        "},
        "**bold**",
        FormattingOptions::default(),
    );
}

#[test]
fn underscore_strong() {
    compare(
        indoc! {"
        __bold__
        "},
        "**bold**",
        FormattingOptions {
            strong_token: Some("__".into()),
            ..Default::default()
        },
    );
}

#[test]
fn default_list_uses_dash() {
    compare(
        indoc! {"
        - item1
        - item2
        "},
        indoc! {"
        - item1
        - item2
        "},
        FormattingOptions::default(),
    );
}

#[test]
fn asterisk_list() {
    compare(
        indoc! {"
        * item1
        * item2
        "},
        indoc! {"
        - item1
        - item2
        "},
        FormattingOptions {
            list_token: Some("*".into()),
            ..Default::default()
        },
    );
}

#[test]
fn plus_list() {
    compare(
        indoc! {"
        + item1
        + item2
        "},
        indoc! {"
        - item1
        - item2
        "},
        FormattingOptions {
            list_token: Some("+".into()),
            ..Default::default()
        },
    );
}

#[test]
fn custom_rule_token() {
    compare(
        indoc! {"
        *****
        "},
        "---",
        FormattingOptions {
            rule_token: Some("*".into()),
            rule_token_count: Some(5),
            ..Default::default()
        },
    );
}

#[test]
fn default_rule_token() {
    compare(
        &format!("{}\n", "-".repeat(72)),
        "---",
        FormattingOptions::default(),
    );
}

#[test]
fn ordered_list_with_dot() {
    compare(
        indoc! {"
        1. item1
        2. item2
        "},
        indoc! {"
        1. item1
        2. item2
        "},
        FormattingOptions::default(),
    );
}

#[test]
fn ordered_list_with_paren() {
    compare(
        indoc! {"
        1) item1
        2) item2
        "},
        indoc! {"
        1. item1
        2. item2
        "},
        FormattingOptions {
            ordered_list_token: Some(")".into()),
            ..Default::default()
        },
    );
}

#[test]
fn increment_ordered_list_bullets_true() {
    compare(
        indoc! {"
        1. item1
        2. item2
        3. item3
        "},
        indoc! {"
        1. item1
        1. item2
        1. item3
        "},
        FormattingOptions::default(),
    );
}

#[test]
fn increment_ordered_list_bullets_false() {
    compare(
        indoc! {"
        1. item1
        1. item2
        1. item3
        "},
        indoc! {"
        1. item1
        2. item2
        3. item3
        "},
        FormattingOptions {
            increment_ordered_list_bullets: Some(false),
            ..Default::default()
        },
    );
}

#[test]
fn code_block_with_backtick() {
    compare(
        indoc! {"
        ``` rust
        fn main() {}
        ```
        "},
        indoc! {"
        ```rust
        fn main() {}
        ```
        "},
        FormattingOptions::default(),
    );
}

#[test]
fn code_block_with_tilde() {
    compare(
        indoc! {"
        ~~~ rust
        fn main() {}
        ~~~
        "},
        indoc! {"
        ```rust
        fn main() {}
        ```
        "},
        FormattingOptions {
            code_block_token: Some("~".into()),
            ..Default::default()
        },
    );
}

#[test]
fn code_block_with_custom_count() {
    compare(
        indoc! {"
        ```` rust
        fn main() {}
        ````
        "},
        indoc! {"
        ```rust
        fn main() {}
        ```
        "},
        FormattingOptions {
            code_block_token_count: Some(4),
            ..Default::default()
        },
    );
}

#[test]
fn rule_with_underscore() {
    compare(
        indoc! {"
        ___
        "},
        "---",
        FormattingOptions {
            rule_token: Some("_".into()),
            rule_token_count: Some(3),
            ..Default::default()
        },
    );
}

#[test]
fn all_custom_tokens() {
    let input = indoc! {"
        # Header

        *italic* and **bold**

        + item1
        + item2

        ---
    "};

    let formatting = FormattingOptions {
        emphasis_token: Some("_".into()),
        strong_token: Some("__".into()),
        list_token: Some("+".into()),
        rule_token: Some("*".into()),
        rule_token_count: Some(3),
        ..Default::default()
    };

    let result = normalize_with(input, formatting);

    assert!(result.contains("_italic_"), "expected underscore emphasis");
    assert!(result.contains("__bold__"), "expected underscore strong");
    assert!(result.contains("+ item1"), "expected plus list token");
    assert!(result.contains("***"), "expected asterisk rule");
}

#[test]
fn invalid_emphasis_token_falls_back_to_default() {
    let formatting = FormattingOptions {
        emphasis_token: Some("~~".into()),
        ..Default::default()
    }
    .validated();

    assert_eq!(formatting.emphasis_token(), "*");
}

#[test]
fn invalid_strong_token_falls_back_to_default() {
    let formatting = FormattingOptions {
        strong_token: Some("***".into()),
        ..Default::default()
    }
    .validated();

    assert_eq!(formatting.strong_token(), "**");
}

#[test]
fn invalid_list_token_falls_back_to_default() {
    let formatting = FormattingOptions {
        list_token: Some(">".into()),
        ..Default::default()
    }
    .validated();

    assert_eq!(formatting.list_token(), "-");
}

#[test]
fn invalid_code_block_token_count_falls_back_to_default() {
    let formatting = FormattingOptions {
        code_block_token_count: Some(1),
        ..Default::default()
    }
    .validated();

    assert_eq!(formatting.code_block_token_count(), 3);
}

#[test]
fn invalid_rule_token_count_falls_back_to_default() {
    let formatting = FormattingOptions {
        rule_token_count: Some(2),
        ..Default::default()
    }
    .validated();

    assert_eq!(formatting.rule_token_count(), 72);
}

#[test]
fn valid_values_preserved_after_validation() {
    let formatting = FormattingOptions {
        emphasis_token: Some("_".into()),
        strong_token: Some("__".into()),
        list_token: Some("+".into()),
        ordered_list_token: Some(")".into()),
        code_block_token: Some("~".into()),
        code_block_token_count: Some(4),
        increment_ordered_list_bullets: Some(true),
        ordered_list_content_indent: Some(4),
        bullet_list_content_indent: Some(4),
        rule_token: Some("*".into()),
        rule_token_count: Some(5),
        wrap_column: Some(80),
        preserve_line_breaks: Some(true),
        line_break_style: Some(LineBreakStyle::Spaces),
        preserve_newlines: Some(true),
    }
    .validated();

    assert_eq!(formatting.emphasis_token(), "_");
    assert_eq!(formatting.strong_token(), "__");
    assert_eq!(formatting.list_token(), "+");
    assert_eq!(formatting.ordered_list_token_char(), ')');
    assert_eq!(formatting.code_block_token_char(), '~');
    assert_eq!(formatting.code_block_token_count(), 4);
    assert_eq!(formatting.increment_ordered_list_bullets(), true);
    assert_eq!(formatting.ordered_list_content_indent(), Some(4));
    assert_eq!(formatting.bullet_list_content_indent(), Some(4));
    assert_eq!(formatting.rule_token(), "*");
    assert_eq!(formatting.rule_token_count(), 5);
    assert_eq!(formatting.wrap_column(), Some(80));
    assert_eq!(formatting.preserve_line_breaks(), true);
    assert_eq!(formatting.line_break_marker(), "  \n");
    assert_eq!(formatting.preserve_newlines(), true);
}

#[test]
fn preserve_line_breaks_round_trips_backslash() {
    compare(
        indoc! {"
        first line\\
        second line
        "},
        "first line\\\nsecond line",
        FormattingOptions {
            preserve_line_breaks: Some(true),
            ..Default::default()
        },
    );
}

#[test]
fn preserve_line_breaks_converts_spaces_to_backslash() {
    compare(
        indoc! {"
        first line\\
        second line
        "},
        "first line  \nsecond line",
        FormattingOptions {
            preserve_line_breaks: Some(true),
            ..Default::default()
        },
    );
}

#[test]
fn preserve_line_breaks_emits_two_spaces_when_configured() {
    let expected = indoc! {"
        first line<SP><SP>
        second line
    "}
    .replace("<SP>", " ");
    compare(
        &expected,
        "first line\\\nsecond line",
        FormattingOptions {
            preserve_line_breaks: Some(true),
            line_break_style: Some(LineBreakStyle::Spaces),
            ..Default::default()
        },
    );
}

#[test]
fn soft_breaks_join_with_space_by_default() {
    compare(
        "first line second line\n",
        indoc! {"
        first line
        second line
        "},
        FormattingOptions::default(),
    );
}

#[test]
fn preserve_newlines_keeps_soft_breaks() {
    compare(
        indoc! {"
        first line
        second line
        "},
        indoc! {"
        first line
        second line
        "},
        FormattingOptions {
            preserve_newlines: Some(true),
            ..Default::default()
        },
    );
}

#[test]
fn preserve_newlines_collapses_blank_lines_into_paragraphs() {
    compare(
        indoc! {"
        first line
        second line

        third line
        "},
        indoc! {"
        first line
        second line

        third line
        "},
        FormattingOptions {
            preserve_newlines: Some(true),
            ..Default::default()
        },
    );
}

#[test]
fn preserve_newlines_is_idempotent() {
    let formatting = FormattingOptions {
        preserve_newlines: Some(true),
        ..Default::default()
    };
    let input = indoc! {"
        first line
        second line
        third line
    "};
    let once = normalize_with(input, formatting.clone());
    let twice = normalize_with(&once, formatting);

    assert_str_eq!(once, twice);
}

#[test]
fn hard_breaks_reflow_to_space_when_preserve_disabled() {
    compare(
        "first line second line\n",
        "first line\\\nsecond line",
        FormattingOptions::default(),
    );
}

#[test]
fn wrap_column_wraps_long_paragraph() {
    compare(
        indoc! {"
        alpha beta gamma delta epsilon
        zeta eta theta iota kappa
        lambda mu
        "},
        "alpha beta gamma delta epsilon zeta eta theta iota kappa lambda mu",
        FormattingOptions {
            wrap_column: Some(30),
            ..Default::default()
        },
    );
}

#[test]
fn wrap_column_wraps_each_segment_between_preserved_breaks() {
    compare(
        indoc! {"
        alpha beta gamma delta epsilon\\
        zeta eta theta iota kappa lambda
        "},
        "alpha beta gamma delta epsilon\\\nzeta eta theta iota kappa lambda",
        FormattingOptions {
            wrap_column: Some(40),
            preserve_line_breaks: Some(true),
            ..Default::default()
        },
    );
}

#[test]
fn wrap_column_disabled_returns_input() {
    compare(
        "alpha beta gamma delta epsilon zeta\n",
        "alpha beta gamma delta epsilon zeta",
        FormattingOptions::default(),
    );
}

#[test]
fn wrap_column_wraps_link_text_keeps_url_atomic() {
    compare(
        indoc! {"
        see [the quick brown
        fox](http://example.com)
        end
        "},
        "see [the quick brown fox](http://example.com) end",
        FormattingOptions {
            wrap_column: Some(20),
            ..Default::default()
        },
    );
}

#[test]
fn wrap_column_keeps_inline_code_atomic() {
    compare(
        indoc! {"
        alpha
        `code with spaces`
        end
        "},
        "alpha `code with spaces` end",
        FormattingOptions {
            wrap_column: Some(20),
            ..Default::default()
        },
    );
}

#[test]
fn wrap_column_keeps_wiki_link_atomic() {
    compare(
        indoc! {"
        alpha [[wiki link]]
        end
        "},
        "alpha [[wiki link]] end",
        FormattingOptions {
            wrap_column: Some(20),
            ..Default::default()
        },
    );
}

#[test]
fn wrap_column_wraps_image_alt_keeps_url_atomic() {
    compare(
        indoc! {"
        see ![alt text
        here](image.png) end
        "},
        "see ![alt text here](image.png) end",
        FormattingOptions {
            wrap_column: Some(20),
            ..Default::default()
        },
    );
}

#[test]
fn wrap_column_long_token_sits_alone() {
    compare(
        indoc! {"
        alpha
        beta-very-long-token-that-cannot-fit
        end
        "},
        "alpha beta-very-long-token-that-cannot-fit end",
        FormattingOptions {
            wrap_column: Some(20),
            ..Default::default()
        },
    );
}

#[test]
fn wrap_column_wraps_inside_bullet_list() {
    compare(
        indoc! {"
        - alpha beta gamma delta
          epsilon zeta eta theta iota
          kappa lambda mu
        "},
        "- alpha beta gamma delta epsilon zeta eta theta iota kappa lambda mu",
        FormattingOptions {
            wrap_column: Some(30),
            ..Default::default()
        },
    );
}

#[test]
fn invalid_content_indent_falls_back_to_default() {
    let formatting = FormattingOptions {
        ordered_list_content_indent: Some(1),
        bullet_list_content_indent: Some(9),
        ..Default::default()
    }
    .validated();

    assert_eq!(formatting.ordered_list_content_indent(), None);
    assert_eq!(formatting.bullet_list_content_indent(), None);
}

#[test]
fn bullet_list_content_indent_four_spaces() {
    compare(
        indoc! {"
        -   item 1

            ``` text
            some config
            ```

        -   item 2
        "},
        indoc! {"
        - item 1

          ``` text
          some config
          ```
        - item 2
        "},
        FormattingOptions {
            bullet_list_content_indent: Some(4),
            ..Default::default()
        },
    );
}

#[test]
fn ordered_list_content_indent_four_spaces() {
    compare(
        indoc! {"
        1.  item 1

            ``` text
            some config
            ```

        2.  item 2
        "},
        indoc! {"
        1. item 1

           ``` text
           some config
           ```
        2. item 2
        "},
        FormattingOptions {
            ordered_list_content_indent: Some(4),
            ..Default::default()
        },
    );
}

#[test]
fn bullet_list_content_indent_nested() {
    compare(
        indoc! {"
        -   item 1
            -   nested 1
            -   nested 2
        "},
        indoc! {"
        - item 1
          - nested 1
          - nested 2
        "},
        FormattingOptions {
            bullet_list_content_indent: Some(4),
            ..Default::default()
        },
    );
}

#[test]
fn content_indent_is_idempotent() {
    let formatting = FormattingOptions {
        ordered_list_content_indent: Some(4),
        bullet_list_content_indent: Some(4),
        ..Default::default()
    };
    let once = normalize_with(
        indoc! {"
        - item 1

          ``` text
          some config
          ```
        - item 2

        1. step 1

           ``` text
           more config
           ```
        2. step 2
        "},
        formatting.clone(),
    );
    let twice = normalize_with(&once, formatting);

    assert_str_eq!(once, twice);
}

fn compare(expected: &str, input: &str, formatting: FormattingOptions) {
    setup();

    let mut graph = Graph::new_with_options(MarkdownOptions {
        formatting,
        ..Default::default()
    });

    graph.from_markdown("key".into(), input, MarkdownReader::new());

    let normalized = graph.to_markdown(&"key".into());

    println!("{:#?}", graph);
    println!("{}", expected);
    println!("normalized:");
    println!("{}", normalized);

    assert_str_eq!(expected, normalized);
}

fn normalize_with(input: &str, formatting: FormattingOptions) -> String {
    setup();

    let mut graph = Graph::new_with_options(MarkdownOptions {
        formatting,
        ..Default::default()
    });

    graph.from_markdown("key".into(), input, MarkdownReader::new());
    graph.to_markdown(&"key".into())
}

static INIT: Once = Once::new();

fn setup() {
    INIT.call_once(|| {
        let _ = env_logger::builder().try_init();
    });
}
