//! Data filtering service for sanitizing cloud resource data

use anyhow::{Result, anyhow};
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};
use uuid::Uuid;
use chrono::{DateTime, Utc};
use rand::Rng;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FilteredResource {
    pub resource_id: String,
    pub operation_context: OperationContext,
    pub operation_data: HashMap<String, serde_json::Value>,
    pub filtering_metadata: FilteringMetadata,
    pub validation_info: ValidationInfo,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OperationContext {
    pub service: String,
    pub operation: String,
    pub region: String,
    pub cloud: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FilteringMetadata {
    pub original_data_fields: usize,
    pub filtered_data_fields: usize,
    pub confidential_fields_removed: Vec<String>,
    pub timestamp: DateTime<Utc>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ValidationInfo {
    pub required_params: Vec<String>,
    pub optional_params: Vec<String>,
    pub validation_rules: HashMap<String, String>,
    pub missing_required_params: Vec<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OperationConfig {
    pub service: String,
    pub operation: String,
    pub confidential_data: Vec<String>,
    pub non_confidential_data: Vec<String>,
    pub required_params: Vec<String>,
    pub optional_params: Vec<String>,
    pub validation_rules: HashMap<String, String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ParsedOperation {
    pub parsed_successfully: bool,
    pub service: String,
    pub operation: String,
    pub confidence: f32,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FilteringSummary {
    pub original_data_size: usize,
    pub filtered_data_size: usize,
    pub size_reduction_bytes: usize,
    pub size_reduction_percentage: u32,
    pub confidential_fields_removed: Vec<String>,
    pub non_confidential_fields_kept: usize,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResourceSummary {
    pub service: String,
    pub operation: String,
    pub resource_id: String,
    pub region: String,
    pub cloud: String,
    pub has_required_data: bool,
    pub data_fields_available: usize,
    pub summary: String,
}

pub struct DataFilteringService {
    operation_configs: HashMap<String, OperationConfig>,
}

impl DataFilteringService {
    pub async fn new() -> Result<Self> {
        let mut service = Self {
            operation_configs: HashMap::new(),
        };
        
        service.load_default_configurations()?;
        Ok(service)
    }
    
    pub async fn initialize(&self) -> Result<()> {
        Ok(())
    }
    
    pub async fn shutdown(&self) -> Result<()> {
        Ok(())
    }

    pub fn filter_resource_for_operation(
        &self,
        resource: &HashMap<String, serde_json::Value>,
        service: &str,
        operation: &str,
    ) -> Result<FilteredResource> {
        let operation_key = format!("{}.{}", service, operation);
        let operation_config = self.operation_configs.get(&operation_key)
            .ok_or_else(|| anyhow!("Unknown operation: {}", operation_key))?;

        let empty_metadata = serde_json::Map::new();
        let metadata = resource.get("metadata")
            .and_then(|m| m.as_object())
            .unwrap_or(&empty_metadata);

        let mut filtered = FilteredResource {
            resource_id: self.extract_safe_resource_id(resource, service)?,
            operation_context: OperationContext {
                service: service.to_string(),
                operation: operation.to_string(),
                region: metadata.get("region")
                    .and_then(|r| r.as_str())
                    .unwrap_or("unknown")
                    .to_string(),
                cloud: metadata.get("cloud")
                    .and_then(|c| c.as_str())
                    .unwrap_or("aws")
                    .to_string(),
            },
            operation_data: HashMap::new(),
            filtering_metadata: FilteringMetadata {
                original_data_fields: metadata.len(),
                filtered_data_fields: 0,
                confidential_fields_removed: Vec::new(),
                timestamp: Utc::now(),
            },
            validation_info: ValidationInfo {
                required_params: operation_config.required_params.clone(),
                optional_params: operation_config.optional_params.clone(),
                validation_rules: operation_config.validation_rules.clone(),
                missing_required_params: Vec::new(),
            },
        };

        self.filter_metadata(resource, operation_config, &mut filtered);
        self.add_validation_info(&mut filtered, operation_config);

        Ok(filtered)
    }

    pub fn filter_resource_for_parsed_operation(
        &self,
        resource: &HashMap<String, serde_json::Value>,
        parsed_operation: &ParsedOperation,
    ) -> Result<FilteredResource> {
        if !parsed_operation.parsed_successfully {
            return Err(anyhow!("Cannot filter data for unparsed operation"));
        }

        self.filter_resource_for_operation(
            resource,
            &parsed_operation.service,
            &parsed_operation.operation,
        )
    }

    pub fn batch_filter_resources(
        &self,
        resources: &[HashMap<String, serde_json::Value>],
        service: &str,
        operation: &str,
    ) -> Vec<Result<FilteredResource>> {
        resources.iter()
            .map(|resource| self.filter_resource_for_operation(resource, service, operation))
            .collect()
    }

    pub fn get_filtering_summary(
        &self,
        resource: &HashMap<String, serde_json::Value>,
        filtered_data: &FilteredResource,
    ) -> FilteringSummary {
        let original_size = serde_json::to_string(resource).unwrap_or_default().len();
        let filtered_size = serde_json::to_string(filtered_data).unwrap_or_default().len();

        FilteringSummary {
            original_data_size: original_size,
            filtered_data_size: filtered_size,
            size_reduction_bytes: original_size.saturating_sub(filtered_size),
            size_reduction_percentage: if original_size > 0 {
                ((original_size.saturating_sub(filtered_size)) * 100 / original_size) as u32
            } else { 0 },
            confidential_fields_removed: filtered_data.filtering_metadata.confidential_fields_removed.clone(),
            non_confidential_fields_kept: filtered_data.filtering_metadata.filtered_data_fields,
        }
    }

    pub fn generate_safe_summary(
        &self,
        resource: &HashMap<String, serde_json::Value>,
        service: &str,
        operation: &str,
    ) -> ResourceSummary {
        match self.filter_resource_for_operation(resource, service, operation) {
            Ok(filtered) => ResourceSummary {
                service: service.to_string(),
                operation: operation.to_string(),
                resource_id: filtered.resource_id.clone(),
                region: filtered.operation_data.get("region")
                    .and_then(|r| r.as_str())
                    .unwrap_or("unknown")
                    .to_string(),
                cloud: filtered.operation_context.cloud.clone(),
                has_required_data: filtered.validation_info.missing_required_params.is_empty(),
                data_fields_available: filtered.filtering_metadata.filtered_data_fields,
                summary: format!("{}.{} for resource in {}",
                    service, operation,
                    filtered.operation_data.get("region")
                        .and_then(|r| r.as_str())
                        .unwrap_or("unknown region")
                ),
            },
            Err(error) => ResourceSummary {
                service: service.to_string(),
                operation: operation.to_string(),
                resource_id: "unknown".to_string(),
                region: "unknown".to_string(),
                cloud: "aws".to_string(),
                has_required_data: false,
                data_fields_available: 0,
                summary: format!("Failed to generate summary for {}.{}: {}", service, operation, error),
            }
        }
    }

    fn extract_safe_resource_id(
        &self,
        resource: &HashMap<String, serde_json::Value>,
        service: &str,
    ) -> Result<String> {
        let empty_metadata = serde_json::Map::new();
        let metadata = resource.get("metadata")
            .and_then(|m| m.as_object())
            .unwrap_or(&empty_metadata);

        match service {
            "aws_ec2" => {
                if let Some(instance_id) = metadata.get("instance_id").and_then(|v| v.as_str()) {
                    Ok(instance_id.to_string())
                } else {
                    Ok(self.generate_mock_id("i-", 17))
                }
            },
            "aws_rds" => {
                if let Some(resource_id) = metadata.get("resource_id").and_then(|v| v.as_str()) {
                    Ok(resource_id.to_string())
                } else if let Some(db_resource_id) = metadata.get("db_resource_id").and_then(|v| v.as_str()) {
                    Ok(db_resource_id.to_string())
                } else {
                    Ok(self.generate_mock_id("db-", 12))
                }
            },
            "aws_s3" => {
                if let Some(bucket_id) = metadata.get("bucket_id").and_then(|v| v.as_str()) {
                    Ok(bucket_id.to_string())
                } else if let Some(s3_bucket_id) = metadata.get("s3_bucket_id").and_then(|v| v.as_str()) {
                    Ok(s3_bucket_id.to_string())
                } else {
                    Ok(self.generate_mock_id("bucket-", 12))
                }
            },
            _ => {
                if let Some(id) = resource.get("id").and_then(|v| v.as_str()) {
                    if id.contains("arn:aws:") {
                        let arn_parts: Vec<&str> = id.split(':').collect();
                        if let Some(resource_part) = arn_parts.last() {
                            if self.is_aws_generated_id(resource_part) {
                                Ok(resource_part.to_string())
                            } else {
                                Ok(self.generate_mock_id("res-", 12))
                            }
                        } else {
                            Ok(self.generate_mock_id("res-", 12))
                        }
                    } else {
                        Ok(id.to_string())
                    }
                } else {
                    Ok(self.generate_mock_id("res-", 12))
                }
            }
        }
    }

    fn generate_mock_id(&self, prefix: &str, length: usize) -> String {
        let chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        let mut rng = rand::thread_rng();
        let mut result = prefix.to_string();
        
        for _ in 0..length {
            let idx = rng.gen_range(0..chars.len());
            result.push(chars.chars().nth(idx).unwrap());
        }
        
        result
    }

    fn is_aws_generated_id(&self, resource_part: &str) -> bool {
        let aws_id_pattern = regex::Regex::new(r"^[a-z]+-[A-F0-9]+$").unwrap();
        let aws_short_pattern = regex::Regex::new(r"^[a-z]-[0-9a-f]+$").unwrap();
        
        aws_id_pattern.is_match(resource_part) || aws_short_pattern.is_match(resource_part)
    }

    fn filter_metadata(
        &self,
        resource: &HashMap<String, serde_json::Value>,
        operation_config: &OperationConfig,
        filtered: &mut FilteredResource,
    ) {
        let empty_metadata = serde_json::Map::new();
        let metadata = resource.get("metadata")
            .and_then(|m| m.as_object())
            .unwrap_or(&empty_metadata);

        let confidential_fields: HashSet<&String> = operation_config.confidential_data.iter().collect();
        let non_confidential_fields: HashSet<&String> = operation_config.non_confidential_data.iter().collect();
        let required_fields: HashSet<&String> = operation_config.required_params.iter().collect();
        let optional_fields: HashSet<&String> = operation_config.optional_params.iter().collect();

        for (field_name, field_value) in metadata.iter() {
            if field_value.is_null() {
                continue;
            }

            if confidential_fields.contains(&field_name) {
                filtered.filtering_metadata.confidential_fields_removed.push(field_name.clone());
                continue;
            }

            if non_confidential_fields.contains(&field_name) &&
               (required_fields.contains(&field_name) || optional_fields.contains(&field_name)) {
                filtered.operation_data.insert(field_name.clone(), field_value.clone());
                filtered.filtering_metadata.filtered_data_fields += 1;
                continue;
            }

            if self.is_safe_field(field_name, field_value) {
                filtered.operation_data.insert(field_name.clone(), field_value.clone());
                filtered.filtering_metadata.filtered_data_fields += 1;
            } else {
                filtered.filtering_metadata.confidential_fields_removed.push(field_name.clone());
            }
        }

        if let Some(region) = metadata.get("region") {
            if !filtered.operation_data.contains_key("region") {
                filtered.operation_data.insert("region".to_string(), region.clone());
                filtered.filtering_metadata.filtered_data_fields += 1;
            }
        }

        if let Some(cloud) = metadata.get("cloud") {
            if !filtered.operation_data.contains_key("cloud") {
                filtered.operation_data.insert("cloud".to_string(), cloud.clone());
                filtered.filtering_metadata.filtered_data_fields += 1;
            }
        }
    }

    fn is_safe_field(&self, field_name: &str, field_value: &serde_json::Value) -> bool {
        let always_safe_fields = [
            "region", "cloud", "availability_zone", "state", "status",
            "instance_type", "db_instance_class", "engine", "engine_version",
            "allocated_storage", "storage_type", "port", "publicly_accessible",
            "platform", "architecture", "launch_time", "creation_date",
            "versioning_status", "storage_class"
        ];

        if always_safe_fields.contains(&field_name) {
            return true;
        }

        let never_safe_fields = [
            "name", "tags", "security_groups", "key_name", "user_data",
            "endpoint", "master_username", "master_user_password", "db_name",
            "bucket_name", "policy", "lifecycle_configuration", "cors_configuration"
        ];

        if never_safe_fields.contains(&field_name) {
            return false;
        }

        if let Some(value_str) = field_value.as_str() {
            let name_pattern = regex::Regex::new(r"^[a-zA-Z][a-zA-Z0-9\-_]*[a-zA-Z0-9]$").unwrap();
            if name_pattern.is_match(value_str) && value_str.len() > 3 {
                return false;
            }

            if self.is_aws_generated_id(value_str) {
                return true;
            }
        }

        field_value.is_number() || field_value.is_boolean()
    }

    fn add_validation_info(&self, filtered: &mut FilteredResource, operation_config: &OperationConfig) {
        for required_param in &operation_config.required_params {
            if !filtered.operation_data.contains_key(required_param) && required_param != "resource_id" {
                filtered.validation_info.missing_required_params.push(required_param.clone());
            }
        }
    }

    fn load_default_configurations(&mut self) -> Result<()> {
        // AWS EC2 configurations
        self.operation_configs.insert("aws_ec2.stop_instance".to_string(), OperationConfig {
            service: "aws_ec2".to_string(),
            operation: "stop_instance".to_string(),
            confidential_data: vec!["name".to_string(), "tags".to_string(), "security_groups".to_string()],
            non_confidential_data: vec!["region".to_string(), "state".to_string(), "instance_type".to_string()],
            required_params: vec!["instance_id".to_string(), "region".to_string()],
            optional_params: vec!["force".to_string()],
            validation_rules: HashMap::new(),
        });

        self.operation_configs.insert("aws_ec2.start_instance".to_string(), OperationConfig {
            service: "aws_ec2".to_string(),
            operation: "start_instance".to_string(),
            confidential_data: vec!["name".to_string(), "tags".to_string(), "security_groups".to_string()],
            non_confidential_data: vec!["region".to_string(), "state".to_string(), "instance_type".to_string()],
            required_params: vec!["instance_id".to_string(), "region".to_string()],
            optional_params: vec![],
            validation_rules: HashMap::new(),
        });

        // AWS RDS configurations
        self.operation_configs.insert("aws_rds.stop_db_instance".to_string(), OperationConfig {
            service: "aws_rds".to_string(),
            operation: "stop_db_instance".to_string(),
            confidential_data: vec!["db_name".to_string(), "master_username".to_string(), "endpoint".to_string()],
            non_confidential_data: vec!["region".to_string(), "engine".to_string(), "db_instance_class".to_string()],
            required_params: vec!["db_instance_identifier".to_string(), "region".to_string()],
            optional_params: vec!["snapshot_id".to_string()],
            validation_rules: HashMap::new(),
        });

        // AWS S3 configurations
        self.operation_configs.insert("aws_s3.delete_bucket".to_string(), OperationConfig {
            service: "aws_s3".to_string(),
            operation: "delete_bucket".to_string(),
            confidential_data: vec!["bucket_name".to_string(), "policy".to_string()],
            non_confidential_data: vec!["region".to_string(), "versioning_status".to_string()],
            required_params: vec!["bucket".to_string(), "region".to_string()],
            optional_params: vec!["force".to_string()],
            validation_rules: HashMap::new(),
        });

        Ok(())
    }
}
