use std::collections::HashMap;
use std::path::Path;
use std::path::PathBuf;

use anyhow::Result;
use cmd_lib::run_cmd;
use cmd_lib::run_fun;
use sealed_test::prelude::*;
use speculoos::prelude::*;

use crate::helpers::*;
use cocogitto::command::bump::{BumpOptions, PackageBumpOptions};
use cocogitto::settings::{MonoRepoPackage, MonorepoConfig, Settings};
use cocogitto::{
    conventional::version::{IncrementCommand, PreCommand},
    CocoGitto,
};

#[sealed_test]
fn bump_ok() -> Result<()> {
    // Arrange
    git_init()?;
    git_commit("chore: first commit")?;
    git_commit("feat: add a feature commit")?;
    git_tag("1.0.0")?;
    git_commit("feat: add another feature commit")?;

    let mut cocogitto = CocoGitto::get()?;

    // Act
    let result = cocogitto.create_version(BumpOptions {
        increment: IncrementCommand::Auto,
        ..Default::default()
    });

    // Assert
    assert_that!(result).is_ok();
    assert_latest_tag("1.1.0")?;
    Ok(())
}

#[sealed_test]
fn annotated_bump_ok() -> Result<()> {
    // Arrange
    git_init()?;
    git_commit("chore: first commit")?;
    git_commit("feat: add a feature commit")?;
    git_tag("1.0.0")?;
    git_commit("feat: add another feature commit")?;

    let mut cocogitto = CocoGitto::get()?;

    // Act
    let result = cocogitto.create_version(BumpOptions {
        annotated: Some(String::from("Release version {{version}}")),
        ..Default::default()
    });

    // Assert
    assert_that!(result).is_ok();
    assert_latest_tag("1.1.0")?;
    assert_tag_is_annotated("1.1.0")?;
    Ok(())
}

#[sealed_test]
fn monorepo_bump_ok() -> Result<()> {
    // Arrange
    let mut settings = Settings {
        ..Default::default()
    };

    init_monorepo(&mut settings)?;

    let mut cocogitto = CocoGitto::get()?;

    // Act
    let result = cocogitto.create_monorepo_version(BumpOptions::default());

    // Assert
    assert_that!(result).is_ok();
    assert_tag_exists("0.1.0")?;
    assert_tag_exists("one-0.1.0")?;
    Ok(())
}

#[sealed_test]
fn monorepo_bump_manual_ok() -> Result<()> {
    // Arrange
    let mut settings = Settings {
        ..Default::default()
    };

    init_monorepo(&mut settings)?;
    run_cmd!(
        git tag "one-0.1.0";
    )?;

    let mut cocogitto = CocoGitto::get()?;

    // Act
    let result = cocogitto.create_monorepo_version(BumpOptions {
        increment: IncrementCommand::Major,
        ..Default::default()
    });

    // Assert
    assert_that!(result).is_ok();
    assert_tag_exists("1.0.0")?;
    Ok(())
}

#[sealed_test]
fn monorepo_bump_manual_disable_changelog_ok() -> Result<()> {
    // Arrange
    let mut settings = Settings {
        disable_changelog: true,
        ..Default::default()
    };

    init_monorepo(&mut settings)?;
    run_cmd!(
        git tag "one-0.1.0";
    )?;

    let mut cocogitto = CocoGitto::get()?;

    // Act
    let result = cocogitto.create_monorepo_version(BumpOptions {
        increment: IncrementCommand::Major,
        ..Default::default()
    });

    // Assert
    assert_that!(result).is_ok();
    assert_tag_exists("1.0.0")?;
    assert_that!(Path::new("CHANGELOG.md")).does_not_exist();
    Ok(())
}

#[sealed_test]
fn monorepo_with_tag_prefix_bump_ok() -> Result<()> {
    // Arrange
    let mut settings = Settings {
        tag_prefix: Some("v".to_string()),
        ..Default::default()
    };

    init_monorepo(&mut settings)?;

    let mut cocogitto = CocoGitto::get()?;

    // Act
    let result = cocogitto.create_monorepo_version(BumpOptions::default());

    // Assert
    assert_that!(result).is_ok();
    assert_tag_exists("v0.1.0")?;
    assert_tag_exists("one-v0.1.0")?;
    Ok(())
}

#[sealed_test]
fn package_bump_ok() -> Result<()> {
    // Arrange
    let mut settings = Settings {
        ..Default::default()
    };

    init_monorepo(&mut settings)?;
    let package = settings
        .monorepo
        .as_ref()
        .and_then(|m| m.packages.get("one"))
        .unwrap();
    let mut cocogitto = CocoGitto::get()?;

    // Act
    let result = cocogitto.create_package_version(PackageBumpOptions {
        package_name: "one",
        package,
        increment: IncrementCommand::AutoPackage("one".to_string()),
        ..Default::default()
    });

    // Assert
    assert_that!(result).is_ok();
    assert_tag_does_not_exist("0.1.0")?;
    assert_tag_exists("one-0.1.0")?;
    Ok(())
}

#[sealed_test]
fn consecutive_package_bump_ok() -> Result<()> {
    // Arrange
    let mut packages = HashMap::new();
    let jenkins = || MonoRepoPackage {
        path: PathBuf::from("jenkins"),
        public_api: false,
        changelog_path: Some("jenkins/CHANGELOG.md".to_owned()),
        ..Default::default()
    };

    packages.insert("jenkins".to_owned(), jenkins());

    let thumbor = || MonoRepoPackage {
        path: PathBuf::from("thumbor"),
        public_api: false,
        changelog_path: Some("thumbor/CHANGELOG.md".to_owned()),
        ..Default::default()
    };

    packages.insert("thumbor".to_owned(), thumbor());

    let settings = Settings {
        monorepo: Some(MonorepoConfig {
            packages,
            ..Default::default()
        }),
        ignore_merge_commits: true,
        ..Default::default()
    };

    let settings = toml::to_string(&settings)?;

    git_init()?;
    mkdir(&["jenkins", "thumbor"])?;
    run_cmd!(
        echo Hello > README.md;
        git add .;
        git commit -m "first commit";
        echo "some jenkins stuff" > jenkins/file;
        git add .;
        git commit -m "feat(jenkins): add jenkins stuffs";
        echo "some thumbor stuff" > thumbor/file;
        git add .;
        git commit -m "feat(thumbor): add thumbor stuffs";
        echo $settings > cog.toml;
        git add .;
        git commit -m "chore: add cog.toml";
    )?;

    let mut cocogitto = CocoGitto::get()?;

    // Act
    cocogitto.create_package_version(PackageBumpOptions {
        package_name: "thumbor",
        package: &thumbor(),
        increment: IncrementCommand::AutoPackage("thumbor".to_owned()),
        ..Default::default()
    })?;

    cocogitto.clear_cache();

    cocogitto.create_package_version(PackageBumpOptions {
        package_name: "jenkins",
        package: &jenkins(),
        increment: IncrementCommand::AutoPackage("jenkins".to_owned()),
        ..Default::default()
    })?;

    run_cmd!(
        echo "fix jenkins bug" > jenkins/fix;
        git add .;
        git commit -m "fix(jenkins): bug fix on jenkins package";
    )?;

    cocogitto.clear_cache();

    cocogitto.create_package_version(PackageBumpOptions {
        package_name: "jenkins",
        package: &jenkins(),
        increment: IncrementCommand::AutoPackage("jenkins".to_owned()),
        ..Default::default()
    })?;

    // Assert
    assert_tag_exists("jenkins-0.1.0")?;
    assert_tag_exists("thumbor-0.1.0")?;
    assert_tag_exists("jenkins-0.1.1")?;
    assert_tag_does_not_exist("jenkins-0.2.0")?;
    assert_tag_does_not_exist("0.1.0")?;
    Ok(())
}

#[sealed_test]
fn ordered_package_bump() -> Result<()> {
    // Arrange
    let mut settings = Settings {
        ignore_merge_commits: true,
        ..Default::default()
    };

    let jenkins = || MonoRepoPackage {
        path: PathBuf::from("jenkins"),
        public_api: false,
        changelog_path: Some("jenkins/CHANGELOG.md".to_owned()),
        bump_order: Some(2),
        ..Default::default()
    };

    settings.monorepo = Some(MonorepoConfig {
        packages: {
            let mut packages = HashMap::new();
            packages.insert("jenkins".to_owned(), jenkins());
            packages
        },
        ..Default::default()
    });

    let thumbor = || MonoRepoPackage {
        path: PathBuf::from("thumbor"),
        public_api: false,
        changelog_path: Some("thumbor/CHANGELOG.md".to_owned()),
        bump_order: Some(1),
        ..Default::default()
    };

    if let Some(monorepo) = settings.monorepo.as_mut() {
        monorepo.packages.insert("thumbor".to_owned(), thumbor());
    } else {
        settings.monorepo = Some(MonorepoConfig {
            packages: {
                let mut packages = HashMap::new();
                packages.insert("thumbor".to_owned(), thumbor());
                packages
            },
            ..Default::default()
        });
    }

    let settings = toml::to_string(&settings)?;

    git_init()?;
    mkdir(&["jenkins", "thumbor"])?;
    run_cmd!(
        echo Hello > README.md;
        git add .;
        git commit -m "chore: first commit";
        echo "some jenkins stuff" > jenkins/file;
        git add .;
        git commit -m "feat(jenkins): add jenkins stuffs";
        echo "some thumbor stuff" > thumbor/file;
        git add .;
        git commit -m "feat(thumbor): add thumbor stuffs";
        echo $settings > cog.toml;
        git add .;
        git commit -m "chore: add cog.toml";
    )?;

    let mut cocogitto = CocoGitto::get()?;

    // Act
    cocogitto.create_monorepo_version(BumpOptions {
        increment: IncrementCommand::Auto,
        pre_release: None,
        build: None,
        hooks_config: None,
        annotated: None,
        dry_run: false,
        skip_ci: false,
        skip_ci_override: None,
        skip_untracked: false,
        disable_bump_commit: false,
        include_packages: false,
    })?;

    // Assert
    let tags_sorted_by_date = run_fun!(git tag --sort=-taggerdate)?;
    let tags_sorted_by_date: Vec<&str> = tags_sorted_by_date.lines().collect();
    assert_that!(tags_sorted_by_date).is_equal_to(vec!["0.0.0", "jenkins-0.1.0", "thumbor-0.1.0"]);

    Ok(())
}

#[sealed_test]
fn should_fallback_to_0_0_0_when_there_is_no_tag() -> Result<()> {
    // Arrange
    git_init()?;
    git_commit("chore: first commit")?;
    git_commit("feat: add a feature commit")?;

    let mut cocogitto = CocoGitto::get()?;

    // Act
    let result = cocogitto.create_version(BumpOptions::default());

    // Assert
    assert_that!(result).is_ok();
    assert_latest_tag("0.1.0")?;
    Ok(())
}

#[sealed_test]
fn should_ignore_latest_prerelease_tag() -> Result<()> {
    // Arrange
    git_init()?;
    git_commit("chore: first commit")?;
    git_commit("feat: add a feature commit")?;

    let mut cocogitto = CocoGitto::get()?;
    cocogitto.create_version(BumpOptions {
        pre_release: Some(PreCommand::Exact("alpha1")),
        ..Default::default()
    })?;

    git_commit("feat: more features")?;
    // Act
    let result = cocogitto.create_version(BumpOptions::default());

    // Assert
    assert_that!(result).is_ok();
    assert_tag_exists("0.1.0-alpha1")?;
    assert_latest_tag("0.1.0")?;

    Ok(())
}

#[sealed_test]
fn increment_pre_release_alpha_1() -> Result<()> {
    // Arrange
    git_init()?;
    git_commit("chore: first commit")?;
    git_commit("feat: add a feature commit")?;

    // Act
    let mut cocogitto = CocoGitto::get()?;
    cocogitto.create_version(BumpOptions {
        pre_release: Some(PreCommand::Auto("alpha.*")),
        ..Default::default()
    })?;

    assert_latest_tag("0.1.0-alpha.1")?;

    Ok(())
}

#[sealed_test]
fn increment_pre_release_alpha_2() -> Result<()> {
    // Arrange
    git_init()?;
    git_commit("chore: first commit")?;
    git_commit("feat: add a feature commit")?;
    git_tag("0.1.0-alpha.1")?;
    git_commit("feat: more features")?;

    // Act
    let mut cocogitto = CocoGitto::get()?;
    cocogitto.create_version(BumpOptions {
        pre_release: Some(PreCommand::Auto("alpha.*")),
        ..Default::default()
    })?;

    assert_latest_tag("0.1.0-alpha.2")?;

    Ok(())
}

#[sealed_test]
fn increment_pre_release_beta_1() -> Result<()> {
    // Arrange
    git_init()?;
    git_commit("chore: first commit")?;
    git_commit("feat: add a feature commit")?;
    git_tag("0.1.0-alpha.1")?;
    git_commit("feat: more features")?;
    git_tag("0.1.0-alpha.2")?;

    // Act
    let mut cocogitto = CocoGitto::get()?;
    cocogitto.create_version(BumpOptions {
        pre_release: Some(PreCommand::Auto("beta.*")),
        ..Default::default()
    })?;

    assert_latest_tag("0.1.0-beta.1")?;

    Ok(())
}

#[sealed_test]
fn increment_pre_release_new_feature_alpha_1() -> Result<()> {
    // Arrange
    git_init()?;
    git_commit("chore: first commit")?;
    git_commit("feat: add a feature commit")?;
    git_tag("0.1.0")?;
    git_commit("fix: fix a bug")?;
    git_tag("0.1.1-alpha.1")?;
    git_commit("feat: more features")?;

    // Act
    let mut cocogitto = CocoGitto::get()?;
    cocogitto.create_version(BumpOptions {
        pre_release: Some(PreCommand::Auto("alpha.*")),
        ..Default::default()
    })?;

    assert_latest_tag("0.2.0-alpha.1")?;

    Ok(())
}

#[sealed_test]
fn auto_bump_package_only_ok() -> Result<()> {
    // Arrange
    let mut packages = HashMap::new();
    let jenkins = || MonoRepoPackage {
        path: PathBuf::from("jenkins"),
        public_api: false,
        changelog_path: Some("jenkins/CHANGELOG.md".to_owned()),
        ..Default::default()
    };

    packages.insert("jenkins".to_owned(), jenkins());

    let thumbor = || MonoRepoPackage {
        path: PathBuf::from("thumbor"),
        public_api: false,
        changelog_path: Some("thumbor/CHANGELOG.md".to_owned()),
        ..Default::default()
    };

    packages.insert("thumbor".to_owned(), thumbor());

    let settings = Settings {
        monorepo: Some(MonorepoConfig {
            packages,
            ..Default::default()
        }),
        generate_mono_repository_global_tag: false,
        ..Default::default()
    };

    let settings = toml::to_string(&settings)?;

    git_init()?;
    mkdir(&["jenkins", "thumbor"])?;
    run_cmd!(
        echo Hello > README.md;
        git add .;
        git commit -m "first commit";
        echo "some jenkins stuff" > jenkins/file;
        git add .;
        git commit -m "feat(jenkins): add jenkins stuffs";
        echo "some thumbor stuff" > thumbor/file;
        git add .;
        git commit -m "feat(thumbor): add thumbor stuffs";
        echo $settings > cog.toml;
        git add .;
        git commit -m "chore: add cog.toml";
    )?;

    let mut cocogitto = CocoGitto::get()?;

    // Act
    cocogitto.create_all_package_version_auto(BumpOptions::default())?;

    assert_tag_exists("jenkins-0.1.0")?;
    assert_tag_exists("thumbor-0.1.0")?;
    assert_tag_does_not_exist("0.1.0")?;

    cocogitto.clear_cache();

    run_cmd!(
        echo "fix jenkins bug" > jenkins/fix;
        git add .;
        git commit -m "fix(jenkins): bug fix on jenkins package";
    )?;

    cocogitto.create_all_package_version_auto(BumpOptions::default())?;

    // Assert
    assert_tag_exists("jenkins-0.1.1")?;
    Ok(())
}

#[sealed_test]
fn auto_bump_global_only_ok() -> Result<()> {
    // Arrange
    let mut packages = HashMap::new();
    let jenkins = || MonoRepoPackage {
        path: PathBuf::from("jenkins"),
        public_api: false,
        changelog_path: Some("jenkins/CHANGELOG.md".to_owned()),
        ..Default::default()
    };

    packages.insert("jenkins".to_owned(), jenkins());

    let thumbor = || MonoRepoPackage {
        path: PathBuf::from("thumbor"),
        public_api: false,
        changelog_path: Some("thumbor/CHANGELOG.md".to_owned()),
        ..Default::default()
    };

    packages.insert("thumbor".to_owned(), thumbor());

    let settings = Settings {
        monorepo: Some(MonorepoConfig {
            packages,
            ..Default::default()
        }),
        generate_mono_repository_package_tags: false,
        ..Default::default()
    };

    let settings = toml::to_string(&settings)?;

    git_init()?;
    mkdir(&["jenkins", "thumbor"])?;
    run_cmd!(
        echo Hello > README.md;
        git add .;
        git commit -m "first commit";
        echo "some jenkins stuff" > jenkins/file;
        git add .;
        git commit -m "feat(jenkins): add jenkins stuffs";
        echo "some thumbor stuff" > thumbor/file;
        git add .;
        git commit -m "feat(thumbor): add thumbor stuffs";
        echo $settings > cog.toml;
        git add .;
        git commit -m "chore: add cog.toml";
    )?;

    let mut cocogitto = CocoGitto::get()?;

    // Act
    cocogitto.create_monorepo_version(BumpOptions::default())?;

    assert_tag_does_not_exist("jenkins-0.1.0")?;
    assert_tag_does_not_exist("thumbor-0.1.0")?;
    assert_tag_exists("0.1.0")?;

    cocogitto.clear_cache();

    run_cmd!(
        echo "fix jenkins bug" > jenkins/fix;
        git add .;
        git commit -m "fix(jenkins): bug fix on jenkins package";
    )?;

    cocogitto.create_monorepo_version(BumpOptions::default())?;

    // Assert
    assert_tag_exists("0.1.1")?;

    run_cmd!(
        echo "feat global feature" > global;
        git add .;
        git commit -m "feat: some global feature";
    )?;

    cocogitto.create_monorepo_version(BumpOptions::default())?;

    assert_tag_exists("0.2.0")?;
    Ok(())
}

// FIXME: Failing on non compliant tag should be configurable
//  until it's implemented we will ignore non compliant tags
// #[sealed_test]
// fn should_fail_when_latest_tag_is_not_semver_compliant() -> Result<()> {
//     // Arrange
//     git_init()?;
//     git_commit("chore: first commit")?;
//     git_commit("feat: add a feature commit")?;
//     git_tag("toto")?;
//     git_commit("feat: add another feature commit")?;
//
//     let mut cocogitto = CocoGitto::get()?;
//
//     // Act
//     let result = cocogitto.create_version(VersionIncrement::Auto, None, None, false);
//     let error = result.unwrap_err().to_string();
//     let error = error.as_str();
//
//     // Assert
//     assert_that!(error).is_equal_to(indoc!(
//         "
//         tag `toto` is not SemVer compliant
//         \tcause: unexpected character 't' while parsing major version number
//         "
//     ));
//     Ok(())
// }

#[sealed_test]
fn bump_with_whitelisted_branch_ok() -> Result<()> {
    // Arrange
    let settings = r#"branch_whitelist = [ "master" ]"#;

    git_init()?;
    run_cmd!(
        echo $settings > cog.toml;
        git add .;
    )?;

    git_commit("chore: first commit")?;
    git_commit("feat: add a feature commit")?;

    let mut cocogitto = CocoGitto::get()?;

    // Act
    let result = cocogitto.create_version(BumpOptions::default());

    // Assert
    assert_that!(result).is_ok();

    Ok(())
}

#[sealed_test]
fn bump_with_whitelisted_branch_fails() -> Result<()> {
    // Arrange
    let settings = r#"branch_whitelist = [ "main" ]"#;

    git_init()?;
    run_cmd!(
        echo $settings > cog.toml;
        git add .;
    )?;

    git_commit("chore: first commit")?;
    git_commit("feat: add a feature commit")?;

    let mut cocogitto = CocoGitto::get()?;

    // Act
    let result = cocogitto.create_version(BumpOptions::default());

    // Assert
    assert_that!(result.unwrap_err().to_string()).is_equal_to(
        "No patterns matched in [\"main\"] for branch 'master', bump is not allowed".to_string(),
    );

    Ok(())
}

#[sealed_test]
fn bump_with_whitelisted_branch_pattern_ok() -> Result<()> {
    // Arrange
    let settings = r#"branch_whitelist = [ "main", "release/**" ]"#;

    git_init()?;
    run_cmd!(
        echo $settings > cog.toml;
        git add .;
    )?;

    git_commit("chore: first commit")?;
    git_commit("feat: add a feature commit")?;

    run_cmd!(git checkout -b release/1.0.0;)?;

    let mut cocogitto = CocoGitto::get()?;

    // Act
    let result = cocogitto.create_version(BumpOptions::default());

    // Assert
    assert_that!(result).is_ok();

    Ok(())
}

#[sealed_test]
fn bump_with_whitelisted_branch_pattern_err() -> Result<()> {
    // Arrange
    let settings = r#"branch_whitelist = [ "release/**" ]"#;

    git_init()?;
    run_cmd!(
        echo $settings > cog.toml;
        git add .;
    )?;

    git_commit("chore: first commit")?;
    git_commit("feat: add a feature commit")?;

    let mut cocogitto = CocoGitto::get()?;

    // Act
    let result = cocogitto.create_version(BumpOptions::default());

    // Assert
    assert_that!(result).is_err();

    Ok(())
}

#[sealed_test]
fn bump_no_error_should_be_thrown_on_only_chore_docs_commit() -> Result<()> {
    // Arrange
    let mut packages = HashMap::new();
    let jenkins = || MonoRepoPackage {
        path: PathBuf::from("jenkins"),
        changelog_path: Some("jenkins/CHANGELOG.md".to_owned()),
        ..Default::default()
    };

    packages.insert("jenkins".to_owned(), jenkins());

    let thumbor = || MonoRepoPackage {
        path: PathBuf::from("thumbor"),
        changelog_path: Some("thumbor/CHANGELOG.md".to_owned()),
        ..Default::default()
    };

    packages.insert("thumbor".to_owned(), thumbor());

    let settings = Settings {
        monorepo: Some(MonorepoConfig {
            packages,
            ..Default::default()
        }),
        ignore_merge_commits: true,
        ..Default::default()
    };

    let settings = toml::to_string(&settings)?;

    git_init()?;
    mkdir(&["jenkins", "thumbor"])?;
    run_cmd!(
        echo Hello > README.md;
        git add .;
        git commit -m "first commit";
        echo "some jenkins stuff" > jenkins/file;
        git add .;
        git commit -m "feat(jenkins): add jenkins stuffs";
        echo "some thumbor stuff" > thumbor/file;
        git add .;
        git commit -m "feat(thumbor): add thumbor stuffs";
        echo $settings > cog.toml;
        git add .;
        git commit -m "chore: add cog.toml";
    )?;

    let mut cocogitto = CocoGitto::get()?;

    // Act
    cocogitto.create_monorepo_version(BumpOptions::default())?;

    run_cmd!(
        echo "chore on jenkins" > jenkins/fix;
        git add .;
        git commit -m "chore(jenkins): jenkins chore";
        echo "docs on jenkins" > jenkins/fix;
        git add .;
        git commit -m "docs(jenkins): jenkins docs";
    )?;

    cocogitto.clear_cache();

    cocogitto.create_monorepo_version(BumpOptions::default())?;

    cocogitto.clear_cache();

    cocogitto.create_package_version(PackageBumpOptions {
        package_name: "jenkins",
        package: &jenkins(),
        increment: IncrementCommand::AutoPackage("jenkins".to_owned()),
        ..Default::default()
    })?;

    run_cmd!(
        echo "more feat on thumbor" > thumbor/feat;
        git add .;
        git commit -m "feat(thumbor): more feat on thumbor";
    )?;

    cocogitto.clear_cache();

    cocogitto.create_monorepo_version(BumpOptions::default())?;

    // Assert
    assert_tag_exists("jenkins-0.1.0")?;
    assert_tag_exists("thumbor-0.1.0")?;
    assert_tag_exists("thumbor-0.2.0")?;
    assert_tag_exists("0.1.0")?;
    assert_tag_exists("0.2.0")?;

    assert_tag_does_not_exist("jenkins-0.1.1")?;
    assert_tag_does_not_exist("jenkins-0.2.0")?;
    assert_tag_does_not_exist("jenkins-1.0.0")?;
    Ok(())
}

#[sealed_test]
fn error_on_no_conventional_commits_found_for_monorepo() -> Result<()> {
    let settings = Settings {
        ..Default::default()
    };

    let settings = toml::to_string(&settings)?;

    git_init()?;

    run_cmd!(
        echo Hello > README.md;
        git add .;
        git commit -m "chore: first commit";

        echo $settings > cog.toml;
        git add .;

        echo "first feature" > file;
        git add .;
        git commit -m "feat: feature commit";
    )?;

    let mut cocogitto = CocoGitto::get()?;

    // Act
    let first_result = cocogitto.create_version(BumpOptions::default());

    // Assert
    assert_that!(first_result).is_ok();

    run_cmd!(
        echo "second feature" > file;
        git add .;
    )?;

    git_commit("second unconventional feature commit")?;

    // Act
    let second_result = cocogitto.create_version(BumpOptions::default());

    // Assert
    assert_that!(second_result).is_err();

    Ok(())
}

#[sealed_test]
fn error_on_no_conventional_commits_found_for_package() -> Result<()> {
    // Arrange
    let mut packages = HashMap::new();
    let jenkins = || MonoRepoPackage {
        path: PathBuf::from("jenkins"),
        changelog_path: Some("jenkins/CHANGELOG.md".to_owned()),
        ..Default::default()
    };

    packages.insert("jenkins".to_owned(), jenkins());

    let settings = Settings {
        monorepo: Some(MonorepoConfig {
            packages,
            ..Default::default()
        }),
        ignore_merge_commits: true,
        ..Default::default()
    };

    let settings = toml::to_string(&settings)?;

    git_init()?;
    mkdir(&["jenkins"])?;
    run_cmd!(
        echo Hello > README.md;
        git add .;
        git commit -m "first commit";

        echo $settings > cog.toml;
        git add .;
        git commit -m "chore: cog config";

        echo "some jenkins stuff" > jenkins/file;
        git add .;
        git commit -m "feat(jenkins): some jenkins stuff";
    )?;

    let mut cocogitto = CocoGitto::get()?;

    let first_result = cocogitto.create_package_version(PackageBumpOptions {
        package_name: "jenkins",
        package: &jenkins(),
        increment: IncrementCommand::AutoPackage("jenkins".to_owned()),
        ..Default::default()
    });

    assert_that!(first_result).is_ok();

    run_cmd!(
        echo "some other jenkins stuff" > jenkins/file;
        git add .;
        git commit -m "some other jenkins stuff";
    )?;

    let second_result = cocogitto.create_package_version(PackageBumpOptions {
        package_name: "jenkins",
        package: &jenkins(),
        increment: IncrementCommand::AutoPackage("jenkins".to_owned()),
        ..Default::default()
    });

    assert_that!(second_result).is_err();

    Ok(())
}

#[sealed_test]
fn bump_with_unconventional_and_conventional_commits_found_for_packages() -> Result<()> {
    // Arrange
    let mut packages = HashMap::new();
    let jenkins = || MonoRepoPackage {
        path: PathBuf::from("jenkins"),
        changelog_path: Some("jenkins/CHANGELOG.md".to_owned()),
        ..Default::default()
    };

    packages.insert("jenkins".to_owned(), jenkins());

    let thumbor = || MonoRepoPackage {
        path: PathBuf::from("thumbor"),
        changelog_path: Some("thumbor/CHANGELOG.md".to_owned()),
        ..Default::default()
    };

    packages.insert("thumbor".to_owned(), thumbor());

    let settings = Settings {
        monorepo: Some(MonorepoConfig {
            packages,
            ..Default::default()
        }),
        ignore_merge_commits: true,
        ..Default::default()
    };

    let settings = toml::to_string(&settings)?;

    git_init()?;
    mkdir(&["jenkins", "thumbor"])?;
    run_cmd!(
        echo Hello > README.md;
        git add .;
        git commit -m "first commit";
        echo "unconventional jenkins stuff" > jenkins/file;
        git add .;
        git commit -m "unconventional jenkins stuff";
        echo "conventional thumbor stuff" > thumbor/file;
        git add .;
        git commit -m "feat(thumbor): conventional thumbor stuff";
        echo $settings > cog.toml;
        git add .;
        git commit -m "chore: add cog.toml";
    )?;

    let mut cocogitto = CocoGitto::get()?;

    // Act
    let result = cocogitto.create_monorepo_version(BumpOptions::default());

    // Assert
    assert_that!(result).is_ok();
    assert_tag_exists("thumbor-0.1.0")?;
    assert_tag_exists("0.1.0")?;

    assert_tag_does_not_exist("jenkins-0.1.0")?;

    Ok(())
}
