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

use std::path::PathBuf;

use camino::{Utf8Path, Utf8PathBuf};
use gleam_core::Error;

#[test]
fn new() {
    let tmp = tempfile::tempdir().unwrap();
    let path = Utf8PathBuf::from_path_buf(tmp.path().join("my_project")).expect("Non Utf8 Path");

    let creator = super::Creator::new(
        super::NewOptions {
            project_root: path.to_string(),
            template: super::Template::Erlang,
            name: None,
            skip_git: false,
            skip_github: false,
        },
        "1.0.0-gleam",
    )
    .unwrap();

    creator.run().unwrap();

    assert!(path.join(".git").exists());
    assert!(path.join("README.md").exists());
    assert!(path.join("gleam.toml").exists());
    assert!(path.join("src/my_project.gleam").exists());
    assert!(path.join("test/my_project_test.gleam").exists());
    assert!(path.join(".github/workflows/test.yml").exists());

    let toml = crate::fs::read(path.join("gleam.toml")).unwrap();
    assert!(toml.contains("name = \"my_project\""));
}

#[test]
fn new_with_default_template() {
    let tmp = tempfile::tempdir().unwrap();
    let path = Utf8PathBuf::from_path_buf(tmp.into_path()).expect("Non Utf8 Path");

    let creator = super::Creator::new(
        super::NewOptions {
            project_root: path.join("my_project").to_string(),
            template: super::Template::Erlang,
            name: None,
            skip_git: false,
            skip_github: true,
        },
        "1.0.0-gleam",
    )
    .unwrap();
    creator.run().unwrap();

    insta::glob!(&path, "my_project/[^.]**/*.*", |file_path| {
        if !file_path.is_dir() {
            insta::assert_snapshot!(
                crate::fs::read(
                    Utf8PathBuf::from_path_buf(file_path.to_path_buf()).expect("Non Utf8 Path"),
                )
                .unwrap()
            );
        }
    });
}

#[test]
fn new_with_javascript_template() {
    let tmp = tempfile::tempdir().unwrap();
    let path = Utf8PathBuf::from_path_buf(tmp.into_path()).expect("Non Utf8 Path");

    let creator = super::Creator::new(
        super::NewOptions {
            project_root: path.join("my_project").to_string(),
            template: super::Template::JavaScript,
            name: None,
            skip_git: false,
            skip_github: true,
        },
        "1.0.0-gleam",
    )
    .unwrap();
    creator.run().unwrap();

    insta::glob!(&path, "my_project/[^.]**/*.*", |file_path| {
        if !file_path.is_dir() {
            insta::assert_snapshot!(
                crate::fs::read(
                    Utf8PathBuf::from_path_buf(file_path.to_path_buf()).expect("Non Utf8 Path"),
                )
                .unwrap()
            );
        }
    });
}

#[test]
fn new_with_skip_git() {
    let tmp = tempfile::tempdir().unwrap();
    let path = Utf8PathBuf::from_path_buf(tmp.path().join("my_project")).expect("Non Utf8 Path");

    let creator = super::Creator::new(
        super::NewOptions {
            project_root: path.to_string(),
            template: super::Template::Erlang,
            name: None,
            skip_git: true,
            skip_github: false,
        },
        "1.0.0-gleam",
    )
    .unwrap();
    creator.run().unwrap();
    assert!(!path.join(".git").exists());
    assert!(!path.join(".github").exists());
}

#[test]
fn new_with_skip_github() {
    let tmp = tempfile::tempdir().unwrap();
    let path = Utf8PathBuf::from_path_buf(tmp.path().join("my_project")).expect("Non Utf8 Path");

    let creator = super::Creator::new(
        super::NewOptions {
            project_root: path.to_string(),
            template: super::Template::Erlang,
            name: None,
            skip_git: false,
            skip_github: true,
        },
        "1.0.0-gleam",
    )
    .unwrap();
    creator.run().unwrap();

    assert!(path.join(".git").exists());

    assert!(!path.join(".github").exists());
    assert!(!path.join(".github/workflows/test.yml").exists());
}

#[test]
fn new_with_skip_git_and_github() {
    let tmp = tempfile::tempdir().unwrap();
    let path = Utf8PathBuf::from_path_buf(tmp.path().join("my_project")).expect("Non Utf8 Path");

    let creator = super::Creator::new(
        super::NewOptions {
            project_root: path.to_string(),
            template: super::Template::Erlang,
            name: None,
            skip_git: true,
            skip_github: true,
        },
        "1.0.0-gleam",
    )
    .unwrap();
    creator.run().unwrap();

    assert!(!path.join(".git").exists());

    assert!(!path.join(".github").exists());
    assert!(!path.join(".github/workflows/test.yml").exists());
}

#[test]
fn invalid_path() {
    let tmp = tempfile::tempdir().unwrap();
    let path = Utf8PathBuf::from_path_buf(tmp.path().join("-------")).expect("Non Utf8 Path");

    assert!(
        super::Creator::new(
            super::NewOptions {
                project_root: path.to_string(),
                template: super::Template::Erlang,
                name: None,
                skip_git: false,
                skip_github: false,
            },
            "1.0.0-gleam",
        )
        .is_err()
    );
}

#[test]
fn invalid_name() {
    let tmp = tempfile::tempdir().unwrap();
    let path = Utf8PathBuf::from_path_buf(tmp.path().join("projec")).expect("Non Utf8 Path");

    assert!(
        super::Creator::new(
            super::NewOptions {
                project_root: path.to_string(),
                template: super::Template::Erlang,
                name: Some("-".into()),
                skip_git: false,
                skip_github: false,
            },
            "1.0.0-gleam",
        )
        .is_err()
    );
}

#[test]
fn existing_directory_no_files() {
    let tmp = tempfile::tempdir().unwrap();
    let path = Utf8PathBuf::from_path_buf(tmp.path().join("my_project")).expect("Non Utf8 Path");

    crate::fs::mkdir(&path).unwrap();

    let creator = super::Creator::new(
        super::NewOptions {
            project_root: path.to_string(),
            template: super::Template::Erlang,
            name: None,
            skip_git: true,
            skip_github: true,
        },
        "1.0.0-gleam",
    )
    .unwrap();

    creator.run().unwrap();

    assert!(path.join("README.md").exists());
}

#[test]
fn existing_directory_with_one_existing_file() {
    let tmp = tempfile::tempdir().unwrap();
    let path = Utf8PathBuf::from_path_buf(tmp.path().join("my_project")).expect("Non Utf8 Path");

    crate::fs::mkdir(&path).unwrap();

    let _ = std::fs::File::create(PathBuf::from(&path).join("README.md")).unwrap();
    let _ = std::fs::File::create(PathBuf::from(&path).join("my_project.gleam")).unwrap();

    assert!(
        super::Creator::new(
            super::NewOptions {
                project_root: path.to_string(),
                template: super::Template::Erlang,
                name: None,
                skip_git: true,
                skip_github: true,
            },
            "1.0.0-gleam",
        )
        .is_err()
    );
}

#[test]
fn existing_directory_with_non_generated_file() {
    let tmp = tempfile::tempdir().unwrap();
    let path = Utf8PathBuf::from_path_buf(tmp.path().join("my_project")).expect("Non Utf8 Path");

    crate::fs::mkdir(&path).unwrap();
    let file_path = PathBuf::from(&path).join("some_fake_thing_that_is_not_generated.md");

    let _ = std::fs::File::create(file_path).unwrap();

    let creator = super::Creator::new(
        super::NewOptions {
            project_root: path.to_string(),
            template: super::Template::Erlang,
            name: None,
            skip_git: true,
            skip_github: true,
        },
        "1.0.0-gleam",
    )
    .unwrap();

    creator.run().unwrap();

    assert!(path.join("README.md").exists());
    assert!(
        path.join("some_fake_thing_that_is_not_generated.md")
            .exists()
    );
}

#[test]
fn conflict_with_existing_files() {
    let tmp = tempfile::tempdir().unwrap();
    let path = Utf8PathBuf::from_path_buf(tmp.path().join("my_project")).expect("Non Utf8 Path");

    crate::fs::mkdir(&path).unwrap();

    let _ = std::fs::File::create(PathBuf::from(&path).join("README.md")).unwrap();

    assert_eq!(
        super::Creator::new(
            super::NewOptions {
                project_root: path.to_string(),
                template: super::Template::Erlang,
                name: None,
                skip_git: true,
                skip_github: true,
            },
            "1.0.0-gleam",
        )
        .err(),
        Some(Error::OutputFilesAlreadyExist {
            file_names: vec![path.join("README.md")]
        })
    );
}

#[test]
fn skip_existing_git_files_when_skip_git_is_true() {
    let tmp = tempfile::tempdir().unwrap();
    let path = Utf8PathBuf::from_path_buf(tmp.path().join("my_project")).expect("Non Utf8 Path");

    crate::fs::mkdir(&path).unwrap();
    let file_path = PathBuf::from(&path).join(".gitignore");

    let _ = std::fs::File::create(file_path).unwrap();

    let creator = super::Creator::new(
        super::NewOptions {
            project_root: path.to_string(),
            template: super::Template::Erlang,
            name: None,
            skip_git: true,
            skip_github: true,
        },
        "1.0.0-gleam",
    )
    .unwrap();

    creator.run().unwrap();

    assert!(path.join("README.md").exists());
    assert!(path.join(".gitignore").exists());
}

#[test]
fn suggested_project_name_updates_directory() {
    let tmp = tempfile::tempdir().unwrap();
    let base = Utf8PathBuf::from_path_buf(tmp.path().to_path_buf()).expect("Non Utf8 Path");
    let original_root = base.join("gleam_testproject");

    let creator = super::Creator::new_with_confirmation(
        super::NewOptions {
            project_root: original_root.to_string(),
            template: super::Template::Erlang,
            name: None,
            skip_git: true,
            skip_github: true,
        },
        "1.0.0-gleam",
        |_| Ok::<bool, Error>(true),
    )
    .unwrap();

    let expected_root = base.join("testproject");
    assert_eq!(creator.project_name, "testproject");
    assert_eq!(
        Utf8Path::new(&creator.options.project_root),
        expected_root.as_path()
    );

    creator.run().unwrap();

    assert!(expected_root.exists());
    assert!(!original_root.exists());
}

#[test]
fn validate_name_format() {
    assert!(crate::new::validate_name("project").is_ok());
    assert!(crate::new::validate_name("project_name").is_ok());
    assert!(crate::new::validate_name("project2").is_ok());

    let invalid = ["Project", "PROJECT", "Project_Name"];
    for name in invalid {
        assert!(matches!(
            crate::new::validate_name(name),
            Err(Error::InvalidProjectName {
                name: _,
                reason: crate::new::InvalidProjectNameReason::FormatNotLowercase
            })
        ));
    }

    let invalid = ["0project", "_project", "project-name"];
    for name in invalid {
        assert!(matches!(
            crate::new::validate_name(name),
            Err(Error::InvalidProjectName {
                name: _,
                reason: crate::new::InvalidProjectNameReason::Format
            })
        ));
    }
}

#[test]
fn suggest_valid_names() {
    assert_eq!(
        crate::new::suggest_valid_name(
            "gleam_",
            &crate::new::InvalidProjectNameReason::GleamPrefix
        ),
        None
    );
    assert_eq!(
        crate::new::suggest_valid_name(
            "gleam_project",
            &crate::new::InvalidProjectNameReason::GleamPrefix
        ),
        Some("project".to_string())
    );

    assert_eq!(
        crate::new::suggest_valid_name(
            "try",
            &crate::new::InvalidProjectNameReason::ErlangReservedWord
        ),
        Some("try_app".to_string())
    );

    assert_eq!(
        crate::new::suggest_valid_name(
            "erl_eval",
            &crate::new::InvalidProjectNameReason::ErlangStandardLibraryModule
        ),
        Some("erl_eval_app".to_string())
    );

    assert_eq!(
        crate::new::suggest_valid_name(
            "assert",
            &crate::new::InvalidProjectNameReason::GleamReservedWord
        ),
        Some("assert_app".to_string())
    );

    assert_eq!(
        crate::new::suggest_valid_name(
            "gleam",
            &crate::new::InvalidProjectNameReason::GleamReservedModule
        ),
        Some("app_gleam".to_string())
    );

    assert_eq!(
        crate::new::suggest_valid_name(
            "Project_Name",
            &crate::new::InvalidProjectNameReason::FormatNotLowercase
        ),
        Some("project_name".to_string())
    );

    assert_eq!(
        crate::new::suggest_valid_name(
            "Pr0ject-n4me!",
            &crate::new::InvalidProjectNameReason::Format
        ),
        Some("pr0ject_n4me_".to_string())
    );

    assert_eq!(
        crate::new::suggest_valid_name(
            "Pr0ject--n4me!",
            &crate::new::InvalidProjectNameReason::Format
        ),
        Some("pr0ject_n4me_".to_string())
    );

    assert_eq!(
        crate::new::suggest_valid_name(
            "_pr0ject-name",
            &crate::new::InvalidProjectNameReason::Format
        ),
        None
    );
}
