#![warn(clippy::shadow_reuse, clippy::shadow_same, clippy::shadow_unrelated)]

use super::*;
use std::env::set_current_dir;
use std::ffi::OsString;
use std::fs::{File, create_dir, remove_dir_all};
use std::thread::sleep;
use tempfile::{TempDir, tempdir};

fn setup_temporary_test_directory_structure1() -> (TempDir, Vec<OsString>) {
    let tenms = std::time::Duration::from_millis(10);

    let mut tmp_dir = tempdir().unwrap();
    tmp_dir.disable_cleanup(true);
    set_current_dir(&tmp_dir).unwrap();

    let mut outvec = Vec::<OsString>::new();

    create_dir("topdir1").unwrap();
    sleep(tenms);
    create_dir("topdir2").unwrap();
    sleep(tenms);
    create_dir("topdir3").unwrap();

    sleep(tenms);
    let file_path0 = tmp_dir.path().join("file0");
    let _ = File::create(&file_path0).unwrap();
    outvec.push(file_path0.as_os_str().to_os_string());
    sleep(tenms);

    let file_path1 = tmp_dir.path().join("file1");
    let mut fh = File::create(&file_path1).unwrap();
    let line = "6chars";
    let _ = write!(fh, "{}", line);
    outvec.push(file_path1.as_os_str().to_os_string());
    sleep(tenms);

    let file_path2 = tmp_dir.path().join("file2");
    let _ = File::create(&file_path2).unwrap();
    outvec.push(file_path2.as_os_str().to_os_string());
    sleep(tenms);

    let file_path3 = tmp_dir.path().join("file3");
    let mut fh = File::create(&file_path3).unwrap();
    let line = "15chars_xxxxxxx";
    let _ = write!(fh, "{}", line);
    outvec.push(file_path3.as_os_str().to_os_string());
    sleep(tenms);

    let file_path4 = tmp_dir.path().join("file4");
    let _ = File::create(&file_path4).unwrap();
    outvec.push(file_path4.as_os_str().to_os_string());
    sleep(tenms);

    let file_path5 = tmp_dir.path().join("file5");
    let mut fh = File::create(&file_path5).unwrap();
    let line = "15chars_xxxxxxx";
    let _ = write!(fh, "{}", line);
    outvec.push(file_path5.as_os_str().to_os_string());
    sleep(tenms);

    let file_path6 = tmp_dir.path().join("file6");
    let mut fh = File::create(&file_path6).unwrap();
    let line = "1";
    let _ = write!(fh, "{}", line);
    outvec.push(file_path6.as_os_str().to_os_string());
    sleep(tenms);

    let file_path7 = tmp_dir.path().join("file7");
    let mut fh = File::create(&file_path7).unwrap();
    let line = "20chars_xxxxxxxxxxxx";
    let _ = write!(fh, "{}", line);
    outvec.push(file_path7.as_os_str().to_os_string());
    sleep(tenms);

    let file_path8 = tmp_dir.path().join("topdir1/file8");
    let mut fh = File::create(&file_path8).unwrap();
    let line = "22chars_xxxxxxxxxxxxxx";
    let _ = write!(fh, "{}", line);
    outvec.push(file_path8.as_os_str().to_os_string());
    sleep(tenms);

    let file_path9 = tmp_dir.path().join("topdir1/file9");
    let mut fh = File::create(&file_path9).unwrap();
    let line = "17chars_xxxxxxxxx";
    let _ = write!(fh, "{}", line);
    outvec.push(file_path9.as_os_str().to_os_string());
    sleep(tenms);

    let file_path10 = tmp_dir.path().join("topdir2/file10");
    let _ = File::create(&file_path10).unwrap();
    outvec.push(file_path10.as_os_str().to_os_string());
    sleep(tenms);

    let file_path11 = tmp_dir.path().join("topdir1/file11");
    let _ = File::create(&file_path11).unwrap();
    outvec.push(file_path11.as_os_str().to_os_string());
    sleep(tenms);

    let file_path12 = tmp_dir.path().join("file12");
    let _ = File::create(&file_path12).unwrap();
    outvec.push(file_path12.as_os_str().to_os_string());
    sleep(tenms);

    let file_path13 = tmp_dir.path().join("file13");
    let _ = File::create(&file_path13).unwrap();
    outvec.push(file_path13.as_os_str().to_os_string());
    sleep(tenms);

    let file_path14 = tmp_dir.path().join("file14");
    let mut fh = File::create(&file_path14).unwrap();
    let line = "333";
    let _ = write!(fh, "{}", line);
    outvec.push(file_path14.as_os_str().to_os_string());
    sleep(tenms);

    (tmp_dir, outvec)
}

fn setup_temporary_test_directory_structure2() -> (TempDir, Vec<OsString>) {
    let mut tmp_dir = tempdir().unwrap();
    tmp_dir.disable_cleanup(true);

    let mut outvec = Vec::<OsString>::new();

    let file_path_0_name = "This is del:\u{007f}, now a newline
 ending now.";
    let file_path0 = tmp_dir.path().join(file_path_0_name);
    let _ = File::create(&file_path0).unwrap();
    outvec.push(file_path0.as_os_str().to_os_string());

    (tmp_dir, outvec)
}

#[test]
fn test_directory_structure1() {
    let mut do_cleanup = true;
    let (topdir, paths) = setup_temporary_test_directory_structure1();

    // Test direction youngest
    // =========================================================
    let mut expected_path_set_youngest = std::collections::HashSet::new();
    expected_path_set_youngest.insert(paths[9].clone());
    expected_path_set_youngest.insert(paths[10].clone());
    expected_path_set_youngest.insert(paths[11].clone());
    expected_path_set_youngest.insert(paths[12].clone());
    expected_path_set_youngest.insert(paths[13].clone());
    expected_path_set_youngest.insert(paths[14].clone());

    let cfg = Cfg {
        directories: false,
        exclude: None,
        include: None,
        number: 6,
        output_combo: OutputCombo::default(),
        result_order: ResultOrder::Youngest,
        startdirs: vec![topdir.path().into()],
        time_attribute: TimeAttribute::Modified,
        unicode_supported: true,
        weird: false,
        xdev: false,
    };
    let mut res = recurse_for_time::<SystemTimeForYoungest>(&cfg).unwrap();

    let mut resulting_path_set_youngest = std::collections::HashSet::new();
    while let Some(entry) = res.pop() {
        resulting_path_set_youngest.insert(entry.path);
    }

    let set_diff = expected_path_set_youngest.symmetric_difference(&resulting_path_set_youngest);
    let potential_errmsg = format!(
        "resulting_path_set_youngest!=expected_path_set_youngest. Test directory {} will not be deleted",
        topdir.path().to_string_lossy()
    );

    // We expect there to be no difference between expected and actual result set
    let went_ok = 0 == set_diff.collect::<Vec<_>>().len();
    if !went_ok {
        do_cleanup = false;
    }
    assert_eq!(went_ok, true, "{potential_errmsg}");

    // Test direction oldest
    // =========================================================
    let mut expected_path_set_oldest = std::collections::HashSet::new();
    expected_path_set_oldest.insert(paths[5].clone());
    expected_path_set_oldest.insert(paths[4].clone());
    expected_path_set_oldest.insert(paths[3].clone());
    expected_path_set_oldest.insert(paths[2].clone());
    expected_path_set_oldest.insert(paths[1].clone());
    expected_path_set_oldest.insert(paths[0].clone());

    let cfg = Cfg {
        directories: false,
        exclude: None,
        include: None,
        number: 6,
        output_combo: OutputCombo::default(),
        result_order: ResultOrder::Oldest,
        startdirs: vec![topdir.path().into()],
        time_attribute: TimeAttribute::Modified,
        unicode_supported: true,
        weird: false,
        xdev: false,
    };
    let mut res = recurse_for_time::<SystemTimeForOldest>(&cfg).unwrap();

    let mut resulting_path_set_oldest = std::collections::HashSet::new();
    while let Some(entry) = res.pop() {
        resulting_path_set_oldest.insert(entry.path);
    }

    let set_diff = expected_path_set_oldest.symmetric_difference(&resulting_path_set_oldest);
    let potential_errmsg = format!(
        "resulting_path_set_oldest!=expected_path_set_oldest. Test directory {} will not be deleted",
        topdir.path().to_string_lossy()
    );

    // We expect there to be no difference between expected and actual result set
    let went_ok = 0 == set_diff.collect::<Vec<_>>().len();
    if !went_ok {
        do_cleanup = false;
    }
    assert_eq!(went_ok, true, "{potential_errmsg}");

    // Test direction smallest
    // =========================================================
    let mut expected_path_set_smallest = std::collections::HashSet::new();
    expected_path_set_smallest.insert(paths[0].clone());
    expected_path_set_smallest.insert(paths[2].clone());
    expected_path_set_smallest.insert(paths[4].clone());
    expected_path_set_smallest.insert(paths[10].clone());
    expected_path_set_smallest.insert(paths[11].clone());
    expected_path_set_smallest.insert(paths[12].clone());
    expected_path_set_smallest.insert(paths[13].clone());
    expected_path_set_smallest.insert(paths[6].clone());
    expected_path_set_smallest.insert(paths[14].clone());

    let cfg = Cfg {
        directories: false,
        exclude: None,
        include: None,
        number: 9,
        output_combo: OutputCombo::default(),
        result_order: ResultOrder::Smallest,
        startdirs: vec![topdir.path().into()],
        time_attribute: TimeAttribute::Modified,
        unicode_supported: true,
        weird: false,
        xdev: false,
    };
    let mut res = recurse_for_size::<FileSizeForSmallest>(&cfg).unwrap();

    let mut resulting_path_set_smallest = std::collections::HashSet::new();
    while let Some(entry) = res.pop() {
        resulting_path_set_smallest.insert(entry.path);
    }

    let set_diff = expected_path_set_smallest.symmetric_difference(&resulting_path_set_smallest);
    let potential_errmsg = format!(
        "resulting_path_set!=expected_path_set_smallest. Test directory {} will not be deleted",
        topdir.path().to_string_lossy()
    );

    // We expect there to be no difference between expected and actual result set
    let went_ok = 0 == set_diff.collect::<Vec<_>>().len();
    if !went_ok {
        do_cleanup = false;
    }
    assert_eq!(went_ok, true, "{potential_errmsg}");

    // Test direction largest
    // =========================================================

    let mut expected_path_set_largest = std::collections::HashSet::new();
    expected_path_set_largest.insert(paths[9].clone());
    expected_path_set_largest.insert(paths[3].clone());
    expected_path_set_largest.insert(paths[5].clone());
    expected_path_set_largest.insert(paths[8].clone());
    expected_path_set_largest.insert(paths[7].clone());

    let cfg = Cfg {
        directories: false,
        exclude: None,
        include: None,
        number: 5,
        output_combo: OutputCombo::default(),
        result_order: ResultOrder::Largest,
        startdirs: vec![topdir.path().into()],
        time_attribute: TimeAttribute::Modified,
        unicode_supported: true,
        weird: false,
        xdev: false,
    };
    let mut res = recurse_for_size::<FileSizeForLargest>(&cfg).unwrap();

    let mut resulting_path_set_largest = std::collections::HashSet::new();
    while let Some(entry) = res.pop() {
        resulting_path_set_largest.insert(entry.path);
    }

    let set_diff = expected_path_set_largest.symmetric_difference(&resulting_path_set_largest);
    let potential_errmsg = format!(
        "resulting_path_set!=expected_path_set_largest. Test directory {} will not be deleted",
        topdir.path().to_string_lossy()
    );

    // We expect there to be no difference between expected and actual result set
    let went_ok = 0 == set_diff.collect::<Vec<_>>().len();
    if !went_ok {
        do_cleanup = false;
    }
    assert_eq!(went_ok, true, "{potential_errmsg}");

    // Test cleanup
    // =========================================================
    if !do_cleanup {
        let _ = remove_dir_all(topdir);
    }
}

#[test]
fn test_special_chars_being_converted_to_unicode_control_pictures() {
    let (topdir, _) = setup_temporary_test_directory_structure2();
    let output_combo = OutputCombo::from_str("p").unwrap();
    let cfg = Cfg {
        directories: false,
        exclude: None,
        include: None,
        number: 1,
        output_combo,
        result_order: ResultOrder::Youngest,
        startdirs: vec![topdir.path().into()],
        time_attribute: TimeAttribute::Modified,
        unicode_supported: true,
        weird: false,
        xdev: false,
    };
    let mut expected_bytes_after_topdir = vec![
        47, 84, 104, 105, 115, 32, 105, 115, 32, 100, 101, 108, 58, 226, 144, 161, 44, 32, 110,
        111, 119, 32, 97, 32, 110, 101, 119, 108, 105, 110, 101, 226, 144, 138, 32, 101, 110, 100,
        105, 110, 103, 32, 110, 111, 119, 46, 10,
    ];
    let topdir_path_string = topdir.path().to_string_lossy().to_string();
    let topdir_path_chars = topdir_path_string.chars();
    let mut expected_bytes: Vec<u8> = topdir_path_chars.map(|c| c as u8).collect(); // to be built on
    expected_bytes.append(&mut expected_bytes_after_topdir);
    let osb = build_output_scoreboard(&cfg).unwrap();
    let actual_bytes = build_output_bytes(&cfg, osb).unwrap();

    let potential_errmsg = format!(
        "actual_bytes!=expected_bytes. Test directory {} will not be deleted",
        topdir.path().to_string_lossy()
    );
    let went_ok = expected_bytes == actual_bytes;

    assert_eq!(went_ok, true, "{potential_errmsg}");
    if went_ok {
        let _ = remove_dir_all(topdir);
    }
}
