// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2026 The Gleam contributors

use std::cmp::Ordering::{Equal, Greater, Less};
use std::collections::HashMap;

use parser::Error;

use super::{
    Identifier::{AlphaNumeric, Numeric},
    *,
};

// Tests adapted from the tests for Elixir's version module

macro_rules! version_parse_test {
    ($name:ident, $input:expr, $major:expr, $minor:expr, $patch:expr,$pre:expr, $build:expr) => {
        #[test]
        fn $name() {
            assert_eq!(
                Version::parse($input).unwrap(),
                Version {
                    major: $major,
                    minor: $minor,
                    patch: $patch,
                    pre: $pre,
                    build: $build
                }
            );
        }
    };

    ($name:ident, $input:expr, $major:expr, $minor:expr, $patch:expr, $pre:expr) => {
        #[test]
        fn $name() {
            assert_eq!(
                Version::parse($input).unwrap(),
                Version {
                    major: $major,
                    minor: $minor,
                    patch: $patch,
                    pre: $pre,
                    build: None
                }
            );
        }
    };

    ($name:ident, $input:expr, $major:expr, $minor:expr, $patch:expr) => {
        #[test]
        fn $name() {
            assert_eq!(
                Version::parse($input).unwrap(),
                Version {
                    major: $major,
                    minor: $minor,
                    patch: $patch,
                    pre: vec![],
                    build: None
                }
            );
        }
    };
}

macro_rules! version_parse_fail_test {
    ($name:ident, $input:expr) => {
        #[test]
        fn $name() {
            println!("{}", $input);
            Version::parse($input).unwrap_err();
        }
    };
}

macro_rules! version_parse_print {
    ($name:ident, $input:expr) => {
        #[test]
        fn $name() {
            assert_eq!($input, Version::parse($input).unwrap().to_string().as_str());
        }
    };
}

version_parse_test!(triplet, "1.2.3", 1, 2, 3);

version_parse_test!(
    build,
    "1.4.5+ignore",
    1,
    4,
    5,
    vec![],
    Some("ignore".to_string())
);

version_parse_test!(
    two_part_build,
    "0.0.1+sha.0702245",
    0,
    0,
    1,
    vec![],
    Some("sha.0702245".to_string())
);

version_parse_test!(
    pre,
    "1.4.5-6-g3318bd5",
    1,
    4,
    5,
    vec![AlphaNumeric("6-g3318bd5".to_string())]
);

version_parse_test!(
    multi_part_pre,
    "1.4.5-6.7.eight",
    1,
    4,
    5,
    vec![Numeric(6), Numeric(7), AlphaNumeric("eight".to_string())]
);

version_parse_test!(
    pre_and_build,
    "1.4.5-6-g3318bd5+ignore",
    1,
    4,
    5,
    vec![AlphaNumeric("6-g3318bd5".to_string())],
    Some("ignore".to_string())
);

version_parse_fail_test!(just_a_word, "foobar");

version_parse_fail_test!(just_major, "2");

version_parse_fail_test!(major_dor, "2.");

version_parse_fail_test!(major_minor, "2.3");

version_parse_fail_test!(major_minor_dot, "2.3.");

version_parse_fail_test!(triplet_dash, "2.3.0-");

version_parse_fail_test!(triplet_plus, "2.3.0+");

version_parse_fail_test!(triplet_dot, "2.3.0.");

version_parse_fail_test!(quad, "2.3.0.4");

version_parse_fail_test!(missing_minor, "2.3.-rc.1");

version_parse_fail_test!(missing_minor_with_dot, "2.3.+rc.1");

version_parse_fail_test!(zero_pre, "2.3.0-01");

version_parse_fail_test!(double_zero_pre, "2.3.00-1");

version_parse_fail_test!(double_zero, "2.3.00");

version_parse_fail_test!(leading_zero_minor, "2.03.0");

version_parse_fail_test!(leading_zero_major, "02.3.0");

version_parse_fail_test!(extra_whitespace, "0. 0.0");

version_parse_fail_test!(and_in_version, "0.1.0-andpre");

version_parse_print!(print_triplet, "1.100.1000");

version_parse_print!(print_pre, "1.100.4-dev");

version_parse_print!(print_pre_dot, "1.100.4-dev.1.r.t");

version_parse_print!(print_build, "1.100.4+dev.1.r.t");

version_parse_print!(print_pre_build, "1.100.4-ewfjhwefj.wefw.w.1.ff+dev.1.r.t");

macro_rules! parse_range_test {
    ($name:ident, $input:expr, $expected:expr) => {
        #[test]
        fn $name() {
            assert_eq!(Version::parse_range($input).unwrap(), $expected);
        }
    };
}

macro_rules! parse_range_fail_test {
    ($name:ident, $input:expr) => {
        #[test]
        fn $name() {
            Version::parse_range($input).unwrap_err();
        }
    };
}

fn v(a: u32, b: u32, c: u32) -> Version {
    Version::new(a, b, c)
}

fn v_(major: u32, minor: u32, patch: u32, pre: Vec<Identifier>, build: Option<String>) -> Version {
    Version {
        major,
        minor,
        patch,
        pre,
        build,
    }
}

type PubgrubRange = pubgrub::Range<Version>;

parse_range_test!(leading_space, " 1.2.3", PubgrubRange::singleton(v(1, 2, 3)));
parse_range_test!(
    trailing_space,
    "1.2.3 ",
    PubgrubRange::singleton(v(1, 2, 3))
);

parse_range_test!(eq_triplet, "== 1.2.3 ", PubgrubRange::singleton(v(1, 2, 3)));

parse_range_test!(
    eq_triplet_nospace,
    "==1.2.3 ",
    PubgrubRange::singleton(v(1, 2, 3))
);

parse_range_test!(
    neq_triplet,
    "!= 1.2.3",
    PubgrubRange::strictly_lower_than(v(1, 2, 3)).union(&PubgrubRange::higher_than(v(1, 2, 4)))
);

parse_range_test!(implicit_eq, "2.2.3", PubgrubRange::singleton(v(2, 2, 3)));

parse_range_test!(
    range_pre_build,
    "1.2.3-thing+oop",
    PubgrubRange::singleton(v_(
        1,
        2,
        3,
        vec![Identifier::AlphaNumeric("thing".to_string())],
        Some("oop".to_string())
    ))
);

parse_range_test!(
    and,
    "< 1.2.3 and > 1.0.1",
    PubgrubRange::strictly_lower_than(v(1, 2, 3))
        .intersection(&PubgrubRange::higher_than(v(1, 0, 2)))
);

parse_range_test!(
    or,
    "< 1.2.3 or > 1.0.1",
    PubgrubRange::strictly_lower_than(v(1, 2, 3)).union(&PubgrubRange::higher_than(v(1, 0, 2)))
);

parse_range_test!(gt, "> 1.0.0", PubgrubRange::higher_than(v(1, 0, 1)));
parse_range_test!(gt_eq, ">= 1.0.0", PubgrubRange::higher_than(v(1, 0, 0)));
parse_range_test!(lt, "< 1.0.0", PubgrubRange::strictly_lower_than(v(1, 0, 0)));
parse_range_test!(
    lt_eq,
    "<= 1.0.0",
    PubgrubRange::strictly_lower_than(v(1, 0, 1))
);

parse_range_test!(
    pessimistic_pair,
    "~> 2.2",
    PubgrubRange::higher_than(v(2, 2, 0))
        .intersection(&PubgrubRange::strictly_lower_than(v(3, 0, 0)))
);

parse_range_test!(
    pessimistic_triplet,
    "~> 4.6.5",
    PubgrubRange::higher_than(v(4, 6, 5))
        .intersection(&PubgrubRange::strictly_lower_than(v(4, 7, 0)))
);

parse_range_test!(
    pessimistic_triplet_pre,
    "~> 4.6.5-eee",
    PubgrubRange::higher_than(v_(
        4,
        6,
        5,
        vec![Identifier::AlphaNumeric("eee".to_string())],
        None,
    ))
    .intersection(&PubgrubRange::strictly_lower_than(v(4, 7, 0)))
);

parse_range_test!(
    pessimistic_triplet_build,
    "~> 4.6.5+eee",
    PubgrubRange::higher_than(v_(4, 6, 5, vec![], Some("eee".to_string())))
        .intersection(&PubgrubRange::strictly_lower_than(v(4, 7, 0)))
);

parse_range_test!(
    greater_or_pessimistic,
    "> 10.0.0 or ~> 3.0.0",
    PubgrubRange::higher_than(v(10, 0, 1)).union(
        &PubgrubRange::higher_than(v(3, 0, 0))
            .intersection(&PubgrubRange::strictly_lower_than(v(3, 1, 0)))
    )
);

parse_range_test!(
    pessimistic_or_pessimistic,
    "~> 1.0.0 or ~> 3.0.0",
    PubgrubRange::higher_than(v(1, 0, 0))
        .intersection(&PubgrubRange::strictly_lower_than(v(1, 1, 0)))
        .union(
            &PubgrubRange::higher_than(v(3, 0, 0))
                .intersection(&PubgrubRange::strictly_lower_than(v(3, 1, 0)))
        )
);

parse_range_test!(
    pessimistic_and_gt,
    "~> 0.6 and >= 0.6.16",
    PubgrubRange::higher_than(v(0, 6, 0))
        .intersection(&PubgrubRange::strictly_lower_than(v(1, 0, 0)))
        .intersection(&PubgrubRange::higher_than(v(0, 6, 16)))
);

parse_range_test!(
    pessimistic_and_gt_pre,
    "~> 1.0-pre and >= 1.0.0-pre.5",
    PubgrubRange::higher_than(v_(
        1,
        0,
        0,
        vec![Identifier::AlphaNumeric("pre".to_string())],
        None
    ))
    .intersection(&PubgrubRange::strictly_lower_than(v(2, 0, 0)))
    .intersection(&PubgrubRange::higher_than(v_(
        1,
        0,
        0,
        vec![
            Identifier::AlphaNumeric("pre".to_string()),
            Identifier::Numeric(5)
        ],
        None
    )))
);

parse_range_fail_test!(range_quad, "1.1.1.1");
parse_range_fail_test!(range_just_major, "1");
parse_range_fail_test!(range_just_major_minor, "1.1");
parse_range_fail_test!(alpha_component, "1.1.a");

parse_range_fail_test!(range_word, "foobar");
parse_range_fail_test!(range_major_dot, "2.");
parse_range_fail_test!(range_major_minor_dot, "2.3.");
parse_range_fail_test!(range_triplet_dash, "2.3.0-");
parse_range_fail_test!(range_triplet_plus, "2.3.0+");
parse_range_fail_test!(range_triplet_dot, "2.3.0.");
parse_range_fail_test!(range_dot_dash, "2.3.-rc.1");
parse_range_fail_test!(range_dot_plus, "2.3.+rc.1");
parse_range_fail_test!(range_zero_pre, "2.3.0-01");
parse_range_fail_test!(patch_zerozero_dash, "2.3.00-1");
parse_range_fail_test!(patch_zerozero, "2.3.00");
parse_range_fail_test!(minor_leading_zero, "2.03.0");
parse_range_fail_test!(major_leading_zero, "02.3.0");
parse_range_fail_test!(triplet_containing_whitespace, "0. 0.0");
parse_range_fail_test!(dash_amp, "0.1.0-&&pre");

parse_range_fail_test!(or_whitespace_before, "!= 1.2.3or == 1.0.1");
parse_range_fail_test!(or_whitespace_after, "!= 1.2.3 or== 1.0.1");
parse_range_fail_test!(and_whitespace_before, "!= 1.2.3and == 1.0.1");
parse_range_fail_test!(and_whitespace_after, "!= 1.2.3 and== 1.0.1");

parse_range_fail_test!(trailing_and, "1.1.1 and");
parse_range_fail_test!(trailing_or, "1.1.1 or");
parse_range_fail_test!(leading_and, "and 1.1.1");
parse_range_fail_test!(leading_or, "and 1.1.1");
parse_range_fail_test!(just_and, "and");
parse_range_fail_test!(just_or, "and");

parse_range_fail_test!(duplicate_eq, "== ==");
parse_range_fail_test!(just_eq, "==");
parse_range_fail_test!(empty, "");

parse_range_fail_test!(pessimistic_major, "~> 1");

macro_rules! assert_order {
    ($name:ident, $left:expr, $ord:expr, $right:expr) => {
        #[test]
        fn $name() {
            let left = Version::parse($left);
            let right = Version::parse($right);
            assert_eq!(left.cmp(&right), $ord)
        }
    };
}

assert_order!(ord_same, "1.0.0", Equal, "1.0.0");
assert_order!(ord_same_build_right, "1.0.0", Equal, "1.0.0+1");
assert_order!(ord_same_build_left, "1.0.0+1", Equal, "1.0.0");
assert_order!(ord_same_diff_build, "1.0.0+1", Equal, "1.0.0+2");

assert_order!(ord_diff_build, "1.0.0+2", Equal, "1.0.0+1");

assert_order!(ord_major_greater, "3.0.0", Greater, "2.0.0");
assert_order!(ord_major_lesser, "1.0.0", Less, "2.0.0");

assert_order!(ord_minor_greater, "1.1.0", Greater, "1.0.0");
assert_order!(ord_minor_lesser, "1.0.0", Less, "1.1.0");

assert_order!(ord_patch_greater, "1.0.1", Greater, "1.0.0");
assert_order!(ord_patch_lesser, "1.0.0", Less, "1.0.1");

assert_order!(ord_pre_smaller_than_zero, "1.0.0", Greater, "1.0.0-rc1");
assert_order!(ord_pre_smaller_than_zero_flip, "1.0.0-rc1", Less, "1.0.0");

assert_order!(ord_pre_rc1_2, "1.0.0-rc1", Less, "1.0.0-rc2");

#[test]
fn manifest_toml() {
    let manifest = toml::to_string(
        &vec![
            (
                "gleam_stdlib".to_string(),
                Version {
                    major: 0,
                    minor: 17,
                    patch: 1,
                    pre: vec![],
                    build: None,
                },
            ),
            (
                "thingy".to_string(),
                Version {
                    major: 0,
                    minor: 1,
                    patch: 0,
                    pre: vec![],
                    build: None,
                },
            ),
        ]
        .into_iter()
        .collect::<HashMap<String, Version>>(),
    )
    .unwrap();
    let expected1 = r#"thingy = "0.1.0"
gleam_stdlib = "0.17.1"
"#;
    let expected2 = r#"gleam_stdlib = "0.17.1"
thingy = "0.1.0"
"#;
    assert!(manifest == expected1 || manifest == expected2);
}

#[test]
fn missing_minor_has_correct_error_type() {
    assert_eq!(Version::parse("1"), Err(Error::MinorVersionMissing(1)))
}

#[test]
fn missing_patch_has_correct_error_type() {
    assert_eq!(Version::parse("1.2"), Err(Error::PatchVersionMissing(1, 2)))
}
