use std::env;
use std::fs;
use std::path::PathBuf;

use anyhow::Result;
use clap::{CommandFactory, Parser};

use korrect::cli::{Cli, Commands, generate_completions};
use korrect::config::{self, DownloadMode, VersionStrategy};
use korrect::get_user_confirmation;

struct Korrect {
    korrect_config_file: PathBuf,
    korrect_config_path: PathBuf,
    korrect_cache_path: PathBuf,
    korrect_base_path: PathBuf,
    korrect_bin_path: PathBuf,
}

impl Korrect {
    fn new() -> Result<Self> {
        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");
        let korrect_base_path = home_dir.join(".korrect");

        Ok(Self {
            korrect_config_file,
            korrect_config_path,
            korrect_cache_path,
            korrect_base_path,
            korrect_bin_path,
        })
    }

    fn dirs(&self) -> Vec<&PathBuf> {
        vec![
            &self.korrect_base_path,
            &self.korrect_bin_path,
            &self.korrect_cache_path,
            &self.korrect_config_path,
        ]
    }

    fn uninstall(&self, force: bool) -> Result<()> {
        remove_korrect_directories(self.dirs(), force);
        Ok(())
    }

    fn setup(
        &self,
        download_mode: DownloadMode,
        version_strategy: VersionStrategy,
        manage_helm: bool,
        force: bool,
    ) -> Result<()> {
        create_korrect_directories(self.dirs(), force)?;

        // Get the path to the current executable
        let current_exe = std::env::current_exe()?;
        let current_dir = current_exe
            .parent()
            .ok_or_else(|| anyhow::anyhow!("Could not get parent directory"))?;

        let korrect_shim_source = current_dir.join("korrect-shim");
        std::os::unix::fs::symlink(&korrect_shim_source, self.korrect_bin_path.join("kubectl"))?;
        std::os::unix::fs::symlink(&korrect_shim_source, self.korrect_bin_path.join("k"))?;
        if manage_helm {
            std::os::unix::fs::symlink(&korrect_shim_source, self.korrect_bin_path.join("helm"))?;
        }

        let custom_cfg = config::Config {
            version: 2,
            korrect_shim: config::KorrectShimConfig {
                download_mode,
                version_strategy,
                manage_helm,
            },
        };
        config::write_config(&self.korrect_config_file, custom_cfg)?;
        log::debug!(
            "Wrote configuration file {:?}: {:?}",
            &self.korrect_config_file,
            custom_cfg
        );

        if let Some(paths) = env::var_os("PATH") {
            let bin_path = &self.korrect_bin_path;
            let found_bin_path = env::split_paths(&paths).any(|p| p == *bin_path);

            if !found_bin_path {
                println!(
                    "You'll need to add \"{}\" to your PATH:\n",
                    bin_path.display()
                );
                if let Some(config_path) = korrect::get_shell_config_path() {
                    let line_to_append = format!("export PATH={}:$PATH", bin_path.display());
                    println!("  echo '{}' >> {}", line_to_append, config_path.display());
                }
            }
            println!(
                "✅ Setup complete!\n\n`kubectl` (or `k`) will now run the korrect-shim. Happy clustering!"
            );
        }

        Ok(())
    }

    fn version(&self) {
        println!(concat!(
            env!("CARGO_PKG_VERSION"),
            env!("GIT_VERSION_SUFFIX")
        ))
    }

    fn config(&self) -> Result<()> {
        if !self.korrect_bin_path.exists() {
            anyhow::bail!("Korrect is not set up. Run 'korrect setup' first.");
        }

        if !self.korrect_config_file.exists() {
            eprint!("Korrect config file does not exist. Consider running 'korrect setup' first.");
        }

        println!(
            "Config file located at {}:\n{:?}",
            self.korrect_config_file.display(),
            korrect::config::load_config(&self.korrect_config_file)?
        );

        Ok(())
    }

    fn list(&self) -> Result<()> {
        if !self.korrect_bin_path.exists() {
            anyhow::bail!("Korrect is not set up. Run 'korrect setup' first.");
        }

        println!(
            "Installed components in {}:",
            self.korrect_bin_path.display()
        );
        let mut entries: Vec<_> = fs::read_dir(&self.korrect_bin_path)?
            .filter_map(Result::ok)
            .collect();

        entries.sort_by_key(|entry| entry.file_name());

        for entry in entries {
            println!("- {}", entry.file_name().to_string_lossy());
        }

        Ok(())
    }
}

fn pretty_list(dirs: Vec<&PathBuf>) -> String {
    dirs.iter()
        .map(|d| format!("  - {}", d.display()))
        .collect::<Vec<_>>()
        .join("\n")
}

fn create_korrect_directories(korrect_dirs: Vec<&PathBuf>, force: bool) -> Result<()> {
    let existing_dirs: Vec<&PathBuf> = korrect_dirs
        .iter()
        .filter(|d| d.exists())
        .copied()
        .collect();

    if !existing_dirs.is_empty() {
        let pretty_list = pretty_list(existing_dirs);
        if !force {
            println!(
                "Following directories will be overwritten:\n{}",
                pretty_list
            );
            if !get_user_confirmation("Continue?") {
                println!(
                    "Bailing out. Hint: Use --force to overwrite any existing files non-interactively.",
                );
                std::process::exit(1);
            }
        }
    }

    for dir in korrect_dirs {
        if dir.exists() {
            fs::remove_dir_all(dir).ok();
            fs::remove_file(dir).ok();
        }
        if let Err(e) = fs::create_dir_all(dir) {
            eprintln!("Failed to create directory {:?}: {}", dir, e);
        } else {
            println!("Created {}", dir.display());
        }
    }
    Ok(())
}

fn remove_korrect_directories(korrect_dirs: Vec<&PathBuf>, force: bool) {
    let existing_dirs: Vec<&PathBuf> = korrect_dirs
        .iter()
        .filter(|d| d.exists())
        .copied()
        .collect();

    if existing_dirs.is_empty() {
        println!("No korrect directories exist, that was easy.");
        return;
    }

    if !force {
        println!("Following directories will be removed:");
        for dir in &korrect_dirs {
            println!(" - {}", dir.display());
        }
        if !get_user_confirmation("Continue?") {
            return;
        }
    }

    for dir in &korrect_dirs {
        if dir.exists() {
            if let Err(e) = fs::remove_dir_all(dir) {
                eprintln!("Failed to remove directory {:?}: {}", dir, e);
            } else {
                println!("Removed directory {:?}", dir);
            }
        }
    }
}

fn main() -> Result<()> {
    env_logger::init();
    let cli = Cli::parse();

    let korrect = Korrect::new().unwrap();
    match cli.command {
        Some(Commands::Completions { shell, help }) => {
            generate_completions(shell, help)?;
        }
        Some(Commands::Uninstall { force }) => korrect.uninstall(force)?,
        Some(Commands::Setup {
            version_strategy,
            download_mode,
            no_manage_helm,
            force,
        }) => {
            let manage_helm = !no_manage_helm;
            korrect.setup(download_mode, version_strategy, manage_helm, force)?;
        }
        Some(Commands::List) => {
            korrect.list()?;
        }
        Some(Commands::Config) => {
            korrect.config()?;
        }
        Some(Commands::Version) => {
            korrect.version();
        }
        _ => {
            Cli::command().print_help()?;
            println!();
        }
    }

    Ok(())
}

#[cfg(test)]
mod korrect_tests {
    use super::*;
    use std::fs;
    use tempfile::TempDir;

    use korrect::test_utils::init_logger;

    #[test]
    fn test_create_korrect_directories() {
        init_logger();
        let temp_dir = TempDir::new().unwrap();
        let dir1 = temp_dir.path().join("dir1");
        let dir2 = temp_dir.path().join("dir2");

        let dirs = vec![&dir1, &dir2];

        // Create directories
        let _ = create_korrect_directories(dirs.clone(), true);

        // Verify directories exist
        assert!(dir1.exists());
        assert!(dir2.exists());
    }

    #[test]
    fn test_force_remove_korrect_directories() {
        init_logger();
        let force = true;
        let temp_dir = TempDir::new().unwrap();
        let dir1 = temp_dir.path().join("dir1");
        let dir2 = temp_dir.path().join("dir2");

        // Create directories
        fs::create_dir_all(&dir1).unwrap();
        fs::create_dir_all(&dir2).unwrap();

        let dirs = vec![&dir1, &dir2];

        // Remove directories
        remove_korrect_directories(dirs.clone(), force);

        // Verify directories are removed
        assert!(!dir1.exists());
        assert!(!dir2.exists());
    }
}
