use crate::common::util::TestScenario;
use std::borrow::Cow;
use std::path::Path;

struct TestCase<'a> {
    from: &'a str,
    to: &'a str,
    expected: &'a str,
}

const TESTS: [TestCase; 10] = [
    TestCase {
        from: "A/B/C",
        to: "A",
        expected: "../..",
    },
    TestCase {
        from: "A/B/C",
        to: "A/B",
        expected: "..",
    },
    TestCase {
        from: "A/B/C",
        to: "A/B/C",
        expected: "",
    },
    TestCase {
        from: "A/B/C",
        to: "A/B/C/D",
        expected: "D",
    },
    TestCase {
        from: "A/B/C",
        to: "A/B/C/D/E",
        expected: "D/E",
    },
    TestCase {
        from: "A/B/C",
        to: "A/B/D",
        expected: "../D",
    },
    TestCase {
        from: "A/B/C",
        to: "A/B/D/E",
        expected: "../D/E",
    },
    TestCase {
        from: "A/B/C",
        to: "A/D",
        expected: "../../D",
    },
    TestCase {
        from: "A/B/C",
        to: "D/E/F",
        expected: "../../../D/E/F",
    },
    TestCase {
        from: "A/B/C",
        to: "A/D/E",
        expected: "../../D/E",
    },
];

#[allow(clippy::needless_lifetimes)]
fn convert_path<'a>(path: &'a str) -> Cow<'a, str> {
    #[cfg(windows)]
    return path.replace('/', "\\").into();
    #[cfg(not(windows))]
    return path.into();
}

#[test]
fn test_relpath_with_from_no_d() {
    let scene = TestScenario::new(util_name!());
    let at = &scene.fixtures;

    for test in &TESTS {
        let from: &str = &convert_path(test.from);
        let to: &str = &convert_path(test.to);
        let expected: &str = &convert_path(test.expected);

        at.mkdir_all(to);
        at.mkdir_all(from);

        scene
            .ucmd()
            .arg(to)
            .arg(from)
            .succeeds()
            .stdout_only(&format!("{expected}\n"));
    }
}

#[test]
fn test_relpath_with_from_with_d() {
    let scene = TestScenario::new(util_name!());
    let at = &scene.fixtures;

    for test in &TESTS {
        let from: &str = &convert_path(test.from);
        let to: &str = &convert_path(test.to);
        let pwd = at.as_string();
        at.mkdir_all(to);
        at.mkdir_all(from);

        // d is part of subpath -> expect relative path
        let mut _result_stdout = scene
            .ucmd()
            .arg(to)
            .arg(from)
            .arg(&format!("-d{pwd}"))
            .succeeds()
            .stdout_move_str();
        // relax rules for windows test environment
        #[cfg(not(windows))]
        assert!(Path::new(&_result_stdout).is_relative());

        // d is not part of subpath -> expect absolute path
        _result_stdout = scene
            .ucmd()
            .arg(to)
            .arg(from)
            .arg("-dnon_existing") // spell-checker:disable-line
            .succeeds()
            .stdout_move_str();
        assert!(Path::new(&_result_stdout).is_absolute());
    }
}

#[test]
fn test_relpath_no_from_no_d() {
    let scene = TestScenario::new(util_name!());
    let at = &scene.fixtures;

    for test in &TESTS {
        let to: &str = &convert_path(test.to);
        at.mkdir_all(to);

        let _result_stdout = scene.ucmd().arg(to).succeeds().stdout_move_str();
        #[cfg(not(windows))]
        assert_eq!(_result_stdout, format!("{to}\n"));
        // relax rules for windows test environment
        #[cfg(windows)]
        assert!(_result_stdout.ends_with(&format!("{to}\n")));
    }
}

#[test]
fn test_relpath_no_from_with_d() {
    let scene = TestScenario::new(util_name!());
    let at = &scene.fixtures;

    for test in &TESTS {
        let to: &str = &convert_path(test.to);
        let pwd = at.as_string();
        at.mkdir_all(to);

        // d is part of subpath -> expect relative path
        let _result_stdout = scene
            .ucmd()
            .arg(to)
            .arg(&format!("-d{pwd}"))
            .succeeds()
            .stdout_move_str();
        // relax rules for windows test environment
        #[cfg(not(windows))]
        assert!(Path::new(&_result_stdout).is_relative());

        // d is not part of subpath -> expect absolute path
        let result_stdout = scene
            .ucmd()
            .arg(to)
            .arg("-dnon_existing") // spell-checker:disable-line
            .succeeds()
            .stdout_move_str();
        assert!(Path::new(&result_stdout).is_absolute());
    }
}
