use std::path::PathBuf;

use clap::Subcommand;
use directories::ProjectDirs;
use serde::{Deserialize, Serialize};
use south::error::{SouthError, SouthResult};

#[derive(Subcommand, Debug, Clone, Serialize, Deserialize)]
pub(crate) enum ConfigOperations {
    /// List all south projects
    List,

    /// Set active south project/workspace. `south update`
    /// will use update all repositories in the active
    /// configuration
    SetActive {
        /// Name of the configuration
        #[arg(short, long)]
        name: String,
    },

    /// Show the path to the south configuration file
    /// which lists all active south workspaces/projects
    ShowConfigPath,

    /// Remove a south project/workspace from the config
    Remove {
        /// Name of the configuration to remove
        #[arg(short, long)]
        name: String,
    },
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct SouthConfig {
    /// Name of the south project
    pub name: String,

    /// Path to the south project
    pub path: PathBuf,
}

#[derive(Serialize, Deserialize)]
pub(crate) struct ConfigManager {
    /// List of saved configurations
    pub configs: Vec<SouthConfig>,

    /// Name of the active configuration
    pub active_config: Option<String>,

    #[serde(skip)]
    /// Path to the configuration file
    pub config_file: PathBuf,
}

impl ConfigManager {
    /// Create a new ConfigManager
    pub fn new() -> SouthResult<Self> {
        // Cross-platform config directory
        let config_dir = ProjectDirs::from("com", "south-workspace", "south")
            .ok_or_else(|| {
                SouthError::ConfigurationError("Unable to find config directory".to_string())
            })?
            .config_dir()
            .to_path_buf();

        let config_file = config_dir.join("config.json");

        if config_file.exists() {
            // Load existing configurations
            let file = std::fs::File::open(&config_file).map_err(|e| {
                SouthError::ConfigurationError(format!("Unable to open config file: {}", e))
            })?;

            // Read the file as ConfigManager (JSON)
            let mut configs: ConfigManager = serde_json::from_reader(file).map_err(|e| {
                SouthError::ConfigurationError(format!("Unable to parse config file: {}", e))
            })?;

            configs.config_file = config_file;

            Ok(configs)
        } else {
            // Create a new configuration file
            println!("Config file not found, creating a new one");
            std::fs::create_dir_all(&config_dir).map_err(|e| {
                SouthError::ConfigurationError(format!("Unable to create config directory: {}", e))
            })?;
            let new_config = ConfigManager {
                configs: Vec::new(),
                active_config: None,
                config_file,
            };
            new_config.save()?;
            Ok(new_config)
        }
    }

    /// Set the active configuration with the given name
    /// If the configuration does not exist, this will return an error
    pub fn set_active_config(&mut self, name: &str) -> SouthResult<()> {
        // Load logic here
        if let Some(config) = self.configs.iter().find(|c| c.name == name) {
            self.active_config = Some(config.name.clone());
            // Also write to the file
            self.save()?;
            Ok(())
        } else {
            Err(SouthError::ConfigurationError(format!(
                "Configuration '{}' not found",
                name
            )))
        }
    }

    /// Save configuration to the file
    pub fn save(&self) -> SouthResult<()> {
        // Save logic here
        let json = serde_json::to_string_pretty(self).map_err(|e| {
            SouthError::ConfigurationError(format!("Unable to serialize config file: {}", e))
        })?;
        // Write the JSON to the file
        std::fs::write(&self.config_file, json).map_err(|e| {
            SouthError::ConfigurationError(format!("Unable to write config file: {}", e))
        })?;
        Ok(())
    }

    /// Create a new configuration
    pub fn create_config(&mut self, config: SouthConfig) -> SouthResult<()> {
        // Update existing entry if one with the same name exists
        if let Some(existing) = self.configs.iter_mut().find(|c| c.name == config.name) {
            existing.path = config.path.clone();
        } else {
            self.configs.push(config.clone());
        }
        // Set the configuration as active
        self.active_config = Some(config.name.clone());
        self.save()?;
        Ok(())
    }

    /// Delete a configuration
    pub fn delete_config(&mut self, name: &str) -> SouthResult<()> {
        // Check if the configuration exists
        if let Some(pos) = self.configs.iter().position(|c| c.name == name) {
            self.configs.remove(pos);

            // If the configuration being deleted is active, set active to another one if available, or None
            if !self.configs.is_empty() {
                self.active_config = Some(self.configs[0].name.clone())
            } else {
                self.active_config = None
            }
            self.save()?;
            Ok(())
        } else {
            Err(SouthError::ConfigurationError(format!(
                "Configuration '{}' not found",
                name
            )))
        }
    }

    /// Get the active configuration
    pub fn get_active_config(&self) -> Option<&SouthConfig> {
        if let Some(ref name) = self.active_config {
            self.configs.iter().find(|c| c.name == *name)
        } else {
            None
        }
    }
}

pub(crate) fn handle_config_operations(
    config: ConfigOperations,
    config_manager: &mut ConfigManager,
) -> SouthResult<()> {
    match config {
        ConfigOperations::SetActive { name } => {
            config_manager.set_active_config(&name)?;
        }
        ConfigOperations::List => {
            for config in &config_manager.configs {
                if Some(&config.name) == config_manager.active_config.as_ref() {
                    println!("[*] {}", config.name);
                } else {
                    println!("[ ] {}", config.name);
                }
            }
        }
        ConfigOperations::ShowConfigPath => {
            println!("{}", config_manager.config_file.display());
        }
        ConfigOperations::Remove { name } => {
            config_manager.delete_config(&name)?;
            println!("Removed workspace '{}'", name);
        }
    }

    Ok(())
}
