use crate::args::Args;
use anyhow::{Context, Result};
use dirs;
use log::{debug, error};
use serde::{Deserialize, Serialize};
use std::fs;
use std::path::PathBuf;

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(rename_all = "lowercase")]
pub enum ColumnAlignment {
    #[default]
    Left,
    Right,
    Center,
}

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(rename_all = "lowercase")]
pub enum ContentArrangement {
    #[default]
    Dynamic,
    DynamicFullWidth,
    Disabled,
}

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(rename_all = "lowercase")]
pub enum TableStyle {
    Ascii,
    Utf8,
    Utf8Full,
    #[default]
    Nothing,
    Custom(String),
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum TableColor {
    Black,
    Red,
    Green,
    Yellow,
    Blue,
    Magenta,
    Cyan,
    White,
    Gray,
    DarkRed,
    DarkGreen,
    DarkYellow,
    DarkBlue,
    DarkMagenta,
    DarkCyan,
    Rgb(u8, u8, u8),
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum TableAttribute {
    Bold,
    Dim,
    Italic,
    Underlined,
    SlowBlink,
    RapidBlink,
    Reverse,
    Hidden,
    CrossedOut,
}

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct TableConfig {
    #[serde(default)]
    pub style: TableStyle,
    #[serde(default)]
    pub modifiers: Vec<String>,
    #[serde(default)]
    pub content_arrangement: ContentArrangement,
    #[serde(default)]
    pub truncation_indicator: String,
}

#[derive(Debug, Clone, Serialize, Default)]
pub struct ColumnConfig {
    pub name: String,
    pub label: String,
    #[serde(default)]
    pub align: ColumnAlignment,
    #[serde(default)]
    pub max_len: Option<String>,
    #[serde(default)]
    pub fg_color: Option<TableColor>,
    #[serde(default)]
    pub bg_color: Option<TableColor>,
    #[serde(default)]
    pub attributes: Vec<TableAttribute>,
    #[serde(default)]
    pub padding: Option<(u16, u16)>,
}

impl<'de> serde::Deserialize<'de> for ColumnConfig {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        #[derive(Deserialize)]
        struct Helper {
            name: String,
            label: Option<String>,
            #[serde(default)]
            align: ColumnAlignment,
            #[serde(default)]
            max_len: Option<String>,
            #[serde(default)]
            fg_color: Option<TableColor>,
            #[serde(default)]
            bg_color: Option<TableColor>,
            #[serde(default)]
            attributes: Vec<TableAttribute>,
            #[serde(default)]
            padding: Option<(u16, u16)>,
        }

        let helper = Helper::deserialize(deserializer)?;
        let label = helper.label.unwrap_or_else(|| helper.name.clone());

        Ok(ColumnConfig {
            name: helper.name,
            label,
            align: helper.align,
            max_len: helper.max_len,
            fg_color: helper.fg_color,
            bg_color: helper.bg_color,
            attributes: helper.attributes,
            padding: helper.padding,
        })
    }
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ServerConfig {
    pub address: Option<String>,
    pub port: Option<u16>,
    pub username: Option<String>,
    pub password_file: Option<PathBuf>,
    pub password: Option<String>,
    pub password_hash: Option<String>,
    pub jwt_secret: Option<String>,
    pub jwt_secret_file: Option<PathBuf>,
    pub cert_file: Option<PathBuf>,
    pub key_file: Option<PathBuf>,
    pub cors_origin: Option<String>,
    pub max_body_size: Option<u64>,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct CompressionPluginConfig {
    pub name: String,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ClientConfig {
    pub url: Option<String>,
    pub username: Option<String>,
    pub password: Option<String>,
    pub jwt: Option<String>,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "server", derive(utoipa::ToSchema))]
pub struct MetaPluginConfig {
    pub name: String,
    #[serde(default)]
    #[cfg_attr(feature = "server", schema(value_type = Object))]
    pub options: std::collections::HashMap<String, serde_yaml::Value>,
    #[serde(default)]
    #[cfg_attr(feature = "server", schema(value_type = Object))]
    pub outputs: std::collections::HashMap<String, String>,
}

/// Unified settings that merges config file and CLI arguments
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Settings {
    #[serde(default)]
    pub dir: PathBuf,
    #[serde(default)]
    pub list_format: Vec<ColumnConfig>,
    #[serde(default)]
    pub table_config: TableConfig,
    #[serde(default)]
    pub human_readable: bool,
    pub output_format: Option<String>,
    #[serde(default)]
    pub quiet: bool,
    #[serde(default)]
    pub force: bool,
    pub server: Option<ServerConfig>,
    pub compression_plugin: Option<CompressionPluginConfig>,
    pub meta_plugins: Option<Vec<MetaPluginConfig>>,
    pub client: Option<ClientConfig>,
    // Non-serializable fields populated from CLI args
    #[serde(skip)]
    pub client_url: Option<String>,
    #[serde(skip)]
    pub client_username: Option<String>,
    #[serde(skip)]
    pub client_password: Option<String>,
    #[serde(skip)]
    pub client_jwt: Option<String>,
    // Metadata key-value pairs from --meta CLI flag
    #[serde(skip)]
    pub meta: Vec<(String, Option<String>)>,
    // Export filename format template (--export-filename-format)
    #[serde(skip)]
    pub export_filename_format: String,
    // Import data file path (--import-data-file)
    #[serde(skip)]
    pub import_data_file: Option<std::path::PathBuf>,
}

impl Settings {
    /// Create unified settings from config and args with proper priority
    pub fn new(args: &Args, default_dir: PathBuf) -> Result<Self> {
        debug!("CONFIG: Creating settings with default dir: {default_dir:?}");

        let config_path = if let Some(config_path) = &args.options.config {
            config_path.clone()
        } else if let Ok(env_config) = std::env::var("KEEP_CONFIG") {
            PathBuf::from(env_config)
        } else {
            let default_path = if let Ok(home_dir) = std::env::var("HOME") {
                let mut path = PathBuf::from(home_dir);
                path.push(".config");
                path.push("keep");
                path.push("config.yml");
                path
            } else {
                PathBuf::from("~/.config/keep/config.yml")
            };
            debug!("CONFIG: Using default config path: {default_path:?}");
            default_path
        };

        debug!("CONFIG: Using config path: {config_path:?}");

        let mut config_builder = config::Config::builder();

        // Load config file if it exists
        if config_path.exists() {
            debug!("CONFIG: Loading config file: {config_path:?}");
            config_builder =
                config_builder.add_source(config::File::from(config_path.clone()).required(false));
        } else {
            debug!("CONFIG: Config file does not exist: {config_path:?}");
        }

        // Add environment variables
        debug!("CONFIG: Adding environment variables");
        let env_source = config::Environment::with_prefix("KEEP")
            .separator("__")
            .ignore_empty(true);
        config_builder = config_builder.add_source(env_source);

        // Override with CLI args
        if let Some(dir) = &args.options.dir {
            debug!("CONFIG: Overriding dir with CLI arg: {dir:?}");
            config_builder = config_builder.set_override(
                "dir",
                dir.to_str()
                    .ok_or_else(|| anyhow::anyhow!("non-UTF-8 directory path"))?,
            )?;
        }

        if args.options.human_readable {
            config_builder = config_builder.set_override("human_readable", true)?;
        }

        if let Some(output_format) = &args.options.output_format {
            config_builder =
                config_builder.set_override("output_format", output_format.as_str())?;
        }

        if args.options.verbose > 0 {
            config_builder = config_builder.set_override("verbose", args.options.verbose)?;
        }

        if args.options.quiet {
            config_builder = config_builder.set_override("quiet", true)?;
        }

        if args.options.force {
            config_builder = config_builder.set_override("force", true)?;
        }

        if let Some(server_password) = &args.options.server_password {
            config_builder =
                config_builder.set_override("server.password", server_password.as_str())?;
        }

        if let Some(server_password_hash) = &args.options.server_password_hash {
            config_builder = config_builder
                .set_override("server.password_hash", server_password_hash.as_str())?;
        }

        if let Some(server_username) = &args.options.server_username {
            config_builder =
                config_builder.set_override("server.username", server_username.as_str())?;
        }

        if let Some(server_address) = &args.mode.server_address {
            config_builder =
                config_builder.set_override("server.address", server_address.as_str())?;
        }

        if let Some(server_port) = args.mode.server_port {
            config_builder = config_builder.set_override("server.port", server_port)?;
        }

        #[cfg(feature = "tls")]
        if let Some(server_cert) = &args.mode.server_cert {
            config_builder = config_builder
                .set_override("server.cert_file", server_cert.to_string_lossy().as_ref())?;
        }

        #[cfg(feature = "tls")]
        if let Some(server_key) = &args.mode.server_key {
            config_builder = config_builder
                .set_override("server.key_file", server_key.to_string_lossy().as_ref())?;
        }

        if let Some(max_body_size) = args.options.server_max_body_size {
            config_builder = config_builder.set_override("server.max_body_size", max_body_size)?;
        }

        if let Some(compression) = &args.item.compression {
            config_builder =
                config_builder.set_override("compression_plugin.name", compression.as_str())?;
        }

        // Build MetaPluginConfig entries from --meta-plugin args (name[:json])
        // These are handled after config deserialization (see below).

        let config = config_builder.build()?;
        debug!("CONFIG: Built config, attempting to deserialize");

        match config.try_deserialize::<Settings>() {
            Ok(mut settings) => {
                debug!("CONFIG: Successfully deserialized settings: {settings:?}");

                // Set defaults for list_format if not provided
                if settings.list_format.is_empty() {
                    debug!("CONFIG: Setting default list_format");
                    settings.list_format = vec![
                        ColumnConfig {
                            name: "id".to_string(),
                            label: "Item".to_string(),
                            align: ColumnAlignment::Right,
                            max_len: None,
                            fg_color: None,
                            bg_color: None,
                            attributes: Vec::new(),
                            padding: None,
                        },
                        ColumnConfig {
                            name: "time".to_string(),
                            label: "Time".to_string(),
                            align: ColumnAlignment::Right,
                            max_len: None,
                            fg_color: None,
                            bg_color: None,
                            attributes: Vec::new(),
                            padding: None,
                        },
                        ColumnConfig {
                            name: "size".to_string(),
                            label: "Size".to_string(),
                            align: ColumnAlignment::Right,
                            max_len: None,
                            fg_color: None,
                            bg_color: None,
                            attributes: Vec::new(),
                            padding: None,
                        },
                        ColumnConfig {
                            name: "meta:text_line_count".to_string(),
                            label: "Lines".to_string(),
                            align: ColumnAlignment::Right,
                            max_len: None,
                            fg_color: None,
                            bg_color: None,
                            attributes: Vec::new(),
                            padding: None,
                        },
                        ColumnConfig {
                            name: "tags".to_string(),
                            label: "Tags".to_string(),
                            align: ColumnAlignment::Left,
                            max_len: None,
                            fg_color: None,
                            bg_color: None,
                            attributes: Vec::new(),
                            padding: None,
                        },
                        ColumnConfig {
                            name: "meta:hostname_short".to_string(),
                            label: "Host".to_string(),
                            align: ColumnAlignment::Left,
                            max_len: None,
                            fg_color: None,
                            bg_color: None,
                            attributes: Vec::new(),
                            padding: None,
                        },
                        ColumnConfig {
                            name: "meta:command".to_string(),
                            label: "Command".to_string(),
                            align: ColumnAlignment::Left,
                            max_len: None,
                            fg_color: None,
                            bg_color: None,
                            attributes: Vec::new(),
                            padding: None,
                        },
                    ];
                }

                // Set default meta_plugins to include 'env' if not provided
                if settings.meta_plugins.is_none() {
                    debug!("CONFIG: Setting default meta_plugins to include 'env'");
                    settings.meta_plugins = Some(vec![MetaPluginConfig {
                        name: "env".to_string(),
                        options: std::collections::HashMap::new(),
                        outputs: std::collections::HashMap::new(),
                    }]);
                }

                // Override meta_plugins from --meta-plugin CLI args
                if !args.item.meta_plugins.is_empty() {
                    debug!("CONFIG: Overriding meta_plugins from --meta-plugin CLI args");
                    let cli_plugins: Vec<MetaPluginConfig> = args
                        .item
                        .meta_plugins
                        .iter()
                        .map(|arg| {
                            let mut options = std::collections::HashMap::new();
                            let mut outputs = std::collections::HashMap::new();
                            if let Some(serde_json::Value::Object(obj)) = &arg.options {
                                // Extract options and outputs from JSON value
                                if let Some(serde_json::Value::Object(opts_obj)) =
                                    obj.get("options")
                                {
                                    for (k, v) in opts_obj {
                                        let yaml_str = serde_json::to_string(v).unwrap_or_default();
                                        let yaml_val: serde_yaml::Value =
                                            serde_yaml::from_str(&yaml_str)
                                                .unwrap_or(serde_yaml::Value::Null);
                                        options.insert(k.clone(), yaml_val);
                                    }
                                }
                                if let Some(serde_json::Value::Object(outs_obj)) =
                                    obj.get("outputs")
                                {
                                    for (k, v) in outs_obj {
                                        let val_str = match v {
                                            serde_json::Value::String(s) => s.clone(),
                                            _ => v.to_string(),
                                        };
                                        outputs.insert(k.clone(), val_str);
                                    }
                                }
                            }
                            MetaPluginConfig {
                                name: arg.name.clone(),
                                options,
                                outputs,
                            }
                        })
                        .collect();
                    settings.meta_plugins = Some(cli_plugins);
                }

                // Override list_format from --list-format CLI arg
                if args.options.list_format != "id,time,size,tags,meta:hostname" {
                    debug!("CONFIG: Overriding list_format from --list-format CLI arg");
                    settings.list_format = Settings::parse_list_format(&args.options.list_format);
                }

                // Set dir to default if not provided or is empty
                if settings.dir == PathBuf::new() {
                    debug!("CONFIG: Setting default dir: {default_dir:?}");
                    settings.dir = default_dir;
                }

                // Populate client settings from CLI args and config
                #[cfg(feature = "client")]
                {
                    settings.client_url = args
                        .options
                        .client_url
                        .clone()
                        .or_else(|| settings.client.as_ref().and_then(|c| c.url.clone()));
                    settings.client_username = args
                        .options
                        .client_username
                        .clone()
                        .or_else(|| settings.client.as_ref().and_then(|c| c.username.clone()));
                    settings.client_password = args
                        .options
                        .client_password
                        .clone()
                        .or_else(|| settings.client.as_ref().and_then(|c| c.password.clone()));
                    settings.client_jwt = args
                        .options
                        .client_jwt
                        .clone()
                        .or_else(|| settings.client.as_ref().and_then(|c| c.jwt.clone()));
                }

                // Parse --meta key=value and bare key arguments
                settings.meta = args
                    .item
                    .meta
                    .iter()
                    .map(|s| {
                        if let Some((key, value)) = s.split_once('=') {
                            (key.to_string(), Some(value.to_string()))
                        } else {
                            (s.to_string(), None)
                        }
                    })
                    .collect();

                // Set export filename format from CLI args
                settings.export_filename_format = args.item.export_filename_format.clone();
                settings.import_data_file = args.item.import_data_file.clone();

                debug!("CONFIG: Final settings: {settings:?}");
                Ok(settings)
            }
            Err(e) => {
                error!("CONFIG: Failed to deserialize settings: {e}");
                Err(e.into())
            }
        }
    }

    pub fn default_dir() -> anyhow::Result<PathBuf> {
        let mut path =
            dirs::home_dir().ok_or_else(|| anyhow::anyhow!("No home directory found"))?;
        path.push(".keep");
        if !path.exists() {
            std::fs::create_dir_all(&path)?;
        }
        Ok(path)
    }

    /// Get server password from password_file or directly from config if configured
    pub fn get_server_password(&self) -> Result<Option<String>> {
        if let Some(server) = &self.server {
            // First check for password_file
            if let Some(password_file) = &server.password_file {
                debug!("CONFIG: Reading password from file: {password_file:?}");
                let password = fs::read(password_file)
                    .with_context(|| format!("Failed to read password file: {password_file:?}"))?;
                let end = password.len().min(4096);
                let password = String::from_utf8_lossy(&password[..end]).trim().to_string();
                return Ok(Some(password));
            }

            // Fall back to direct password field
            if let Some(password) = &server.password {
                debug!("CONFIG: Using password from config");
                return Ok(Some(password.clone()));
            }
        }
        Ok(None)
    }

    // Helper methods to access configuration values
    pub fn server_password(&self) -> Option<String> {
        self.get_server_password().ok().flatten()
    }

    pub fn server_password_hash(&self) -> Option<String> {
        self.server.as_ref().and_then(|s| s.password_hash.clone())
    }

    pub fn server_username(&self) -> Option<String> {
        self.server.as_ref().and_then(|s| s.username.clone())
    }

    /// Get JWT secret from jwt_secret_file or directly from config if configured
    pub fn get_server_jwt_secret(&self) -> Result<Option<String>> {
        if let Some(server) = &self.server {
            // First check for jwt_secret_file
            if let Some(jwt_secret_file) = &server.jwt_secret_file {
                debug!("CONFIG: Reading JWT secret from file: {jwt_secret_file:?}");
                let secret = fs::read(jwt_secret_file).with_context(|| {
                    format!("Failed to read JWT secret file: {jwt_secret_file:?}")
                })?;
                let end = secret.len().min(4096);
                let secret = String::from_utf8_lossy(&secret[..end]).trim().to_string();
                return Ok(Some(secret));
            }

            // Fall back to direct jwt_secret field
            if let Some(secret) = &server.jwt_secret {
                debug!("CONFIG: Using JWT secret from config");
                return Ok(Some(secret.clone()));
            }
        }
        Ok(None)
    }

    pub fn server_jwt_secret(&self) -> Option<String> {
        self.get_server_jwt_secret().ok().flatten()
    }

    pub fn server_address(&self) -> Option<String> {
        self.server.as_ref().and_then(|s| s.address.clone())
    }

    pub fn server_port(&self) -> Option<u16> {
        self.server.as_ref().and_then(|s| s.port)
    }

    pub fn server_cert_file(&self) -> Option<PathBuf> {
        self.server.as_ref().and_then(|s| s.cert_file.clone())
    }

    pub fn server_key_file(&self) -> Option<PathBuf> {
        self.server.as_ref().and_then(|s| s.key_file.clone())
    }

    pub fn server_cors_origin(&self) -> Option<String> {
        self.server.as_ref().and_then(|s| s.cors_origin.clone())
    }

    pub fn compression(&self) -> Option<String> {
        self.compression_plugin.as_ref().map(|c| c.name.clone())
    }

    pub fn meta_plugins_names(&self) -> Vec<String> {
        self.meta_plugins
            .as_ref()
            .map(|plugins| plugins.iter().map(|p| p.name.clone()).collect())
            .unwrap_or_default()
    }

    /// Validates the configuration against plugin schemas.
    ///
    /// Checks that:
    /// - All configured meta plugin names are valid and registered
    /// - Required options are present for each meta plugin
    /// - Compression plugin name (if set) is a valid compression type
    ///
    /// Returns a list of warning strings. An empty list means the config is valid.
    pub fn validate_config(&self) -> Vec<String> {
        use crate::common::schema::gather_meta_plugin_schemas;
        use crate::compression_engine::CompressionType;
        use strum::IntoEnumIterator;

        let mut warnings = Vec::new();

        // Validate compression plugin
        if let Some(ref comp) = self.compression_plugin {
            let valid_types: Vec<String> =
                CompressionType::iter().map(|ct| ct.to_string()).collect();
            if !valid_types.contains(&comp.name) {
                warnings.push(format!(
                    "Unknown compression_plugin.name: '{}'. Valid types: {}",
                    comp.name,
                    valid_types.join(", ")
                ));
            }
        }

        // Validate meta plugins
        if let Some(ref plugins) = self.meta_plugins {
            let schemas = gather_meta_plugin_schemas();
            let schema_map: std::collections::HashMap<&str, &crate::common::schema::PluginSchema> =
                schemas.iter().map(|s| (s.name.as_str(), s)).collect();

            for plugin in plugins {
                match schema_map.get(plugin.name.as_str()) {
                    Some(schema) => {
                        // Check required options
                        for opt in &schema.options {
                            if opt.required && !plugin.options.contains_key(&opt.name) {
                                warnings.push(format!(
                                    "Meta plugin '{}': missing required option '{}'",
                                    plugin.name, opt.name
                                ));
                            }
                        }
                    }
                    None => {
                        warnings.push(format!(
                            "Unknown meta plugin: '{}'. Available: {}",
                            plugin.name,
                            schema_map.keys().copied().collect::<Vec<_>>().join(", ")
                        ));
                    }
                }
            }
        }

        warnings
    }

    /// Parse a comma-separated column list string into Vec<ColumnConfig>.
    ///
    /// Maps known column names to their default labels and alignment.
    /// For unknown names (including meta:* columns), uses the name as its own label.
    fn parse_list_format(input: &str) -> Vec<ColumnConfig> {
        input
            .split(',')
            .map(|s| s.trim())
            .filter(|s| !s.is_empty())
            .map(|name| {
                let (label, align) = match name {
                    "id" => ("Item", ColumnAlignment::Right),
                    "time" => ("Time", ColumnAlignment::Right),
                    "size" => ("Size", ColumnAlignment::Right),
                    "meta:text_line_count" => ("Lines", ColumnAlignment::Right),
                    "meta:token_count" => ("Tokens", ColumnAlignment::Right),
                    "tags" => ("Tags", ColumnAlignment::Left),
                    "meta:hostname_short" => ("Host", ColumnAlignment::Left),
                    "meta:hostname" => ("Host", ColumnAlignment::Left),
                    "meta:command" => ("Command", ColumnAlignment::Left),
                    "compression" => ("Compression", ColumnAlignment::Left),
                    other if other.starts_with("meta:") => {
                        let sub = other.strip_prefix("meta:").unwrap_or(other);
                        (sub, ColumnAlignment::Left)
                    }
                    other => (other, ColumnAlignment::Left),
                };
                ColumnConfig {
                    name: name.to_string(),
                    label: label.to_string(),
                    align,
                    ..Default::default()
                }
            })
            .collect()
    }
}
