use crate::common::util::*;

const DIR: &str = "dir";
const DIR_FILE: &str = "dir/file";
const NESTED_DIR: &str = "dir/ect/ory";
const NESTED_DIR_FILE: &str = "dir/ect/ory/file";

#[cfg(windows)]
const NOT_FOUND: &str = "The system cannot find the file specified.";
#[cfg(not(windows))]
const NOT_FOUND: &str = "No such file or directory";

#[cfg(windows)]
const NOT_EMPTY: &str = "The directory is not empty.";
#[cfg(not(windows))]
const NOT_EMPTY: &str = "Directory not empty";

#[cfg(windows)]
const NOT_A_DIRECTORY: &str = "The directory name is invalid.";
#[cfg(not(windows))]
const NOT_A_DIRECTORY: &str = "Not a directory";

#[test]
fn test_invalid_arg() {
    new_ucmd!().arg("--definitely-invalid").fails().code_is(1);
}

#[test]
fn test_rmdir_empty_directory_no_parents() {
    let (at, mut ucmd) = at_and_ucmd!();

    at.mkdir(DIR);

    ucmd.arg(DIR).succeeds().no_stderr();

    assert!(!at.dir_exists(DIR));
}

#[test]
fn test_rmdir_empty_directory_with_parents() {
    let (at, mut ucmd) = at_and_ucmd!();

    at.mkdir_all(NESTED_DIR);

    ucmd.arg("-p").arg(NESTED_DIR).succeeds().no_stderr();

    assert!(!at.dir_exists(NESTED_DIR));
    assert!(!at.dir_exists(DIR));
}

#[test]
fn test_rmdir_nonempty_directory_no_parents() {
    let (at, mut ucmd) = at_and_ucmd!();

    at.mkdir(DIR);
    at.touch(DIR_FILE);

    ucmd.arg(DIR)
        .fails()
        .stderr_is(format!("rmdir: failed to remove 'dir': {}", NOT_EMPTY));

    assert!(at.dir_exists(DIR));
}

#[test]
fn test_rmdir_nonempty_directory_with_parents() {
    let (at, mut ucmd) = at_and_ucmd!();

    at.mkdir_all(NESTED_DIR);
    at.touch(NESTED_DIR_FILE);

    ucmd.arg("-p").arg(NESTED_DIR).fails().stderr_is(format!(
        "rmdir: failed to remove 'dir/ect/ory': {}",
        NOT_EMPTY
    ));

    assert!(at.dir_exists(NESTED_DIR));
}

#[test]
fn test_rmdir_ignore_nonempty_directory_no_parents() {
    let (at, mut ucmd) = at_and_ucmd!();

    at.mkdir(DIR);
    at.touch(DIR_FILE);

    ucmd.arg("--ignore-fail-on-non-empty")
        .arg(DIR)
        .succeeds()
        .no_stderr();

    assert!(at.dir_exists(DIR));
}

#[test]
fn test_rmdir_ignore_nonempty_directory_with_parents() {
    let (at, mut ucmd) = at_and_ucmd!();

    at.mkdir_all(NESTED_DIR);
    at.touch(NESTED_DIR_FILE);

    ucmd.arg("--ignore-fail-on-non-empty")
        .arg("-p")
        .arg(NESTED_DIR)
        .succeeds()
        .no_stderr();

    assert!(at.dir_exists(NESTED_DIR));
}

#[test]
fn test_rmdir_not_a_directory() {
    let (at, mut ucmd) = at_and_ucmd!();

    at.touch("file");

    ucmd.arg("--ignore-fail-on-non-empty")
        .arg("file")
        .fails()
        .no_stdout()
        .stderr_is(format!(
            "rmdir: failed to remove 'file': {}",
            NOT_A_DIRECTORY
        ));
}

#[test]
fn test_verbose_single() {
    let (at, mut ucmd) = at_and_ucmd!();

    at.mkdir(DIR);

    ucmd.arg("-v")
        .arg(DIR)
        .succeeds()
        .no_stderr()
        .stdout_is("rmdir: removing directory, 'dir'\n");
}

#[test]
fn test_verbose_multi() {
    let (at, mut ucmd) = at_and_ucmd!();

    at.mkdir(DIR);

    ucmd.arg("-v")
        .arg("does_not_exist")
        .arg(DIR)
        .fails()
        .stdout_is(
            "rmdir: removing directory, 'does_not_exist'\n\
             rmdir: removing directory, 'dir'\n",
        )
        .stderr_is(format!(
            "rmdir: failed to remove 'does_not_exist': {}",
            NOT_FOUND
        ));
}

#[test]
fn test_verbose_nested_failure() {
    let (at, mut ucmd) = at_and_ucmd!();

    at.mkdir_all(NESTED_DIR);
    at.touch("dir/ect/file");

    ucmd.arg("-pv")
        .arg(NESTED_DIR)
        .fails()
        .stdout_is(
            "rmdir: removing directory, 'dir/ect/ory'\n\
             rmdir: removing directory, 'dir/ect'\n",
        )
        .stderr_is(format!("rmdir: failed to remove 'dir/ect': {}", NOT_EMPTY));
}

#[cfg(unix)]
#[test]
fn test_rmdir_ignore_nonempty_no_permissions() {
    use std::fs;

    let (at, mut ucmd) = at_and_ucmd!();

    // We make the *parent* dir read-only to prevent deleting the dir in it.
    at.mkdir_all("dir/ect/ory");
    at.touch("dir/ect/ory/file");
    let dir_ect = at.plus("dir/ect");
    let mut perms = fs::metadata(&dir_ect).unwrap().permissions();
    perms.set_readonly(true);
    fs::set_permissions(&dir_ect, perms.clone()).unwrap();

    // rmdir should now get a permissions error that it interprets as
    // a non-empty error.
    ucmd.arg("--ignore-fail-on-non-empty")
        .arg("dir/ect/ory")
        .succeeds()
        .no_stderr();

    assert!(at.dir_exists("dir/ect/ory"));

    // Politely restore permissions for cleanup
    perms.set_readonly(false);
    fs::set_permissions(&dir_ect, perms).unwrap();
}

#[test]
fn test_rmdir_remove_symlink_file() {
    let (at, mut ucmd) = at_and_ucmd!();

    at.touch("file");
    at.symlink_file("file", "fl");

    ucmd.arg("fl/").fails().stderr_is(format!(
        "rmdir: failed to remove 'fl/': {}",
        NOT_A_DIRECTORY
    ));
}

// This behavior is known to happen on Linux but not all Unixes
#[cfg(any(target_os = "linux", target_os = "android"))]
#[test]
fn test_rmdir_remove_symlink_dir() {
    let (at, mut ucmd) = at_and_ucmd!();

    at.mkdir("dir");
    at.symlink_dir("dir", "dl");

    ucmd.arg("dl/")
        .fails()
        .stderr_is("rmdir: failed to remove 'dl/': Symbolic link not followed");
}

#[cfg(any(target_os = "linux", target_os = "android"))]
#[test]
fn test_rmdir_remove_symlink_dangling() {
    let (at, mut ucmd) = at_and_ucmd!();

    at.symlink_dir("dir", "dl");

    ucmd.arg("dl/")
        .fails()
        .stderr_is("rmdir: failed to remove 'dl/': Symbolic link not followed");
}
