//  Copyright 2024 RustFS Team
//
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.

use crate::{AuditEntry, AuditError, AuditResult};
use futures::{StreamExt, stream::FuturesUnordered};
use hashbrown::{HashMap, HashSet};
use rustfs_config::{
    DEFAULT_DELIMITER, ENABLE_KEY, ENV_PREFIX, MQTT_BROKER, MQTT_KEEP_ALIVE_INTERVAL, MQTT_PASSWORD, MQTT_QOS, MQTT_QUEUE_DIR,
    MQTT_QUEUE_LIMIT, MQTT_RECONNECT_INTERVAL, MQTT_TOPIC, MQTT_USERNAME, WEBHOOK_AUTH_TOKEN, WEBHOOK_BATCH_SIZE,
    WEBHOOK_CLIENT_CERT, WEBHOOK_CLIENT_KEY, WEBHOOK_ENDPOINT, WEBHOOK_HTTP_TIMEOUT, WEBHOOK_MAX_RETRY, WEBHOOK_QUEUE_DIR,
    WEBHOOK_QUEUE_LIMIT, WEBHOOK_RETRY_INTERVAL, audit::AUDIT_ROUTE_PREFIX,
};
use rustfs_ecstore::config::{Config, KVS};
use rustfs_targets::{
    Target, TargetError,
    target::{ChannelTargetType, TargetType, mqtt::MQTTArgs, webhook::WebhookArgs},
};
use std::sync::Arc;
use std::time::Duration;
use tracing::{debug, error, info, warn};
use url::Url;

/// Registry for managing audit targets
pub struct AuditRegistry {
    /// Storage for created targets
    targets: HashMap<String, Box<dyn Target<AuditEntry> + Send + Sync>>,
}

impl Default for AuditRegistry {
    fn default() -> Self {
        Self::new()
    }
}

impl AuditRegistry {
    /// Creates a new AuditRegistry
    pub fn new() -> Self {
        Self { targets: HashMap::new() }
    }

    /// Creates all audit targets from system configuration and environment variables.
    /// This method processes the creation of each target concurrently as follows:
    /// 1. Iterate through supported target types (webhook, mqtt).
    /// 2. For each type, resolve its configuration from file and environment variables.
    /// 3. Identify all target instance IDs that need to be created.
    /// 4. Merge configurations with precedence: ENV > file instance > file default.
    /// 5. Create async tasks for enabled instances.
    /// 6. Execute tasks concurrently and collect successful targets.
    /// 7. Persist successful configurations back to system storage.
    pub async fn create_targets_from_config(
        &mut self,
        config: &Config,
    ) -> AuditResult<Vec<Box<dyn Target<AuditEntry> + Send + Sync>>> {
        // Collect only environment variables with the relevant prefix to reduce memory usage
        let all_env: Vec<(String, String)> = std::env::vars().filter(|(key, _)| key.starts_with(ENV_PREFIX)).collect();

        // A collection of asynchronous tasks for concurrently executing target creation
        let mut tasks = FuturesUnordered::new();
        // let final_config = config.clone();

        // Record the defaults for each segment so that the segment can eventually be rebuilt
        let mut section_defaults: HashMap<String, KVS> = HashMap::new();

        // Supported target types for audit
        let target_types = vec![ChannelTargetType::Webhook.as_str(), ChannelTargetType::Mqtt.as_str()];

        // 1. Traverse all target types and process them
        for target_type in target_types {
            let span = tracing::Span::current();
            span.record("target_type", target_type);
            info!(target_type = %target_type, "Starting audit target type processing");

            // 2. Prepare the configuration source
            let section_name = format!("{AUDIT_ROUTE_PREFIX}{target_type}").to_lowercase();
            let file_configs = config.0.get(&section_name).cloned().unwrap_or_default();
            let default_cfg = file_configs.get(DEFAULT_DELIMITER).cloned().unwrap_or_default();
            debug!(?default_cfg, "Retrieved default configuration");

            // Save defaults for eventual write back
            section_defaults.insert(section_name.clone(), default_cfg.clone());

            // Get valid fields for the target type
            let valid_fields = match target_type {
                "webhook" => get_webhook_valid_fields(),
                "mqtt" => get_mqtt_valid_fields(),
                _ => {
                    warn!(target_type = %target_type, "Unknown target type, skipping");
                    continue;
                }
            };
            debug!(?valid_fields, "Retrieved valid configuration fields");

            // 3. Resolve instance IDs and configuration overrides from environment variables
            let mut instance_ids_from_env = HashSet::new();
            let mut env_overrides: HashMap<String, HashMap<String, String>> = HashMap::new();

            for (env_key, env_value) in &all_env {
                let audit_prefix = format!("{ENV_PREFIX}{AUDIT_ROUTE_PREFIX}{target_type}").to_uppercase();
                if !env_key.starts_with(&audit_prefix) {
                    continue;
                }

                let suffix = &env_key[audit_prefix.len()..];
                if suffix.is_empty() {
                    continue;
                }

                // Parse field and instance from suffix (FIELD_INSTANCE or FIELD)
                let (field_name, instance_id) = if let Some(last_underscore) = suffix.rfind('_') {
                    let potential_field = &suffix[1..last_underscore]; // Skip leading _
                    let potential_instance = &suffix[last_underscore + 1..];

                    // Check if the part before the last underscore is a valid field
                    if valid_fields.contains(&potential_field.to_lowercase()) {
                        (potential_field.to_lowercase(), potential_instance.to_lowercase())
                    } else {
                        // Treat the entire suffix as field name with default instance
                        (suffix[1..].to_lowercase(), DEFAULT_DELIMITER.to_string())
                    }
                } else {
                    // No underscore, treat as field with default instance
                    (suffix[1..].to_lowercase(), DEFAULT_DELIMITER.to_string())
                };

                if valid_fields.contains(&field_name) {
                    if instance_id != DEFAULT_DELIMITER {
                        instance_ids_from_env.insert(instance_id.clone());
                    }
                    env_overrides
                        .entry(instance_id)
                        .or_default()
                        .insert(field_name, env_value.clone());
                } else {
                    debug!(
                        env_key = %env_key,
                        field_name = %field_name,
                        "Ignoring environment variable field not found in valid fields for target type {}",
                        target_type
                    );
                }
            }
            debug!(?env_overrides, "Completed environment variable analysis");

            // 4. Determine all instance IDs that need to be processed
            let mut all_instance_ids: HashSet<String> =
                file_configs.keys().filter(|k| *k != DEFAULT_DELIMITER).cloned().collect();
            all_instance_ids.extend(instance_ids_from_env);
            debug!(?all_instance_ids, "Determined all instance IDs");

            // 5. Merge configurations and create tasks for each instance
            for id in all_instance_ids {
                // 5.1. Merge configuration, priority: Environment variables > File instance > File default
                let mut merged_config = default_cfg.clone();

                // Apply file instance configuration if available
                if let Some(file_instance_cfg) = file_configs.get(&id) {
                    merged_config.extend(file_instance_cfg.clone());
                }

                // Apply environment variable overrides
                if let Some(env_instance_cfg) = env_overrides.get(&id) {
                    let mut kvs_from_env = KVS::new();
                    for (k, v) in env_instance_cfg {
                        kvs_from_env.insert(k.clone(), v.clone());
                    }
                    merged_config.extend(kvs_from_env);
                }
                debug!(instance_id = %id, ?merged_config, "Completed configuration merge");

                // 5.2. Check if the instance is enabled
                let enabled = merged_config
                    .lookup(ENABLE_KEY)
                    .map(|v| parse_enable_value(&v))
                    .unwrap_or(false);

                if enabled {
                    info!(instance_id = %id, "Creating audit target");

                    // Create task for concurrent execution
                    let target_type_clone = target_type.to_string();
                    let id_clone = id.clone();
                    let merged_config_arc = Arc::new(merged_config.clone());
                    let task = tokio::spawn(async move {
                        let result = create_audit_target(&target_type_clone, &id_clone, &merged_config_arc).await;
                        (target_type_clone, id_clone, result, merged_config_arc)
                    });

                    tasks.push(task);

                    // Update final config with successful instance
                    // final_config.0.entry(section_name.clone()).or_default().insert(id, merged_config);
                } else {
                    info!(instance_id = %id, "Skipping disabled audit target, will be removed from final configuration");
                    // Remove disabled target from final configuration
                    // final_config.0.entry(section_name.clone()).or_default().remove(&id);
                }
            }
        }

        // 6. Concurrently execute all creation tasks and collect results
        let mut successful_targets = Vec::new();
        let mut successful_configs = Vec::new();
        while let Some(task_result) = tasks.next().await {
            match task_result {
                Ok((target_type, id, result, kvs_arc)) => match result {
                    Ok(target) => {
                        info!(target_type = %target_type, instance_id = %id, "Created audit target successfully");
                        successful_targets.push(target);
                        successful_configs.push((target_type, id, kvs_arc));
                    }
                    Err(e) => {
                        error!(target_type = %target_type, instance_id = %id, error = %e, "Failed to create audit target");
                    }
                },
                Err(e) => {
                    error!(error = %e, "Task execution failed");
                }
            }
        }

        // Rebuild in pieces based on "default items + successful instances" and overwrite writeback to ensure that deleted/disabled instances will not be "resurrected"
        if !successful_configs.is_empty() || !section_defaults.is_empty() {
            info!("Prepare to rebuild and save target configurations to the system configuration...");

            // Aggregate successful instances into segments
            let mut successes_by_section: HashMap<String, HashMap<String, KVS>> = HashMap::new();
            for (target_type, id, kvs) in successful_configs {
                let section_name = format!("{AUDIT_ROUTE_PREFIX}{target_type}").to_lowercase();
                successes_by_section
                    .entry(section_name)
                    .or_default()
                    .insert(id.to_lowercase(), (*kvs).clone());
            }

            let mut new_config = config.clone();

            // Collection of segments that need to be processed: Collect all segments where default items exist or where successful instances exist
            let mut sections: HashSet<String> = HashSet::new();
            sections.extend(section_defaults.keys().cloned());
            sections.extend(successes_by_section.keys().cloned());

            for section_name in sections {
                let mut section_map: std::collections::HashMap<String, KVS> = std::collections::HashMap::new();

                // The default entry (if present) is written back to `_`
                if let Some(default_cfg) = section_defaults.get(&section_name) {
                    if !default_cfg.is_empty() {
                        section_map.insert(DEFAULT_DELIMITER.to_string(), default_cfg.clone());
                    }
                }

                // Successful instance write back
                if let Some(instances) = successes_by_section.get(&section_name) {
                    for (id, kvs) in instances {
                        section_map.insert(id.clone(), kvs.clone());
                    }
                }

                // Empty segments are removed and non-empty segments are replaced as a whole.
                if section_map.is_empty() {
                    new_config.0.remove(&section_name);
                } else {
                    new_config.0.insert(section_name, section_map);
                }
            }

            // 7. Save the new configuration to the system
            let Some(store) = rustfs_ecstore::new_object_layer_fn() else {
                return Err(AuditError::StorageNotAvailable(
                    "Failed to save target configuration: server storage not initialized".to_string(),
                ));
            };

            match rustfs_ecstore::config::com::save_server_config(store, &new_config).await {
                Ok(_) => info!("New audit configuration saved to system successfully"),
                Err(e) => {
                    error!(error = %e, "Failed to save new audit configuration");
                    return Err(AuditError::SaveConfig(Box::new(e)));
                }
            }
        }
        Ok(successful_targets)
    }

    /// Adds a target to the registry
    pub fn add_target(&mut self, id: String, target: Box<dyn Target<AuditEntry> + Send + Sync>) {
        self.targets.insert(id, target);
    }

    /// Removes a target from the registry
    pub fn remove_target(&mut self, id: &str) -> Option<Box<dyn Target<AuditEntry> + Send + Sync>> {
        self.targets.remove(id)
    }

    /// Gets a target from the registry
    pub fn get_target(&self, id: &str) -> Option<&(dyn Target<AuditEntry> + Send + Sync)> {
        self.targets.get(id).map(|t| t.as_ref())
    }

    /// Lists all target IDs
    pub fn list_targets(&self) -> Vec<String> {
        self.targets.keys().cloned().collect()
    }

    /// Closes all targets and clears the registry
    pub async fn close_all(&mut self) -> AuditResult<()> {
        let mut errors = Vec::new();

        for (id, target) in self.targets.drain() {
            if let Err(e) = target.close().await {
                error!(target_id = %id, error = %e, "Failed to close audit target");
                errors.push(e);
            }
        }

        if !errors.is_empty() {
            return Err(AuditError::Target(errors.into_iter().next().unwrap()));
        }

        Ok(())
    }
}

/// Creates an audit target based on type and configuration
async fn create_audit_target(
    target_type: &str,
    id: &str,
    config: &KVS,
) -> Result<Box<dyn Target<AuditEntry> + Send + Sync>, TargetError> {
    match target_type {
        val if val == ChannelTargetType::Webhook.as_str() => {
            let args = parse_webhook_args(id, config)?;
            let target = rustfs_targets::target::webhook::WebhookTarget::new(id.to_string(), args)?;
            Ok(Box::new(target))
        }
        val if val == ChannelTargetType::Mqtt.as_str() => {
            let args = parse_mqtt_args(id, config)?;
            let target = rustfs_targets::target::mqtt::MQTTTarget::new(id.to_string(), args)?;
            Ok(Box::new(target))
        }
        _ => Err(TargetError::Configuration(format!("Unknown target type: {target_type}"))),
    }
}

/// Gets valid field names for webhook configuration
fn get_webhook_valid_fields() -> HashSet<String> {
    vec![
        ENABLE_KEY.to_string(),
        WEBHOOK_ENDPOINT.to_string(),
        WEBHOOK_AUTH_TOKEN.to_string(),
        WEBHOOK_CLIENT_CERT.to_string(),
        WEBHOOK_CLIENT_KEY.to_string(),
        WEBHOOK_BATCH_SIZE.to_string(),
        WEBHOOK_QUEUE_LIMIT.to_string(),
        WEBHOOK_QUEUE_DIR.to_string(),
        WEBHOOK_MAX_RETRY.to_string(),
        WEBHOOK_RETRY_INTERVAL.to_string(),
        WEBHOOK_HTTP_TIMEOUT.to_string(),
    ]
    .into_iter()
    .collect()
}

/// Gets valid field names for MQTT configuration
fn get_mqtt_valid_fields() -> HashSet<String> {
    vec![
        ENABLE_KEY.to_string(),
        MQTT_BROKER.to_string(),
        MQTT_TOPIC.to_string(),
        MQTT_USERNAME.to_string(),
        MQTT_PASSWORD.to_string(),
        MQTT_QOS.to_string(),
        MQTT_KEEP_ALIVE_INTERVAL.to_string(),
        MQTT_RECONNECT_INTERVAL.to_string(),
        MQTT_QUEUE_DIR.to_string(),
        MQTT_QUEUE_LIMIT.to_string(),
    ]
    .into_iter()
    .collect()
}

/// Parses webhook arguments from KVS configuration
fn parse_webhook_args(_id: &str, config: &KVS) -> Result<WebhookArgs, TargetError> {
    let endpoint = config
        .lookup(WEBHOOK_ENDPOINT)
        .filter(|s| !s.is_empty())
        .ok_or_else(|| TargetError::Configuration("webhook endpoint is required".to_string()))?;

    let endpoint_url =
        Url::parse(&endpoint).map_err(|e| TargetError::Configuration(format!("invalid webhook endpoint URL: {e}")))?;

    let args = WebhookArgs {
        enable: true, // Already validated as enabled
        endpoint: endpoint_url,
        auth_token: config.lookup(WEBHOOK_AUTH_TOKEN).unwrap_or_default(),
        queue_dir: config.lookup(WEBHOOK_QUEUE_DIR).unwrap_or_default(),
        queue_limit: config
            .lookup(WEBHOOK_QUEUE_LIMIT)
            .and_then(|s| s.parse().ok())
            .unwrap_or(100000),
        client_cert: config.lookup(WEBHOOK_CLIENT_CERT).unwrap_or_default(),
        client_key: config.lookup(WEBHOOK_CLIENT_KEY).unwrap_or_default(),
        target_type: TargetType::AuditLog,
    };

    args.validate()?;
    Ok(args)
}

/// Parses MQTT arguments from KVS configuration  
fn parse_mqtt_args(_id: &str, config: &KVS) -> Result<MQTTArgs, TargetError> {
    let broker = config
        .lookup(MQTT_BROKER)
        .filter(|s| !s.is_empty())
        .ok_or_else(|| TargetError::Configuration("MQTT broker is required".to_string()))?;

    let broker_url = Url::parse(&broker).map_err(|e| TargetError::Configuration(format!("invalid MQTT broker URL: {e}")))?;

    let topic = config
        .lookup(MQTT_TOPIC)
        .filter(|s| !s.is_empty())
        .ok_or_else(|| TargetError::Configuration("MQTT topic is required".to_string()))?;

    let qos = config
        .lookup(MQTT_QOS)
        .and_then(|s| s.parse::<u8>().ok())
        .and_then(|q| match q {
            0 => Some(rumqttc::QoS::AtMostOnce),
            1 => Some(rumqttc::QoS::AtLeastOnce),
            2 => Some(rumqttc::QoS::ExactlyOnce),
            _ => None,
        })
        .unwrap_or(rumqttc::QoS::AtLeastOnce);

    let args = MQTTArgs {
        enable: true, // Already validated as enabled
        broker: broker_url,
        topic,
        qos,
        username: config.lookup(MQTT_USERNAME).unwrap_or_default(),
        password: config.lookup(MQTT_PASSWORD).unwrap_or_default(),
        max_reconnect_interval: parse_duration(&config.lookup(MQTT_RECONNECT_INTERVAL).unwrap_or_else(|| "5s".to_string()))
            .unwrap_or(Duration::from_secs(5)),
        keep_alive: parse_duration(&config.lookup(MQTT_KEEP_ALIVE_INTERVAL).unwrap_or_else(|| "60s".to_string()))
            .unwrap_or(Duration::from_secs(60)),
        queue_dir: config.lookup(MQTT_QUEUE_DIR).unwrap_or_default(),
        queue_limit: config.lookup(MQTT_QUEUE_LIMIT).and_then(|s| s.parse().ok()).unwrap_or(100000),
        target_type: TargetType::AuditLog,
    };

    args.validate()?;
    Ok(args)
}

/// Parses enable value from string
fn parse_enable_value(value: &str) -> bool {
    matches!(value.to_lowercase().as_str(), "1" | "on" | "true" | "yes")
}

/// Parses duration from string (e.g., "3s", "5m")
fn parse_duration(s: &str) -> Option<Duration> {
    if let Some(stripped) = s.strip_suffix('s') {
        stripped.parse::<u64>().ok().map(Duration::from_secs)
    } else if let Some(stripped) = s.strip_suffix('m') {
        stripped.parse::<u64>().ok().map(|m| Duration::from_secs(m * 60))
    } else if let Some(stripped) = s.strip_suffix("ms") {
        stripped.parse::<u64>().ok().map(Duration::from_millis)
    } else {
        s.parse::<u64>().ok().map(Duration::from_secs)
    }
}
