use std::env;
use std::fs::{self};
use std::path::{Path, PathBuf};
use std::process::{Command as ProcessCommand, Stdio};
use std::str::FromStr;

use anyhow::{Context, Result};
use kube::Client;
use semver::Version;
use tokio::runtime::Runtime;

use korrect::cache::{
    cache_lookup, cache_lookup_and_ttl_check, cache_write, hash_kubeconfig_to_filename,
    is_ttl_valid, parse_version,
};
use korrect::config::DownloadMode;
use korrect::config::VersionStrategy;
use korrect::managed_binary::ManagedBinary;
use korrect::{self, ToK8sStr, config};
use tempfile::TempDir;

struct KorrectShim {
    managed_binary: ManagedBinary,
    config: config::Config,
    cpu_arch: String,
    korrect_bin_path: PathBuf,
    korrect_cache_path: PathBuf,
    os: String,
    shim_version: String,
}

impl KorrectShim {
    fn new(config: Option<config::Config>, bin: Option<ManagedBinary>) -> Result<Self> {
        let shim_version =
            concat!(env!("CARGO_PKG_VERSION"), env!("GIT_VERSION_SUFFIX")).to_owned();
        let home_dir = dirs::home_dir().ok_or_else(|| {
            std::io::Error::new(std::io::ErrorKind::NotFound, "Home directory not found")
        })?;
        let config_dir: PathBuf = dirs::config_dir().ok_or_else(|| {
            std::io::Error::new(std::io::ErrorKind::NotFound, "Config directory not found")
        })?;
        let cache_dir: PathBuf = dirs::cache_dir().ok_or_else(|| {
            std::io::Error::new(std::io::ErrorKind::NotFound, "Cache directory not found")
        })?;
        let korrect_config_path = config_dir.join("korrect");
        let korrect_config_file = korrect_config_path.join("config.toml");
        let korrect_cache_path = cache_dir.join("korrect");
        let korrect_bin_path = home_dir.join(".korrect").join("bin");

        fs::create_dir_all(&korrect_cache_path).ok();
        fs::create_dir_all(&korrect_bin_path).ok();

        let os = korrect::detect_os();
        let cpu_arch = korrect::detect_cpu_arch();

        let first_arg = env::args().next();
        let binary_name = first_arg
            .as_deref()
            .and_then(|path| Path::new(path).file_name())
            .and_then(|name| name.to_str())
            .unwrap_or("unknown");

        log::debug!("ManagedBinary Name: {}", binary_name);
        let managed_binary = bin.unwrap_or(ManagedBinary::from_str(binary_name)?);

        // let cfg = config.unwrap_or(korrect::config::load_config(&korrect_config_file)?);
        let cfg =
            config.unwrap_or_else(
                || match korrect::config::load_config(&korrect_config_file) {
                    Ok(c) => c,
                    Err(e) => {
                        eprintln!(
                            "❌ Failed to load configuration from {}:\n{}\nConsider running 'korrect setup --force' to replace it.",
                            korrect_config_file.display(),
                            e
                        );
                        std::process::exit(1);
                    }
                },
            );
        log::debug!("Download mode: {:?}", cfg.korrect_shim.download_mode);
        log::debug!("Version strategy: {:?}", cfg.korrect_shim.version_strategy);

        Ok(Self {
            managed_binary,
            cpu_arch,
            korrect_bin_path,
            korrect_cache_path,
            config: cfg,
            os,
            shim_version,
        })
    }

    fn get_current_stable_managed_binary_version(&self, minor: Option<u64>) -> Result<Version> {
        let suffix = minor
            .map(|n| format!("-{}", &n.to_string()))
            .unwrap_or("".to_string());
        let cache_filename = &format!("{}{}", self.managed_binary, suffix);
        let cache_file = self.korrect_cache_path.join(cache_filename);

        if let Some(version) = cache_lookup_and_ttl_check(&cache_file) {
            return Ok(version);
        }

        match self.managed_binary.get_latest_version_for_minor(minor) {
            Ok(version) => {
                cache_write(&cache_file, &version)?;
                Ok(version)
            }
            Err(e) => {
                if let Some((cached_version, _)) = cache_lookup(&cache_file) {
                    log::debug!("Failed to get stable version: {}. Using cached version.", e);
                    Ok(cached_version)
                } else {
                    Err(anyhow::anyhow!(e))
                }
            }
        }
    }

    fn get_kubeconfig_file() -> Result<PathBuf> {
        let kubeconf_file = std::env::var("KUBECONFIG").ok();

        match kubeconf_file {
            Some(config) => {
                let path = PathBuf::from_str(&config)?;
                Ok(path)
            }
            None => Ok(dirs::home_dir()
                .expect("Home directory not found.")
                .join(".kube")
                .join("config")),
        }
    }

    fn request_cluster_api_version() -> Result<String> {
        let rt = Runtime::new()?;
        rt.block_on(async {
            let client = Client::try_default().await?;
            let version = client.apiserver_version().await?;
            Ok(format!("v{}", version.git_version))
        })
    }

    fn get_cluster_version(&self, kubeconfig: Option<PathBuf>) -> Result<Option<Version>> {
        let kubeconfig = kubeconfig.unwrap_or(Self::get_kubeconfig_file()?);
        let cache_file = self.get_cluster_version_cache_file(&kubeconfig)?;

        log::debug!(
            "cache_file for kubeconfig [{}] is [{}].",
            &kubeconfig.display(),
            &cache_file.to_str().unwrap_or("<ERROR Unwrapping>")
        );

        // Try reading from cache first
        if let Some((version, ttl)) = cache_lookup(&cache_file) {
            if is_ttl_valid(ttl) {
                return Ok(Some(version));
            }
        }

        if let Ok(k8s_version_str) = Self::request_cluster_api_version() {
            let normed_version = korrect::normalize_k8s_version_string(&k8s_version_str)?;
            let parsed_version = parse_version(&normed_version)?;

            cache_write(&cache_file, &parsed_version)?;

            let version = parse_version(&normed_version)?;
            Ok(Some(version))
        } else {
            Ok(None)
        }
    }

    fn get_cluster_version_cache_file(&self, kubeconfig: &PathBuf) -> Result<PathBuf> {
        let filename = hash_kubeconfig_to_filename(kubeconfig)?;
        Ok(self.korrect_cache_path.join(filename))
    }

    fn download_managed_binary_if_missing(&self, version: &Version) -> Result<PathBuf> {
        let target_path =
            self.korrect_bin_path
                .join(format!("{}-{}", self.managed_binary, version.to_k8s_str()));

        if target_path.exists() {
            log::debug!(
                "Target binary [{}] exists. Skipping download.",
                target_path.display()
            );
            return Ok(target_path);
        }

        let dl_url =
            self.managed_binary
                .bin_url(&version.to_k8s_str(), &self.os, &self.cpu_arch)?;

        match self.managed_binary {
            ManagedBinary::Helm => {
                let tmpd = TempDir::new()?;
                let tmp = &tmpd.path().to_path_buf().join("helm.tgz");
                log::debug!("Downloading {} to {}", dl_url, tmp.display());
                korrect::network::download_file_with_progress(&dl_url, tmp)
                    .context("Failed to download file")?;
                ManagedBinary::extract_helm_from_tar_gz(tmp, &target_path)?;
                log::debug!("Removing tmpd: {:?}", tmpd);
            }
            ManagedBinary::Kubectl => {
                log::debug!("Downloading {} to {}", dl_url, target_path.display());
                korrect::network::download_file_with_progress(&dl_url, &target_path)
                    .context("Failed to download file")?;
            }
            _ => {}
        };

        Ok(target_path)
    }

    // Make this into a method on the managed binary,
    fn offer_managed_binary_version(&self, version: &Version) -> Result<Option<PathBuf>> {
        let filename = format!("{}-{}", self.managed_binary, version.to_k8s_str());
        let target_path = self.korrect_bin_path.join(&filename);
        if target_path.exists() {
            Ok(Some(target_path))
        } else {
            Ok(None)
        }
    }

    fn offer_latest_managed_binary_version(&self) -> Result<Option<PathBuf>> {
        let binaries = korrect::list_binaries(&self.korrect_bin_path, &self.managed_binary)?;
        let version = korrect::find_most_recent_version(&binaries)?;
        Ok(version)
    }

    fn offer_latest_compatible_managed_binary_version(
        &self,
        cluster_version: &Version,
    ) -> Result<Option<PathBuf>> {
        log::debug!("Cluster Version: {}", cluster_version);
        let binaries = korrect::list_binaries(&self.korrect_bin_path, &self.managed_binary)?;
        let compatible =
            korrect::find_most_recent_compatible_version(&binaries, Some(cluster_version))?;

        Ok(compatible)
    }

    fn print_no_managed_bin_warning(&self) {
        eprintln!(
            "⚠️ No compatible {} found and downloads are disabled. \
             Either manually place a {} binary in [{}] or update the korrect configuration to enable downloads.",
            self.managed_binary,
            self.managed_binary,
            self.korrect_bin_path
                .to_str()
                .unwrap_or("the korrect bin directory")
        );
    }

    fn print_no_managed_bin_warning_for_version(&self, version: &Version) {
        eprintln!(
            "⚠️ Exact {} version {} not found and downloads are disabled. \
             Either manually place a {} binary in [{}] or update the korrect configuration to enable downloads.",
            version,
            self.managed_binary,
            self.managed_binary,
            self.korrect_bin_path
                .to_str()
                .unwrap_or("the korrect bin directory")
        );
    }

    // FIXME: This whole thing needs to be reviewed
    fn resolve_latest_cluster_version(&self, cluster_version: &Version) -> Result<Option<PathBuf>> {
        let stable_bin_version = self.get_current_stable_managed_binary_version(None)?;
        let binary_version = self
            .managed_binary
            .latest_bin_version_from_cluster_version(cluster_version, &stable_bin_version)
            .unwrap_or_else(|| {
                panic!(
                    "No binary version for {} exists for cluster version {}",
                    self.managed_binary, cluster_version
                )
            });
        let binary = match self.config.korrect_shim.download_mode {
            DownloadMode::Never => self
                .offer_latest_compatible_managed_binary_version(cluster_version)?
                .or_else(|| {
                    self.print_no_managed_bin_warning();
                    None
                }),
            DownloadMode::Always => {
                // Here's where we'd change the logic if we implement AGRESSIVE Latest version_strategy
                let bin = self.offer_latest_compatible_managed_binary_version(cluster_version)?;
                match bin {
                    Some(b) => {
                        log::debug!("Offering binary {}", b.display());
                        Some(b)
                    }
                    None => {
                        // let latest_version =
                        //     self.get_current_stable_managed_binary_version(desired_version)?;
                        log::debug!("No binary to offer. Downloading {}", binary_version);
                        Some(self.download_managed_binary_if_missing(&binary_version)?)
                    }
                }
            }
            DownloadMode::Ask => {
                // let latest_version =
                //     self.get_current_stable_managed_binary_version(desired_version)?;
                if korrect::get_user_confirmation(
                    format!("Download {} version {binary_version}?", self.managed_binary).as_str(),
                ) {
                    // let latest_version = self.get_current_stable_managed_binary_version(desired_version)?;
                    Some(self.download_managed_binary_if_missing(&binary_version)?)
                } else {
                    self.offer_latest_compatible_managed_binary_version(cluster_version)?
                        .or_else(|| {
                            self.print_no_managed_bin_warning();
                            None
                        })
                }
            }
        };

        Ok(binary)
    }

    fn resolve_exact_cluster_version(&self, cluster_version: &Version) -> Result<Option<PathBuf>> {
        // This is provided to ensure we never try to obtain a version of the binary newer than the latest available bin version
        let latest_bin_version = self.get_current_stable_managed_binary_version(None)?;
        let binary_version = self
            .managed_binary
            .exact_bin_version_from_cluster_version(cluster_version, &latest_bin_version)
            .unwrap_or_else(|| {
                panic!(
                    "No binary version for {} exists for cluster version {}",
                    self.managed_binary, cluster_version
                )
            });

        let binary = match self.config.korrect_shim.download_mode {
            DownloadMode::Never => {
                self.offer_managed_binary_version(&binary_version)?
                    .or_else(|| {
                        self.print_no_managed_bin_warning_for_version(&binary_version);
                        None
                    })
            }
            DownloadMode::Always => Some(self.download_managed_binary_if_missing(&binary_version)?),
            DownloadMode::Ask => {
                if korrect::get_user_confirmation(&format!(
                    "Download {} version {}?",
                    self.managed_binary, binary_version
                )) {
                    Some(self.download_managed_binary_if_missing(&binary_version)?)
                } else {
                    self.print_no_managed_bin_warning_for_version(&binary_version);
                    None
                }
            }
        };
        Ok(binary)
    }

    fn resolve_no_cluster_version(&self) -> Result<Option<PathBuf>> {
        let binary = match self.config.korrect_shim.download_mode {
            DownloadMode::Never => self.offer_latest_managed_binary_version()?.or_else(|| {
                self.print_no_managed_bin_warning();
                None
            }),
            DownloadMode::Always => {
                let version = self.get_current_stable_managed_binary_version(None)?;
                Some(self.download_managed_binary_if_missing(&version)?)
            }
            DownloadMode::Ask => {
                if korrect::get_user_confirmation(&format!(
                    "Download the latest stable {}?",
                    self.managed_binary
                )) {
                    let version = self.get_current_stable_managed_binary_version(None)?;
                    Some(self.download_managed_binary_if_missing(&version)?)
                } else {
                    self.offer_latest_managed_binary_version()?
                }
            }
        };
        Ok(binary)
    }

    fn run(&self) -> Result<()> {
        log::debug!("Debug logging Enabled.");
        log::info!("Running shim for {}.", self.managed_binary);
        let cluster_version = self.get_cluster_version(None)?;

        let target_binary: Option<PathBuf> = match &cluster_version {
            Some(version) => match self.config.korrect_shim.version_strategy {
                VersionStrategy::Latest => self.resolve_latest_cluster_version(version)?,
                VersionStrategy::Exact => self.resolve_exact_cluster_version(version)?,
            },
            None => self.resolve_no_cluster_version()?,
        };

        if target_binary.is_none() {
            anyhow::bail!("No target binary available.");
        }

        let target_binary = target_binary.unwrap();

        log::info!(
            "Invoking [{}] [{:?}].",
            &target_binary.display(),
            env::args()
        );

        // Execute managed binary with all arguments
        let status = ProcessCommand::new(target_binary.clone())
            .args(env::args().skip(1))
            .stdin(Stdio::inherit())
            .stdout(Stdio::inherit())
            .stderr(Stdio::inherit())
            .status()
            .unwrap_or_else(|e| {
        eprintln!(
            "❌ Failed to execute {:?}: {}\nHint: Check if the file is executable and accessible.\nConsider running 'korrect setup --force'.",
            &target_binary, e
        );
        std::process::exit(1);
    });

        let mut args = env::args().skip(1);
        if args.next() == Some("version".to_string()) && args.next().is_none() {
            println!("Korrect-shim Version: v{}", self.shim_version)
        };

        std::process::exit(status.code().unwrap_or(1));
    }
}

fn main() -> Result<()> {
    env_logger::init();
    let shim = KorrectShim::new(None, None)?;
    shim.run()
}

#[cfg(test)]
mod korrect_shim_tests {
    use super::*;
    use std::env;
    use std::fs;

    use mockito::{Matcher, Server};
    use regex::Regex;
    use serial_test::serial;
    use tempfile::TempDir;

    use korrect::ToK8sStr;
    use korrect::config::{DownloadMode, VersionStrategy};
    use korrect::test_utils::{init_logger, setup_testenv};

    fn setup_korrect_kubectl_shim(versions: &[&str]) -> Result<KorrectShim> {
        setup_korrect_shim(versions, ManagedBinary::Kubectl)
    }

    fn setup_korrect_helm_shim(versions: &[&str]) -> Result<KorrectShim> {
        setup_korrect_shim(versions, ManagedBinary::Helm)
    }

    fn setup_korrect_shim(versions: &[&str], bin: ManagedBinary) -> Result<KorrectShim> {
        let test_env = setup_testenv();
        unsafe { env::set_var("HOME", &test_env.home) };
        let cfg = Some(config::Config {
            version: 2,
            korrect_shim: config::KorrectShimConfig {
                download_mode: DownloadMode::Always,
                version_strategy: VersionStrategy::Latest,
                manage_helm: true,
            },
        });
        let shim = KorrectShim::new(cfg, Some(bin))?;
        let bin_path = &shim.korrect_bin_path;
        fs::create_dir_all(bin_path)?;

        // Create mock kubectl binaries
        for version in versions {
            let filename = format!("kubectl-{}", version);
            let filepath = bin_path.join(&filename);
            fs::write(&filepath, "mock kubectl binary content")?;
            println!("Writing to {}", &filepath.display());
        }
        Ok(shim)
    }

    #[test]
    #[serial]
    fn test_get_version_cache_file() {
        init_logger();
        for bin in [ManagedBinary::Helm, ManagedBinary::Kubectl] {
            let test_env = setup_testenv();
            unsafe { env::set_var("HOME", &test_env.home) };
            let shim = setup_korrect_shim(&[], bin).unwrap();

            // Create a temporary kubeconfig file
            let temp_kubeconfig_dir = TempDir::new().unwrap().path().join("");
            let temp_kubeconfig = temp_kubeconfig_dir.join("kubeconfig");

            fs::create_dir_all(&temp_kubeconfig_dir).unwrap();
            fs::write(&temp_kubeconfig, "test-content").unwrap();

            let cache_file = shim
                .get_cluster_version_cache_file(&temp_kubeconfig)
                .unwrap();
            assert!(cache_file.starts_with(shim.korrect_cache_path));
        }
    }

    #[test]
    #[serial]
    fn test_download_kubectl() {
        init_logger();
        let test_env = setup_testenv();
        unsafe { env::set_var("HOME", &test_env.home) };

        let mut server = Server::new();
        let url = server.url();
        let test_file_content = b"A bunch of bytes";
        let version = "v1.23.0";

        server
            .mock("GET", Matcher::Regex(r"/release/.*/kubectl".to_string()))
            .with_status(200)
            .with_header("content-type", "text/plain")
            .with_header(
                "content-length",
                test_file_content.len().to_string().as_str(),
            )
            .with_body(test_file_content)
            .create();

        unsafe {
            env::set_var("KORRECT_BASE_URL", url);
        }
        let shim = setup_korrect_kubectl_shim(&[]).unwrap();

        let parsed_version = parse_version(version).expect("Invalid Version.");
        let result = shim.download_managed_binary_if_missing(&parsed_version);

        assert!(result.is_ok(), "Test failed: result is {:?}", result);

        let target_path = shim.korrect_bin_path.join(format!("kubectl-{}", version));
        assert!(
            target_path.exists(),
            "Target path is missing: {:?}",
            target_path
        );
    }

    #[test]
    #[serial]
    #[ignore = "function download_manag... requires a tar.gz file content with proper structure"]
    fn test_download_helm() {
        init_logger();
        let test_env = setup_testenv();
        unsafe { env::set_var("HOME", &test_env.home) };

        let mut server = Server::new();
        let url = server.url();
        let test_file_content = b"A bunch of bytes";
        let version = "v3.18.0";

        server
            .mock("GET", Matcher::Regex(r"/helm-.*.tar.gz".to_string()))
            .with_status(200)
            .with_header("content-type", "text/plain")
            .with_header(
                "content-length",
                test_file_content.len().to_string().as_str(),
            )
            .with_body(test_file_content)
            .create();

        unsafe {
            env::set_var("KORRECT_BASE_URL", url);
        }
        let shim = setup_korrect_helm_shim(&[]).unwrap();

        let parsed_version = parse_version(version).expect("Invalid Version.");
        let result = shim.download_managed_binary_if_missing(&parsed_version);

        assert!(result.is_ok(), "Test failed: result is {:?}", result);

        let target_path = shim.korrect_bin_path.join(format!("helm-{}", version));
        assert!(
            target_path.exists(),
            "Target path is missing: {:?}",
            target_path
        );
    }

    #[test]
    #[serial]
    fn test_get_valid_current_stable_version() {
        init_logger();
        let mut server = Server::new();
        let url = server.url();
        let test_file_content = b"v1.2.3";

        server
            .mock("GET", "/release/stable.txt")
            .with_status(200)
            .with_header("content-type", "text/plain")
            .with_body(test_file_content)
            .create();

        unsafe { env::set_var("KORRECT_BASE_URL", url) };
        let shim = setup_korrect_kubectl_shim(&[]).unwrap();

        let version = shim.get_current_stable_managed_binary_version(None);
        assert!(version.is_ok());
        let version_str = version.unwrap();
        assert!(version_str.to_k8s_str().starts_with('v'));
        assert!(
            Regex::new(r"v\d+\.\d+\.\d+")
                .unwrap()
                .is_match(&version_str.to_k8s_str())
        );
    }

    #[test]
    #[serial]
    fn test_get_valid_current_specific_version() {
        init_logger();
        let mut server = Server::new();
        let url = server.url();
        let test_file_content = b"v1.14.3";
        let specific_minor = 14;

        server
            .mock(
                "GET",
                format!("/release/stable-1.{specific_minor}.txt").as_str(),
            )
            .with_status(200)
            .with_header("content-type", "text/plain")
            .with_body(test_file_content)
            .create();

        unsafe { env::set_var("KORRECT_BASE_URL", url) };
        let shim = setup_korrect_kubectl_shim(&[]).unwrap();

        let version = shim.get_current_stable_managed_binary_version(Some(specific_minor));
        assert!(version.is_ok());
        let version_str = version.unwrap();
        assert!(version_str.to_k8s_str().starts_with('v'));
        assert!(
            Regex::new(r"v\d+\.\d+\.\d+")
                .unwrap()
                .is_match(&version_str.to_k8s_str())
        );
    }

    #[test]
    #[serial]
    fn test_get_invalid_current_stable_version() {
        init_logger();
        let mut server = Server::new();
        let url = server.url();
        let test_file_content = b"Something invalid";

        server
            .mock("GET", "/release/stable.txt")
            .with_status(200)
            .with_header("content-type", "text/plain")
            .with_body(test_file_content)
            .create();

        unsafe {
            env::set_var("KORRECT_BASE_URL", url);
        }
        let shim = setup_korrect_kubectl_shim(&[]).unwrap();

        let version = shim.get_current_stable_managed_binary_version(None);
        log::debug!("Version is: {:?}", version);
        assert!(version.is_err_and(|x| {
            x.to_string()
                .starts_with("Invalid version format from upstream")
        }));
    }

    #[test]
    #[serial]
    fn test_get_cluster_version_with_cache() {
        init_logger();
        let test_env = setup_testenv();
        unsafe { env::set_var("HOME", &test_env.home) };
        let shim = setup_korrect_kubectl_shim(&[]).unwrap();

        // Create a cached cluster version
        let temp_kubeconfig = test_env.cache.join("kubeconfig");
        let cache_file = shim
            .get_cluster_version_cache_file(&temp_kubeconfig)
            .unwrap();
        let expected_version = parse_version("v1.23.0").unwrap();
        let _ = cache_write(&cache_file, &expected_version);

        let version = shim
            .get_cluster_version(Some(temp_kubeconfig))
            .unwrap()
            .unwrap();
        assert_eq!(version, expected_version);
        assert_eq!(version.to_k8s_str(), "v1.23.0");
        assert_eq!(version.to_string(), "1.23.0");
    }

    #[test]
    #[serial]
    fn test_exact_version_match() -> Result<()> {
        init_logger();
        let shim = setup_korrect_kubectl_shim(&["v1.25.0", "v1.24.0", "v1.26.0"])?;
        let version = parse_version("v1.25.0")?;
        let result = shim.offer_latest_compatible_managed_binary_version(&version)?;

        assert!(result.is_some());
        let expected_value = "kubectl-v1.26.0";
        assert!(
            result
                .as_ref()
                .unwrap()
                .to_str()
                .unwrap()
                .contains(expected_value),
            "Result did not contain [{}]. Actual value: {:?}",
            expected_value,
            result
        );
        Ok(())
    }

    #[test]
    #[serial]
    fn test_compatible_version_lower() -> Result<()> {
        init_logger();
        let shim = setup_korrect_kubectl_shim(&["v1.24.5", "v1.24.0", "v1.23.0"])?;
        let version = parse_version("v1.25.0")?;
        let result = shim.offer_latest_compatible_managed_binary_version(&version)?;

        assert!(result.is_some());
        let expected_value = "kubectl-v1.24.5";
        assert!(
            result
                .as_ref()
                .unwrap()
                .to_str()
                .unwrap()
                .contains(expected_value),
            "Result did not contain [{}]. Actual value: {:?}",
            expected_value,
            result
        );
        Ok(())
    }

    #[test]
    #[serial]
    fn test_compatible_version_higher() -> Result<()> {
        init_logger();
        let shim = setup_korrect_kubectl_shim(&["v1.26.0", "v1.26.5", "v1.27.0"])?;
        let version = parse_version("v1.25.0")?;
        let result = shim.offer_latest_compatible_managed_binary_version(&version)?;

        assert!(result.is_some());
        let expected_value = "kubectl-v1.26.5";
        assert!(
            result
                .as_ref()
                .unwrap()
                .to_str()
                .unwrap()
                .contains(expected_value),
            "Result did not contain [{}]. Actual value: {:?}",
            expected_value,
            result
        );
        Ok(())
    }

    #[test]
    #[serial]
    fn test_no_compatible_version() -> Result<()> {
        init_logger();
        let shim = setup_korrect_kubectl_shim(&["v1.20.0", "v1.28.0"])?;
        let version = parse_version("v1.25.0")?;
        let result = shim.offer_latest_compatible_managed_binary_version(&version)?;

        assert!(result.is_none());
        Ok(())
    }

    #[test]
    #[serial]
    fn test_multiple_compatible_versions() -> Result<()> {
        init_logger();
        let shim =
            setup_korrect_kubectl_shim(&["v1.24.5", "v1.24.0", "v1.25.0", "v1.25.3", "v1.26.0"])?;
        let version = parse_version("v1.25.0")?;
        let result = shim.offer_latest_compatible_managed_binary_version(&version)?;

        assert!(result.is_some());
        // Should choose the most recent compatible version
        let expected_value = "kubectl-v1.26.0";
        assert!(
            result
                .as_ref()
                .unwrap()
                .to_str()
                .unwrap()
                .contains(expected_value),
            "Result did not contain [{}]. Actual value: {:?}",
            expected_value,
            result
        );
        Ok(())
    }
}
// Architectural notes:
//
// Versions must explictly refer to either the k8s cluster or the managed binary.
//
// There must be a mapping between the k8s cluster version and the managed binary version:
//   This mapping should respect either "latest" or "exact" semantics
//
// A given k8s cluster version defines the range of compatible managed binary versions. Within this range, we may already have one or more versions that have already been downloaded and can be offered. If none can be offered, then we may need to download one if the user has allowed this (DownloadMode)
