//! `fxt config` -- system configuration.

use anyhow::Result;
use clap::{Parser, Subcommand};
use colored::Colorize;
use serde::Serialize;

use crate::grpc::FoxhuntClient;
use crate::output::{self, HumanReadable, OutputFormat};

#[derive(Parser, Debug)]
pub struct ConfigCommand {
    #[command(subcommand)]
    action: ConfigAction,
}

#[derive(Subcommand, Debug)]
enum ConfigAction {
    /// Get a configuration value
    Get {
        /// Configuration key
        key: String,
        /// Configuration category (optional filter)
        #[arg(long)]
        category: Option<String>,
    },
    /// Set a configuration value
    Set {
        /// Configuration key
        key: String,
        /// Configuration value
        value: String,
        /// Configuration category
        #[arg(long)]
        category: Option<String>,
    },
    /// Export full configuration as TOML
    Export {
        /// Category to export (omit for all)
        #[arg(long)]
        category: Option<String>,
    },
    /// Show resolved environment (merged CLI + env + config file)
    Env,
}

// ── Result types ──────────────────────────────────────────────────────

#[derive(Serialize)]
struct ConfigSettingInfo {
    category: String,
    key: String,
    value: String,
    data_type: String,
    hot_reload: bool,
    description: String,
}

#[derive(Serialize)]
struct GetConfigResult {
    settings: Vec<ConfigSettingInfo>,
}

impl HumanReadable for GetConfigResult {
    fn print_human(&self) {
        if self.settings.is_empty() {
            println!("{}", "No matching configuration settings found.".yellow());
            return;
        }
        for s in &self.settings {
            println!(
                "{}.{} = {}",
                s.category.dimmed(),
                s.key.bold(),
                s.value.green(),
            );
            if !s.description.is_empty() {
                println!("  # {}", s.description.dimmed());
            }
            if s.hot_reload {
                println!("  {} hot-reload enabled", "~".cyan());
            }
        }
    }
}

#[derive(Serialize)]
struct SetConfigResult {
    success: bool,
    message: String,
}

impl HumanReadable for SetConfigResult {
    fn print_human(&self) {
        if self.success {
            println!("{} {}", "OK".green().bold(), self.message);
        } else {
            println!("{} {}", "FAILED".red().bold(), self.message);
        }
    }
}

#[derive(Serialize)]
struct ExportConfigResult {
    data: String,
    format: String,
    settings_count: i32,
}

impl HumanReadable for ExportConfigResult {
    fn print_human(&self) {
        println!(
            "{} ({} settings, format={})",
            "Configuration Export".cyan().bold(),
            self.settings_count,
            self.format,
        );
        println!("{}", self.data);
    }
}

#[derive(Serialize)]
struct EnvResult {
    api_url: String,
    config_file: String,
    log_level: String,
    token_storage: String,
}

impl HumanReadable for EnvResult {
    fn print_human(&self) {
        println!("{}", "Resolved Environment".cyan().bold());
        println!("  API URL:        {}", self.api_url);
        println!("  Config File:    {}", self.config_file);
        println!("  Log Level:      {}", self.log_level);
        println!("  Token Storage:  {}", self.token_storage);
    }
}

// ── Helpers ───────────────────────────────────────────────────────────

fn config_data_type_name(value: i32) -> &'static str {
    use crate::proto::config::ConfigDataType;
    match ConfigDataType::try_from(value) {
        Ok(ConfigDataType::String) => "string",
        Ok(ConfigDataType::Number) => "number",
        Ok(ConfigDataType::Boolean) => "boolean",
        Ok(ConfigDataType::Json) => "json",
        Ok(ConfigDataType::Encrypted) => "encrypted",
        _ => "unspecified",
    }
}

// ── Execute ───────────────────────────────────────────────────────────

impl ConfigCommand {
    pub async fn execute(&self, client: &FoxhuntClient, format: OutputFormat) -> Result<()> {
        match &self.action {
            ConfigAction::Get { key, category } => {
                let resp = client
                    .config()
                    .get_configuration(crate::proto::config::GetConfigurationRequest {
                        category: category.clone(),
                        key: Some(key.clone()),
                        environment: None,
                    })
                    .await?
                    .into_inner();

                let settings: Vec<ConfigSettingInfo> = resp
                    .settings
                    .iter()
                    .map(|s| ConfigSettingInfo {
                        category: s.category.clone(),
                        key: s.key.clone(),
                        value: if s.sensitive {
                            "********".to_owned()
                        } else {
                            s.value.clone()
                        },
                        data_type: config_data_type_name(s.data_type).to_owned(),
                        hot_reload: s.hot_reload,
                        description: s.description.clone(),
                    })
                    .collect();

                let result = GetConfigResult { settings };
                output::render(&result, format)?;
            }

            ConfigAction::Set {
                key,
                value,
                category,
            } => {
                let cat = category.clone().unwrap_or_else(|| "default".to_owned());
                let resp = client
                    .config()
                    .update_configuration(crate::proto::config::UpdateConfigurationRequest {
                        category: cat,
                        key: key.clone(),
                        value: value.clone(),
                        changed_by: "fxt-cli".to_owned(),
                        change_reason: None,
                        environment: None,
                    })
                    .await?
                    .into_inner();

                let result = SetConfigResult {
                    success: resp.success,
                    message: resp.message,
                };
                output::render(&result, format)?;
            }

            ConfigAction::Export { category } => {
                let categories = match category {
                    Some(c) => vec![c.clone()],
                    None => vec![],
                };

                let resp = client
                    .config()
                    .export_configuration(crate::proto::config::ExportConfigurationRequest {
                        categories,
                        environment: None,
                        format: crate::proto::config::ExportFormat::Toml as i32,
                    })
                    .await?
                    .into_inner();

                let format_name = {
                    use crate::proto::config::ExportFormat;
                    match ExportFormat::try_from(resp.format) {
                        Ok(ExportFormat::Json) => "json",
                        Ok(ExportFormat::Yaml) => "yaml",
                        Ok(ExportFormat::Toml) => "toml",
                        Ok(ExportFormat::Env) => "env",
                        _ => "unknown",
                    }
                };

                let result = ExportConfigResult {
                    data: resp.exported_data,
                    format: format_name.to_owned(),
                    settings_count: resp.settings_count,
                };
                output::render(&result, format)?;
            }

            ConfigAction::Env => {
                let config = crate::config::FxtConfig::load().unwrap_or_default();
                let config_path = dirs::home_dir()
                    .map(|h| h.join(".foxhunt").join("config.toml"))
                    .map(|p| p.display().to_string())
                    .unwrap_or_else(|| "~/.foxhunt/config.toml".to_owned());

                let result = EnvResult {
                    api_url: config.api_url,
                    config_file: config_path,
                    log_level: config.log_level,
                    token_storage: config.token_storage,
                };
                output::render(&result, format)?;
            }
        }
        Ok(())
    }
}
