macro_rules! mktests {
    ($name:ident, $input:expr, $expected:expr) => {
        #[test]
        fn $name() {
            let actual = gix_validate::reference::name_partial_or_sanitize($input.as_bstr());
            assert_eq!(actual, $expected);
            assert!(gix_validate::reference::name_partial(actual.as_ref()).is_ok());
        }
    };
}

mod name_partial {
    mod valid {
        use bstr::ByteSlice;
        macro_rules! mktest {
            ($name:ident, $input:expr) => {
                #[test]
                fn $name() {
                    assert!(gix_validate::reference::name_partial($input.as_bstr()).is_ok())
                }
            };
        }

        mktest!(refs_path, b"refs/heads/main");
        mktests!(refs_path_san, b"refs/heads/main", "refs/heads/main");
        mktest!(main_worktree_pseudo_ref, b"main-worktree/HEAD");
        mktests!(
            main_worktree_pseudo_ref_san,
            b"main-worktree/HEAD",
            "main-worktree/HEAD"
        );
        mktest!(main_worktree_ref, b"main-worktree/refs/bisect/good");
        mktests!(
            main_worktree_ref_san,
            b"main-worktree/refs/bisect/good",
            "main-worktree/refs/bisect/good"
        );
        mktest!(other_worktree_pseudo_ref, b"worktrees/id/HEAD");
        mktests!(other_worktree_pseudo_ref_san, b"worktrees/id/HEAD", "worktrees/id/HEAD");
        mktest!(other_worktree_ref, b"worktrees/id/refs/bisect/good");
        mktests!(
            other_worktree_ref_san,
            b"worktrees/id/refs/bisect/good",
            "worktrees/id/refs/bisect/good"
        );
        mktest!(worktree_private_ref, b"refs/worktree/private");
        mktests!(
            worktree_private_ref_san,
            b"refs/worktree/private",
            "refs/worktree/private"
        );
        mktest!(refs_path_with_file_extension, b"refs/heads/main.ext");
        mktests!(
            refs_path_with_file_extension_san,
            b"refs/heads/main.ext",
            "refs/heads/main.ext"
        );
        mktest!(refs_path_underscores_and_dashes, b"refs/heads/main-2nd_ext");
        mktests!(
            refs_path_underscores_and_dashes_san,
            b"refs/heads/main-2nd_ext",
            "refs/heads/main-2nd_ext"
        );
        mktest!(relative_path, b"etc/foo");
        mktests!(relative_path_san, b"etc/foo", "etc/foo");
        mktest!(all_uppercase, b"MAIN");
        mktests!(all_uppercase_san, b"MAIN", "MAIN");
        mktest!(all_uppercase_with_underscore, b"NEW_HEAD");
        mktests!(all_uppercase_with_underscore_san, b"NEW_HEAD", "NEW_HEAD");
        mktest!(partial_name_lowercase, b"main");
        mktests!(partial_name_lowercase_san, b"main", "main");
        mktest!(chinese_utf8, "heads/你好吗".as_bytes());
        mktests!(chinese_utf8_san, "heads/你好吗".as_bytes(), "heads/你好吗");
        mktest!(parentheses_special_case_upload_pack, b"(null)");
        mktests!(parentheses_special_case_upload_pack_san, b"(null)", "(null)");
    }

    mod invalid {
        use bstr::ByteSlice;
        use gix_validate::reference::name::Error as RefError;

        macro_rules! mktest {
            ($name:ident, $input:literal, $expected:pat) => {
                #[test]
                fn $name() {
                    match gix_validate::reference::name_partial($input.as_bstr()) {
                        Err($expected) => {}
                        got => panic!("Wanted {}, got {:?}", stringify!($expected), got),
                    }
                }
            };
        }

        mktest!(refs_path_double_dot, b"refs/../somewhere", RefError::StartsWithDot);
        mktests!(refs_path_double_dot_san, b"refs/../somewhere", "refs/-/somewhere");
        mktest!(
            refs_path_name_starts_with_dot,
            b".refs/somewhere",
            RefError::StartsWithDot
        );
        mktest!(
            refs_path_name_starts_with_multi_dot,
            b"..refs/somewhere",
            RefError::RepeatedDot
        );
        mktests!(
            refs_path_name_starts_with_multi_dot_san,
            b"..refs/somewhere",
            "-refs/somewhere"
        );
        mktests!(
            refs_path_name_starts_with_dot_san,
            b".refs/somewhere",
            "-refs/somewhere"
        );
        mktest!(
            refs_path_component_is_singular_dot,
            b"refs/./still-inside-but-not-cool",
            RefError::StartsWithDot
        );
        mktests!(
            refs_path_component_is_singular_dot_san,
            b"refs/./still-inside-but-not-cool",
            "refs/-/still-inside-but-not-cool"
        );
        mktest!(any_path_starts_with_slash, b"/etc/foo", RefError::StartsWithSlash);
        mktests!(any_path_starts_with_slash_san, b"/etc/foo", "etc/foo");
        mktest!(empty_path, b"", RefError::Empty);
        mktests!(empty_path_san, b"", "-");
        mktest!(refs_starts_with_slash, b"/refs/heads/main", RefError::StartsWithSlash);
        mktests!(refs_starts_with_slash_san, b"/refs/heads/main", "refs/heads/main");
        mktest!(ends_with_slash, b"refs/heads/main/", RefError::EndsWithSlash);
        mktests!(ends_with_slash_san, b"refs/heads/main/", "refs/heads/main");
        mktest!(
            path_with_duplicate_slashes,
            b"refs//heads/main",
            RefError::RepeatedSlash
        );
        mktests!(path_with_duplicate_slashes_san, b"refs//heads/main", "refs/heads/main");
        mktest!(
            path_with_spaces,
            b"refs/heads/name with spaces",
            RefError::InvalidByte { .. }
        );
        mktests!(
            path_with_spaces_san,
            b"refs//heads////name with spaces",
            "refs/heads/name-with-spaces"
        );
        mktest!(
            path_with_backslashes,
            br"refs\heads/name with spaces",
            RefError::InvalidByte { .. }
        );
        mktests!(
            path_with_backslashes_san,
            br"refs\heads/name with spaces",
            "refs-heads/name-with-spaces"
        );
    }
}

mod name {
    mod valid {
        use bstr::ByteSlice;
        macro_rules! mktest {
            ($name:ident, $input:expr) => {
                #[test]
                fn $name() {
                    assert!(gix_validate::reference::name($input.as_bstr()).is_ok())
                }
            };
        }

        mktest!(main_worktree_pseudo_ref, b"main-worktree/HEAD");
        mktests!(
            main_worktree_pseudo_ref_san,
            b"main-worktree/HEAD",
            "main-worktree/HEAD"
        );
        mktest!(main_worktree_ref, b"main-worktree/refs/bisect/good");
        mktests!(
            main_worktree_ref_san,
            b"main-worktree/refs/bisect/good",
            "main-worktree/refs/bisect/good"
        );
        mktest!(other_worktree_pseudo_ref, b"worktrees/id/HEAD");
        mktests!(other_worktree_pseudo_ref_san, b"worktrees/id/HEAD", "worktrees/id/HEAD");
        mktest!(other_worktree_ref, b"worktrees/id/refs/bisect/good");
        mktests!(
            other_worktree_ref_san,
            b"worktrees/id/refs/bisect/good",
            "worktrees/id/refs/bisect/good"
        );
        mktest!(worktree_private_ref, b"refs/worktree/private");
        mktests!(
            worktree_private_ref_san,
            b"refs/worktree/private",
            "refs/worktree/private"
        );
        mktest!(refs_path, b"refs/heads/main");
        mktests!(refs_path_san, b"refs/heads/main", "refs/heads/main");
        mktest!(refs_path_with_file_extension, b"refs/heads/main.ext");
        mktests!(
            refs_path_with_file_extension_san,
            b"refs/heads/main.ext",
            "refs/heads/main.ext"
        );
        mktest!(refs_path_underscores_and_dashes, b"refs/heads/main-2nd_ext");
        mktests!(
            refs_path_underscores_and_dashes_san,
            b"refs/heads/main-2nd_ext",
            "refs/heads/main-2nd_ext"
        );
        mktest!(relative_path, b"etc/foo");
        mktests!(relative_path_san, b"etc/foo", "etc/foo");
        mktest!(all_uppercase, b"MAIN");
        mktests!(all_uppercase_san, b"MAIN", "MAIN");
        mktest!(all_uppercase_with_underscore, b"NEW_HEAD");
        mktests!(all_uppercase_with_underscore_san, b"NEW_HEAD", "NEW_HEAD");
        mktest!(chinese_utf8, "refs/heads/你好吗".as_bytes());
        mktests!(chinese_utf8_san, "refs/heads/你好吗".as_bytes(), "refs/heads/你好吗");
        mktest!(dot_in_directory_component, b"this./totally./works");
        mktests!(
            dot_in_directory_component_san,
            b"this./totally./works",
            "this./totally./works"
        );
    }

    mod invalid {
        use bstr::ByteSlice;
        use gix_validate::reference::name::Error as RefError;

        macro_rules! mktest {
            ($name:ident, $input:literal, $expected:pat) => {
                #[test]
                fn $name() {
                    match gix_validate::reference::name($input.as_bstr()) {
                        Err($expected) => {}
                        got => panic!("Wanted {}, got {:?}", stringify!($expected), got),
                    }
                }
            };
        }

        mktest!(refs_path_double_dot, b"refs/../somewhere", RefError::StartsWithDot);
        mktests!(refs_path_double_dot_san, b"refs/../somewhere", "refs/-/somewhere");
        mktest!(refs_name_special_case_upload_pack, b"(null)", RefError::SomeLowercase);
        mktests!(refs_name_special_case_upload_pack_san, b"(null)", "(null)");
        mktest!(
            refs_path_name_starts_with_dot,
            b".refs/somewhere",
            RefError::StartsWithDot
        );
        mktests!(
            refs_path_name_starts_with_dot_san,
            b".refs/somewhere",
            "-refs/somewhere"
        );

        mktest!(
            refs_path_name_starts_with_dot_in_name,
            b"refs/.somewhere",
            RefError::StartsWithDot
        );
        mktests!(
            refs_path_name_starts_with_dot_in_name_san,
            b"refs/.somewhere",
            "refs/-somewhere"
        );
        mktest!(
            refs_path_name_ends_with_dot_in_name,
            b"refs/somewhere.",
            RefError::EndsWithDot
        );
        mktests!(
            refs_path_name_ends_with_dot_in_name_san,
            b"refs/somewhere.",
            "refs/somewhere-"
        );
        mktest!(
            refs_path_component_is_singular_dot,
            b"refs/./still-inside-but-not-cool",
            RefError::StartsWithDot
        );
        mktests!(
            refs_path_component_is_singular_dot_an,
            b"refs/./still-inside-but-not-cool",
            "refs/-/still-inside-but-not-cool"
        );
        mktest!(capitalized_name_without_path, b"Main", RefError::SomeLowercase);
        mktests!(capitalized_name_without_path_san, b"Main", "Main");
        mktest!(lowercase_name_without_path, b"main", RefError::SomeLowercase);
        mktests!(lowercase_name_without_path_san, b"main", "main");
        mktest!(any_path_starts_with_slash, b"/etc/foo", RefError::StartsWithSlash);
        mktests!(any_path_starts_with_slash_san, b"/etc/foo", "etc/foo");
        mktest!(empty_path, b"", RefError::Empty);
        mktests!(empty_path_san, b"", "-");
        mktest!(refs_starts_with_slash, b"/refs/heads/main", RefError::StartsWithSlash);
        mktests!(refs_starts_with_slash_san, b"/refs/heads/main", "refs/heads/main");
        mktest!(ends_with_slash, b"refs/heads/main/", RefError::EndsWithSlash);
        mktests!(ends_with_slash_san, b"refs/heads/main/", "refs/heads/main");
        mktest!(ends_with_slash_multiple, b"refs/heads/main///", RefError::EndsWithSlash);
        mktests!(ends_with_slash_multiple_san, b"refs/heads/main///", "refs/heads/main");
        mktest!(
            a_path_with_duplicate_slashes,
            b"refs//heads/main",
            RefError::RepeatedSlash
        );
        mktests!(
            a_path_with_duplicate_slashes_san,
            b"refs//heads/main",
            "refs/heads/main"
        );
    }
}

mod branch_name {
    mod valid {
        use bstr::ByteSlice;

        #[test]
        fn refs_heads_main() {
            assert!(gix_validate::reference::branch_name(b"refs/heads/main".as_bstr()).is_ok());
        }

        #[test]
        fn refs_heads_head_different_case() {
            assert!(gix_validate::reference::branch_name(b"refs/heads/HEAd".as_bstr()).is_ok());
        }
    }

    mod invalid {
        use bstr::ByteSlice;

        #[test]
        fn refs_heads_head_is_reserved() {
            assert!(matches!(
                gix_validate::reference::branch_name(b"refs/heads/HEAD".as_bstr()),
                Err(gix_validate::reference::name::Error::Reserved { name }) if name == "refs/heads/HEAD"
            ));
        }

        #[test]
        fn invalid_refname_is_wrapped() {
            assert!(matches!(
                gix_validate::reference::branch_name(b"refs//heads/main".as_bstr()),
                Err(gix_validate::reference::name::Error::RepeatedSlash)
            ));
        }
    }
}
