use crate::meta_plugin::{BaseMetaPlugin, MetaPlugin, MetaPluginType};

use smart_default::SmartDefault;

#[derive(Debug, Clone, SmartDefault)]
pub struct HostnameMetaPlugin {
    #[default = false]
    is_finalized: bool,
    base: BaseMetaPlugin,
}

impl HostnameMetaPlugin {
    pub fn new(
        options: Option<std::collections::HashMap<String, serde_yaml::Value>>,
        outputs: Option<std::collections::HashMap<String, serde_yaml::Value>>,
    ) -> HostnameMetaPlugin {
        let mut base = BaseMetaPlugin::new();

        // Set default outputs
        let default_outputs = &["hostname", "hostname_full", "hostname_short"];
        base.initialize_plugin(default_outputs, &options, &outputs);

        // Start with default options - hostname is now boolean only
        base.options
            .insert("hostname".to_string(), serde_yaml::Value::Bool(true));
        base.options
            .insert("hostname_full".to_string(), serde_yaml::Value::Bool(true));
        base.options
            .insert("hostname_short".to_string(), serde_yaml::Value::Bool(true));

        // Override with provided options
        if let Some(opts) = &options {
            for (key, value) in opts {
                // Convert string "true"/"false" to boolean for hostname option
                if key == "hostname"
                    && let serde_yaml::Value::String(s) = value
                {
                    if s == "false" {
                        base.options
                            .insert(key.clone(), serde_yaml::Value::Bool(false));
                        continue;
                    } else if s == "true" {
                        base.options
                            .insert(key.clone(), serde_yaml::Value::Bool(true));
                        continue;
                    }
                }
                base.options.insert(key.clone(), value.clone());
            }
        }

        // Determine which outputs are enabled based on options
        let hostname_enabled = base
            .options
            .get("hostname")
            .and_then(|v| v.as_bool())
            .unwrap_or(true);

        let hostname_full_enabled = base
            .options
            .get("hostname_full")
            .and_then(|v| v.as_bool())
            .unwrap_or(true);

        let hostname_short_enabled = base
            .options
            .get("hostname_short")
            .and_then(|v| v.as_bool())
            .unwrap_or(true);

        // Start with default outputs, setting disabled ones to None
        let mut final_outputs = std::collections::HashMap::new();

        // Handle hostname output
        if hostname_enabled {
            final_outputs.insert(
                "hostname".to_string(),
                serde_yaml::Value::String("hostname".to_string()),
            );
        } else {
            final_outputs.insert("hostname".to_string(), serde_yaml::Value::Null);
        }

        // Handle hostname_full output
        if hostname_full_enabled {
            final_outputs.insert(
                "hostname_full".to_string(),
                serde_yaml::Value::String("hostname_full".to_string()),
            );
        } else {
            final_outputs.insert("hostname_full".to_string(), serde_yaml::Value::Null);
        }

        // Handle hostname_short output
        if hostname_short_enabled {
            final_outputs.insert(
                "hostname_short".to_string(),
                serde_yaml::Value::String("hostname_short".to_string()),
            );
        } else {
            final_outputs.insert("hostname_short".to_string(), serde_yaml::Value::Null);
        }

        // Override with provided outputs, but only if they're enabled
        if let Some(outs) = &outputs {
            for (key, value) in outs {
                // Only add if the output is enabled
                match key.as_str() {
                    "hostname" => {
                        if hostname_enabled {
                            final_outputs.insert(key.clone(), value.clone());
                        }
                    }
                    "hostname_full" => {
                        if hostname_full_enabled {
                            final_outputs.insert(key.clone(), value.clone());
                        }
                    }
                    "hostname_short" => {
                        if hostname_short_enabled {
                            final_outputs.insert(key.clone(), value.clone());
                        }
                    }
                    _ => {
                        final_outputs.insert(key.clone(), value.clone());
                    }
                }
            }
        }

        base.outputs = final_outputs;

        HostnameMetaPlugin {
            is_finalized: false,
            base,
        }
    }

    fn get_hostname(&self) -> String {
        // First get the short hostname
        let short_hostname = match gethostname::gethostname().into_string() {
            Ok(hostname) => hostname,
            Err(_) => return "unknown".to_string(),
        };

        // First try DNS resolution for both IPv4 and IPv6 addresses
        // lookup_host should handle both A and AAAA records
        if let Ok(addrs_iter) = dns_lookup::lookup_host(&short_hostname) {
            // Collect addresses into a Vec to be able to use first()
            let addrs: Vec<std::net::IpAddr> = addrs_iter.collect();

            // Try each address (both IPv4 and IPv6)
            for addr in &addrs {
                // Convert to IpAddr for lookup_addr
                let ip_addr = match addr {
                    std::net::IpAddr::V4(ipv4) => std::net::IpAddr::V4(*ipv4),
                    std::net::IpAddr::V6(ipv6) => std::net::IpAddr::V6(*ipv6),
                };
                // Perform reverse lookup for each address
                match dns_lookup::lookup_addr(&ip_addr) {
                    Ok(full_hostname) => {
                        // Only use if it's different from the short hostname and looks like a FQDN
                        if full_hostname != short_hostname && full_hostname.contains('.') {
                            return full_hostname;
                        }
                    }
                    Err(_) => continue,
                }
            }

            // If no reverse lookup worked, but we have addresses, try to construct FQDN
            // from the first address's domain if the short hostname is part of a domain
            if let Some(_first_addr) = addrs.first() {
                // For local addresses, we might not get a reverse lookup, so try to infer
                // from the system's domain name
                if let Ok(domain) = std::process::Command::new("domainname").output()
                    && domain.status.success()
                {
                    let domain_str = String::from_utf8_lossy(&domain.stdout).trim().to_string();
                    if !domain_str.is_empty() && domain_str != "(none)" {
                        return format!("{short_hostname}.{domain_str}");
                    }
                }
            }
        }

        // Fallback: try to get the FQDN using the system's hostname resolution
        // This should give us the full hostname if configured
        if let Ok(full_hostname) = std::process::Command::new("hostname").arg("-f").output()
            && full_hostname.status.success()
        {
            let full_hostname_str = String::from_utf8_lossy(&full_hostname.stdout)
                .trim()
                .to_string();
            if !full_hostname_str.is_empty() && full_hostname_str != short_hostname {
                return full_hostname_str;
            }
        }

        // Final fallback: return the short hostname
        short_hostname
    }
}

impl MetaPlugin for HostnameMetaPlugin {
    fn is_finalized(&self) -> bool {
        self.is_finalized
    }

    fn set_finalized(&mut self, finalized: bool) {
        self.is_finalized = finalized;
    }

    fn set_save_meta(&mut self, save_meta: crate::meta_plugin::SaveMetaFn) {
        self.base.set_save_meta(save_meta);
    }

    fn save_meta(&self, name: &str, value: &str) {
        self.base.save_meta(name, value);
    }

    fn finalize(&mut self) -> crate::meta_plugin::MetaPluginResponse {
        // If already finalized, don't process again
        if self.is_finalized {
            return crate::meta_plugin::MetaPluginResponse {
                metadata: Vec::new(),
                is_finalized: true,
            };
        }

        // Mark as finalized
        self.is_finalized = true;

        crate::meta_plugin::MetaPluginResponse {
            metadata: Vec::new(),
            is_finalized: true,
        }
    }

    fn update(&mut self, _data: &[u8]) -> crate::meta_plugin::MetaPluginResponse {
        // If already finalized, don't process more data
        if self.is_finalized {
            return crate::meta_plugin::MetaPluginResponse {
                metadata: Vec::new(),
                is_finalized: true,
            };
        }

        crate::meta_plugin::MetaPluginResponse {
            metadata: Vec::new(),
            is_finalized: false,
        }
    }

    fn meta_type(&self) -> MetaPluginType {
        MetaPluginType::Hostname
    }

    fn initialize(&mut self) -> crate::meta_plugin::MetaPluginResponse {
        // If already finalized, don't process again
        if self.is_finalized {
            return crate::meta_plugin::MetaPluginResponse {
                metadata: Vec::new(),
                is_finalized: true,
            };
        }

        // Get the full hostname
        let full_hostname = self.get_hostname();
        let short_hostname = full_hostname
            .split('.')
            .next()
            .unwrap_or(&full_hostname)
            .to_string();

        // Determine which hostnames to include based on options
        let hostname_enabled = self
            .base
            .options
            .get("hostname")
            .and_then(|v| v.as_bool())
            .unwrap_or(true);

        let hostname_full_enabled = self
            .base
            .options
            .get("hostname_full")
            .and_then(|v| v.as_bool())
            .unwrap_or(true);

        let hostname_short_enabled = self
            .base
            .options
            .get("hostname_short")
            .and_then(|v| v.as_bool())
            .unwrap_or(true);

        // Always use gethostname() for the 'hostname' output when enabled
        let hostname_value = if hostname_enabled {
            gethostname::gethostname()
                .into_string()
                .unwrap_or_else(|_| "unknown".to_string())
        } else {
            String::new()
        };

        // Prepare metadata to return
        let mut metadata = Vec::new();

        // Add enabled metadata to the response using process_metadata_outputs
        if hostname_enabled
            && let Some(meta_data) = crate::meta_plugin::process_metadata_outputs(
                "hostname",
                serde_yaml::Value::String(hostname_value.clone()),
                self.base.outputs(),
            )
        {
            metadata.push(meta_data);
        }
        if hostname_full_enabled
            && let Some(meta_data) = crate::meta_plugin::process_metadata_outputs(
                "hostname_full",
                serde_yaml::Value::String(full_hostname.clone()),
                self.base.outputs(),
            )
        {
            metadata.push(meta_data);
        }
        if hostname_short_enabled
            && let Some(meta_data) = crate::meta_plugin::process_metadata_outputs(
                "hostname_short",
                serde_yaml::Value::String(short_hostname.clone()),
                self.base.outputs(),
            )
        {
            metadata.push(meta_data);
        }

        // Update outputs based on enabled status
        // Handle hostname output
        if hostname_enabled {
            if let Some(output_value) = self.base.outputs_mut().get_mut("hostname") {
                *output_value = serde_yaml::Value::String(hostname_value);
            }
        } else {
            self.base
                .outputs_mut()
                .insert("hostname".to_string(), serde_yaml::Value::Null);
        }

        // Handle hostname_full output
        if hostname_full_enabled {
            if let Some(output_value) = self.base.outputs_mut().get_mut("hostname_full") {
                *output_value = serde_yaml::Value::String(full_hostname);
            }
        } else {
            self.base
                .outputs_mut()
                .insert("hostname_full".to_string(), serde_yaml::Value::Null);
        }

        // Handle hostname_short output
        if hostname_short_enabled {
            if let Some(output_value) = self.base.outputs_mut().get_mut("hostname_short") {
                *output_value = serde_yaml::Value::String(short_hostname);
            }
        } else {
            self.base
                .outputs_mut()
                .insert("hostname_short".to_string(), serde_yaml::Value::Null);
        }

        // Mark as finalized since this plugin only needs to run once
        self.is_finalized = true;

        crate::meta_plugin::MetaPluginResponse {
            metadata,
            is_finalized: true,
        }
    }

    fn outputs(&self) -> &std::collections::HashMap<String, serde_yaml::Value> {
        self.base.outputs()
    }

    fn outputs_mut(
        &mut self,
    ) -> anyhow::Result<&mut std::collections::HashMap<String, serde_yaml::Value>> {
        Ok(self.base.outputs_mut())
    }

    fn default_outputs(&self) -> Vec<String> {
        vec![
            "hostname".to_string(),
            "hostname_full".to_string(),
            "hostname_short".to_string(),
        ]
    }

    fn options(&self) -> &std::collections::HashMap<String, serde_yaml::Value> {
        self.base.options()
    }

    fn options_mut(
        &mut self,
    ) -> anyhow::Result<&mut std::collections::HashMap<String, serde_yaml::Value>> {
        Ok(self.base.options_mut())
    }
}
use crate::meta_plugin::register_meta_plugin;

// Register the plugin at module initialization time
#[ctor::ctor]
fn register_hostname_plugin() {
    register_meta_plugin(MetaPluginType::Hostname, |options, outputs| {
        Box::new(HostnameMetaPlugin::new(options, outputs))
    })
    .expect("Failed to register HostnameMetaPlugin");
}
