use assert_cmd::cargo;

use std::{env, fs};

use korrect::config::{Config, DownloadMode, VersionStrategy};
use korrect::test_utils::{assert_dirs_exist, assert_dirs_not_exist, init_logger, setup_testenv};

mod integration_utils;
use integration_utils::{
    assert_on_korrect_list, assert_on_outputs, assert_setup_korrect_failure, force_setup_korrect,
    force_uninstall_korrect, setup_korrect, uninstall_korrect,
};

#[test]
fn test_korrect_setup_creates_and_uninstall_removes_expected_directories() {
    init_logger();
    let test_env = setup_korrect(None, None, None);

    assert_dirs_exist(&test_env.paths());

    uninstall_korrect(Some(&test_env), None, None);

    assert_dirs_exist(&test_env.paths());

    force_uninstall_korrect(Some(&test_env), None, None);

    assert_dirs_not_exist(&test_env.paths());
}

#[test]
fn test_korrect_setup_fails_on_existing_directories() {
    init_logger();
    let test_env = setup_testenv().with_directory(".korrect");
    assert_setup_korrect_failure(Some(&test_env), None, None);

    // Construct expected paths
    let korrect_base_path = test_env.home.join(".korrect");
    let paths = vec![
        korrect_base_path.join("bin"),
        test_env.config.join("korrect"),
        test_env.cache.join("korrect"),
    ];

    assert!(
        korrect_base_path.exists(),
        "Base path should exist: {:?}",
        korrect_base_path
    );
    assert_dirs_not_exist(&paths);

    force_setup_korrect(Some(&test_env), None, None);

    assert_dirs_exist(&test_env.paths());
}

#[test]
fn test_korrect_list_shows_expected_files() {
    init_logger();
    let test_env = setup_korrect(None, None, None);
    let list_items = &["helm", "k", "kubectl"];
    assert_on_korrect_list(&test_env.home, list_items);
}

#[test]
fn test_korrect_handles_missing_config_file() {
    init_logger();
    let test_env = setup_korrect(None, None, None);

    let conf_file = &test_env.config.join("korrect").join("config.toml");
    log::debug!("Removing config file: {:?}", conf_file);

    let _ = fs::remove_file(conf_file);
    assert!(!conf_file.exists(), "Config file should not exist");

    let mut cmd = cargo::cargo_bin_cmd!("korrect");
    let output = cmd
        .arg("config")
        .env("HOME", &test_env.home)
        .output()
        .expect("failed to execute korrect list");

    assert!(output.status.success());

    let expected_stderr = "Korrect config file";
    let expected_stdout = "Config { version: 2, korrect_shim: KorrectShimConfig { download_mode: Ask, version_strategy: Latest, manage_helm: true } }";
    assert_on_outputs(
        output.clone(),
        Some(expected_stderr.into()),
        Some(expected_stdout.into()),
    );
    let expected_stderr = "'korrect setup' first";
    assert_on_outputs(
        output,
        Some(expected_stderr.into()),
        Some(expected_stdout.into()),
    );
}

#[test]
fn test_korrect_handles_invalid_config_file() {
    init_logger();

    let test_env = setup_korrect(None, None, None);

    let conf_file = &test_env.config.join("korrect").join("config.toml");
    log::debug!("Config file: {:?}", conf_file);

    let config_string = "nonsense config";

    let _ = fs::write(conf_file, config_string);

    let mut cmd = cargo::cargo_bin_cmd!("korrect");
    let output = cmd
        .arg("config")
        .env("HOME", &test_env.home)
        .output()
        .expect("failed to execute korrect list");

    assert!(!output.status.success());

    let expected_stderr = "TOML parse error".into();
    assert_on_outputs(output, Some(expected_stderr), None);

    // Check that the expected message is present
    let binding = test_env.config.join("korrect").join("config.toml");
    let _expected_config_file_path = &binding.to_str().unwrap();
}

#[test]
fn test_korrect_setup_override_options_are_stored() {
    init_logger();
    let test_env = setup_korrect(None, Some(DownloadMode::Never), None);

    let conf_file = &test_env.config.join("korrect").join("config.toml");
    log::debug!("conf file: {:?}", conf_file);

    assert!(conf_file.exists(), "Config file does not exist");

    let contents = fs::read_to_string(conf_file).expect("Could not read config.toml");

    let parsed_config: Config = toml::from_str(&contents).expect("TOML parse failed");

    assert_eq!(
        parsed_config.korrect_shim.download_mode,
        DownloadMode::Never,
        "Expected download_mode to be Never"
    );

    assert_eq!(
        parsed_config.korrect_shim.version_strategy,
        VersionStrategy::Latest,
        "Expected version_strategy to be Latest"
    );
}

#[test]
fn test_korrect_setup_default_options_are_stored() {
    init_logger();
    let test_env = setup_korrect(None, None, None);

    let conf_file = &test_env.config.join("korrect").join("config.toml");
    log::debug!("conf file: {:?}", conf_file);

    assert!(conf_file.exists(), "Config file does not exist");

    let contents = fs::read_to_string(conf_file).expect("Could not read config.toml");
    let parsed_config: Config = toml::from_str(&contents).expect("TOML parse failed");

    assert_eq!(
        parsed_config.korrect_shim.download_mode,
        DownloadMode::Always,
        "Expected download_mode to be Always"
    );

    assert_eq!(
        parsed_config.korrect_shim.version_strategy,
        VersionStrategy::Latest,
        "Expected version_strategy to be Latest"
    );
}

#[test]
fn test_korrect_list_before_setup_shows_error() {
    init_logger();
    let test_env = setup_testenv();

    // Run the `korrect list` command with HOME overridden
    let output = cargo::cargo_bin_cmd!("korrect")
        .arg("list")
        .env("HOME", &test_env.home)
        .output()
        .expect("failed to execute korrect list");

    // Ensure the command failed
    assert!(!output.status.success());

    let expected_stderr = "Korrect is not set up".into();
    assert_on_outputs(output.clone(), Some(expected_stderr), None);

    let expected_stderr = "Run 'korrect setup' first".into();
    assert_on_outputs(output, Some(expected_stderr), None);
}

#[test]
fn test_korrect_config_before_setup_shows_error() {
    init_logger();
    let test_env = setup_testenv();

    // Run the `korrect list` command with HOME overridden
    let output = cargo::cargo_bin_cmd!("korrect")
        .arg("config")
        .env("HOME", &test_env.home)
        .output()
        .expect("Failed to execute korrect list.");

    // Ensure the command failed
    assert!(!output.status.success());

    let expected_stderr = "Korrect is not set up".into();
    assert_on_outputs(output.clone(), Some(expected_stderr), None);

    let expected_stderr = "Run 'korrect setup' first".into();
    assert_on_outputs(output, Some(expected_stderr), None);
}

#[test]
fn test_korrect_reminds_user_about_path_when_path_excludes_korrect_bin_directory() {
    init_logger();
    let test_env = setup_testenv();

    // Run the `korrect setup` command
    let output = cargo::cargo_bin_cmd!("korrect")
        .arg("setup")
        .env("SHELL", "zsh")
        .env("HOME", &test_env.home)
        .output()
        .expect("Failed to execute korrect setup.");

    assert!(output.status.success());

    let expected_stdout = "You'll need to add".into();
    assert_on_outputs(output.clone(), None, Some(expected_stdout));
    let expected_stdout = "export PATH".into();
    assert_on_outputs(output, None, Some(expected_stdout));
}

#[test]
fn test_korrect_does_not_remind_user_about_path_when_path_includes_korrect_bin_directory() {
    init_logger();
    let test_env = setup_testenv();
    let korrect_bin_dir = test_env.home.join(".korrect").join("bin");

    // Run the `korrect setup` command
    let output = cargo::cargo_bin_cmd!("korrect")
        .arg("setup")
        .env("SHELL", "zsh")
        .env(
            "PATH",
            format!(
                "{}:{}",
                korrect_bin_dir.to_str().unwrap(),
                env::var_os("PATH").unwrap_or_default().to_str().unwrap()
            ),
        )
        .env("HOME", &test_env.home)
        .output()
        .expect("Failed to execute korrect setup.");

    assert!(output.status.success());

    let expected_stdout_0 = "You'll need to add";
    let expected_stdout_1 = "export PATH";
    ////////////////////////////////////////////////////////////////////
    // TODO: introduce an "assert not in outputs" to cover the case that strings should NOT appear
    // assert_on_outputs(output.clone(), None, Some(expected_stdout_0));
    // assert_on_outputs(output, None, Some(expected_stdout_1));
    let stderr = String::from_utf8_lossy(&output.stderr);
    let stdout = String::from_utf8_lossy(&output.stdout);
    assert!(
        !(stdout.contains(expected_stdout_0) && stdout.contains(expected_stdout_1)),
        "Unexpected output: {}",
        stderr
    );
    ////////////////////////////////////////////////////////////////////
}

#[test]
fn test_force_setup_overwrites_any_existing_files_and_dirs() {
    init_logger();
    let test_env = setup_testenv().with_file(".korrect", "some content");
    assert_setup_korrect_failure(Some(&test_env), None, None);
    force_setup_korrect(Some(&test_env), None, None);
    assert_setup_korrect_failure(Some(&test_env), None, None);
    force_uninstall_korrect(Some(&test_env), None, None);
    setup_korrect(Some(&test_env), None, None);
    // Addresses the following bug:
    // ❯❯ korrect setup
    // Following directories will be overwritten:
    //   - /var/home/dlevin/.korrect
    //   - /var/home/dlevin/.cache/korrect
    //   - /var/home/dlevin/.config/korrect
    // Continue? [y/N]: y
    // Failed to create directory "/var/home/dlevin/.korrect": File exists (os error 17)
    // Failed to create directory "/var/home/dlevin/.korrect/bin": Not a directory (os error 20)
    // Created /var/home/dlevin/.cache/korrect
    // Created /var/home/dlevin/.config/korrect
    // Error: Not a directory (os error 20)
}

// FIXME: In absence of network, the tests should still pass
//   Installing korrect v0.3.0 (/var/home/dlevin/Documents/Coding/korrect)
//     Updating crates.io index
// warning: spurious network error (3 tries remaining): [6] Could not resolve hostname (Could not resolve host: index.crates.io)
// warning: spurious network error (2 tries remaining): [6] Could not resolve hostname (Could not resolve host: index.crates.io)
// warning: spurious network error (1 tries remaining): [6] Could not resolve hostname (Could not resolve host: index.crates.io)
// error: failed to compile `korrect v0.3.0 (/var/home/dlevin/Documents/Coding/korrect)`, intermediate artifacts can be found at `/var/home/dlevin/.cargo/cargo`.
// To reuse those artifacts with a future compilation, set the environment variable `CARGO_TARGET_DIR` to that path.

// Caused by:
//   failed to get `anyhow` as a dependency of package `korrect v0.3.0 (/var/home/dlevin/Documents/Coding/korrect)`

// Caused by:
//   download of config.json failed

// Caused by:
//   failed to download from `https://index.crates.io/config.json`

// Caused by:
//   [6] Could not resolve hostname (Could not resolve host: index.crates.io)
// [Finished running. Exit status: 101]

// FIXME:
// ❯❯ k
// Error: io: failed to lookup address information: Temporary failure in name resolution
