//! Policy Hook System
//!
//! Provides pre-execution, post-execution, and violation hooks for policy
//! evaluation.

use std::collections::HashMap;
use std::process::Command;

use serde::{Deserialize, Serialize};
use tokio::process::Command as AsyncCommand;
use tracing::{debug, error, info, warn};

use crate::config::HooksConfig;
use crate::error::{ControlCenterError, Result};
use crate::policies::{PolicyRequestContext, PolicyResult};

/// Hook execution context
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HookContext {
    pub hook_type: HookType,
    pub timestamp: chrono::DateTime<chrono::Utc>,
    pub request_id: String,
    pub policy_context: serde_json::Value,
    pub environment: HashMap<String, String>,
}

/// Types of policy hooks
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum HookType {
    PreExecution,
    PostExecution,
    PolicyViolation,
    AuditLog,
    Alert,
}

/// Hook execution result
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HookExecutionResult {
    pub hook_id: String,
    pub hook_type: HookType,
    pub success: bool,
    pub output: Option<String>,
    pub error_message: Option<String>,
    pub execution_time_ms: u64,
}

/// Policy hook manager
pub struct PolicyHookManager {
    config: HooksConfig,
    registered_hooks: HashMap<String, Box<dyn PolicyHook + Send + Sync>>,
}

/// Trait for custom policy hooks
#[async_trait::async_trait]
pub trait PolicyHook {
    /// Execute the hook with given context
    async fn execute(&self, context: &HookContext) -> Result<HookExecutionResult>;

    /// Get hook identifier
    fn id(&self) -> &str;

    /// Get hook type
    fn hook_type(&self) -> HookType;

    /// Check if hook should execute for given context
    fn should_execute(&self, context: &HookContext) -> bool {
        true // Default: always execute
    }
}

impl PolicyHookManager {
    /// Create new policy hook manager
    pub fn new(config: &HooksConfig) -> Self {
        let mut manager = Self {
            config: config.clone(),
            registered_hooks: HashMap::new(),
        };

        // Register built-in hooks
        manager.register_builtin_hooks();

        manager
    }

    /// Register built-in hooks
    fn register_builtin_hooks(&mut self) {
        // Audit logging hook
        let audit_hook = Box::new(AuditLogHook::new());
        self.registered_hooks
            .insert(audit_hook.id().to_string(), audit_hook);

        // Alert notification hook
        let alert_hook = Box::new(AlertNotificationHook::new());
        self.registered_hooks
            .insert(alert_hook.id().to_string(), alert_hook);

        // Metrics collection hook
        let metrics_hook = Box::new(MetricsCollectionHook::new());
        self.registered_hooks
            .insert(metrics_hook.id().to_string(), metrics_hook);
    }

    /// Register custom hook
    pub fn register_hook(&mut self, hook: Box<dyn PolicyHook + Send + Sync>) {
        self.registered_hooks.insert(hook.id().to_string(), hook);
    }

    /// Execute pre-execution hooks
    pub async fn execute_pre_hooks(
        &self,
        context: &PolicyRequestContext,
    ) -> Result<Vec<HookExecutionResult>> {
        let hook_context = self.build_hook_context(HookType::PreExecution, context, None)?;
        self.execute_hooks(&self.config.pre_execution, &hook_context)
            .await
    }

    /// Execute post-execution hooks
    pub async fn execute_post_hooks(
        &self,
        context: &PolicyRequestContext,
        result: &PolicyResult,
    ) -> Result<Vec<HookExecutionResult>> {
        let hook_context =
            self.build_hook_context(HookType::PostExecution, context, Some(result))?;
        self.execute_hooks(&self.config.post_execution, &hook_context)
            .await
    }

    /// Execute violation hooks
    pub async fn execute_violation_hooks(
        &self,
        context: &PolicyRequestContext,
        result: &PolicyResult,
    ) -> Result<Vec<HookExecutionResult>> {
        let hook_context =
            self.build_hook_context(HookType::PolicyViolation, context, Some(result))?;
        self.execute_hooks(&self.config.on_policy_violation, &hook_context)
            .await
    }

    /// Execute configured hooks
    async fn execute_hooks(
        &self,
        hook_names: &[String],
        context: &HookContext,
    ) -> Result<Vec<HookExecutionResult>> {
        let mut results = Vec::new();

        for hook_name in hook_names {
            let Some(hook) = self.registered_hooks.get(hook_name) else {
                warn!("Hook not found: {}", hook_name);
                continue;
            };

            if !hook.should_execute(context) {
                continue;
            }

            match hook.execute(context).await {
                Ok(result) => {
                    debug!("Hook {} executed successfully", hook_name);
                    results.push(result);
                }
                Err(e) => {
                    error!("Hook {} execution failed: {}", hook_name, e);
                    results.push(HookExecutionResult {
                        hook_id: hook_name.clone(),
                        hook_type: hook.hook_type(),
                        success: false,
                        output: None,
                        error_message: Some(e.to_string()),
                        execution_time_ms: 0,
                    });
                }
            }
        }

        Ok(results)
    }

    /// Build hook context from policy context
    fn build_hook_context(
        &self,
        hook_type: HookType,
        policy_context: &PolicyRequestContext,
        result: Option<&PolicyResult>,
    ) -> Result<HookContext> {
        let mut context = HookContext {
            hook_type,
            timestamp: chrono::Utc::now(),
            request_id: uuid::Uuid::new_v4().to_string(),
            policy_context: serde_json::to_value(policy_context)?,
            environment: std::env::vars().collect(),
        };

        // Add result context if available
        if let Some(policy_result) = result {
            let mut policy_json = serde_json::to_value(policy_context)?;
            if let serde_json::Value::Object(ref mut map) = policy_json {
                map.insert("result".to_string(), serde_json::to_value(policy_result)?);
            }
            context.policy_context = policy_json;
        }

        Ok(context)
    }
}

/// Built-in audit logging hook
pub struct AuditLogHook {
    id: String,
}

impl Default for AuditLogHook {
    fn default() -> Self {
        Self {
            id: "audit_log".to_string(),
        }
    }
}

impl AuditLogHook {
    pub fn new() -> Self {
        Self::default()
    }
}

#[async_trait::async_trait]
impl PolicyHook for AuditLogHook {
    async fn execute(&self, context: &HookContext) -> Result<HookExecutionResult> {
        let start_time = std::time::Instant::now();

        // Log policy evaluation for audit trail
        info!(
            target: "audit",
            hook_type = ?context.hook_type,
            request_id = %context.request_id,
            timestamp = %context.timestamp,
            policy_context = ?context.policy_context,
            "Policy evaluation audit log"
        );

        // In a production system, this would also:
        // - Write to secure audit log files
        // - Send to SIEM systems
        // - Store in audit database

        Ok(HookExecutionResult {
            hook_id: self.id.clone(),
            hook_type: HookType::AuditLog,
            success: true,
            output: Some("Audit log entry created".to_string()),
            error_message: None,
            execution_time_ms: start_time.elapsed().as_millis() as u64,
        })
    }

    fn id(&self) -> &str {
        &self.id
    }

    fn hook_type(&self) -> HookType {
        HookType::AuditLog
    }
}

/// Built-in alert notification hook
pub struct AlertNotificationHook {
    id: String,
}

impl Default for AlertNotificationHook {
    fn default() -> Self {
        Self {
            id: "alert_notification".to_string(),
        }
    }
}

impl AlertNotificationHook {
    pub fn new() -> Self {
        Self::default()
    }
}

#[async_trait::async_trait]
impl PolicyHook for AlertNotificationHook {
    async fn execute(&self, context: &HookContext) -> Result<HookExecutionResult> {
        let start_time = std::time::Instant::now();

        // Only send alerts for violations and high-priority events
        match context.hook_type {
            HookType::PolicyViolation => {
                warn!(
                    "Policy violation detected: request_id={}, timestamp={}",
                    context.request_id, context.timestamp
                );

                // In a production system, this would:
                // - Send email notifications
                // - Post to Slack/Teams
                // - Create PagerDuty incidents
                // - Update monitoring dashboards
            }
            _ => {
                debug!("Alert hook called for non-violation event");
            }
        }

        Ok(HookExecutionResult {
            hook_id: self.id.clone(),
            hook_type: HookType::Alert,
            success: true,
            output: Some("Alert processed".to_string()),
            error_message: None,
            execution_time_ms: start_time.elapsed().as_millis() as u64,
        })
    }

    fn id(&self) -> &str {
        &self.id
    }

    fn hook_type(&self) -> HookType {
        HookType::Alert
    }

    fn should_execute(&self, context: &HookContext) -> bool {
        // Only execute for violations or high-priority events
        matches!(context.hook_type, HookType::PolicyViolation)
    }
}

/// Built-in metrics collection hook
pub struct MetricsCollectionHook {
    id: String,
}

impl Default for MetricsCollectionHook {
    fn default() -> Self {
        Self {
            id: "metrics_collection".to_string(),
        }
    }
}

impl MetricsCollectionHook {
    pub fn new() -> Self {
        Self::default()
    }
}

#[async_trait::async_trait]
impl PolicyHook for MetricsCollectionHook {
    async fn execute(&self, context: &HookContext) -> Result<HookExecutionResult> {
        let start_time = std::time::Instant::now();

        // Collect metrics about policy evaluations
        debug!(
            "Collecting metrics: hook_type={:?}, timestamp={}",
            context.hook_type, context.timestamp
        );

        // In a production system, this would:
        // - Update Prometheus metrics
        // - Send to StatsD
        // - Update time-series databases
        // - Calculate performance statistics

        Ok(HookExecutionResult {
            hook_id: self.id.clone(),
            hook_type: context.hook_type.clone(),
            success: true,
            output: Some("Metrics collected".to_string()),
            error_message: None,
            execution_time_ms: start_time.elapsed().as_millis() as u64,
        })
    }

    fn id(&self) -> &str {
        &self.id
    }

    fn hook_type(&self) -> HookType {
        HookType::PostExecution
    }
}

/// External command hook for running shell scripts/programs
pub struct ExternalCommandHook {
    id: String,
    command: String,
    args: Vec<String>,
    hook_type: HookType,
}

impl ExternalCommandHook {
    pub fn new(id: String, command: String, args: Vec<String>, hook_type: HookType) -> Self {
        Self {
            id,
            command,
            args,
            hook_type,
        }
    }
}

#[async_trait::async_trait]
impl PolicyHook for ExternalCommandHook {
    async fn execute(&self, context: &HookContext) -> Result<HookExecutionResult> {
        let start_time = std::time::Instant::now();

        // Prepare environment variables
        let mut env_vars = context.environment.clone();
        env_vars.insert("HOOK_TYPE".to_string(), format!("{:?}", context.hook_type));
        env_vars.insert("REQUEST_ID".to_string(), context.request_id.clone());
        env_vars.insert("TIMESTAMP".to_string(), context.timestamp.to_rfc3339());

        // Execute external command
        let mut cmd = AsyncCommand::new(&self.command);
        cmd.args(&self.args);
        cmd.envs(&env_vars);

        // Pass policy context as JSON via stdin
        let context_json = serde_json::to_string(&context.policy_context)?;

        match cmd.output().await {
            Ok(output) => {
                let success = output.status.success();
                let stdout = String::from_utf8_lossy(&output.stdout).to_string();
                let stderr = String::from_utf8_lossy(&output.stderr).to_string();

                Ok(HookExecutionResult {
                    hook_id: self.id.clone(),
                    hook_type: self.hook_type.clone(),
                    success,
                    output: if !stdout.is_empty() {
                        Some(stdout)
                    } else {
                        None
                    },
                    error_message: if !stderr.is_empty() {
                        Some(stderr)
                    } else {
                        None
                    },
                    execution_time_ms: start_time.elapsed().as_millis() as u64,
                })
            }
            Err(e) => Ok(HookExecutionResult {
                hook_id: self.id.clone(),
                hook_type: self.hook_type.clone(),
                success: false,
                output: None,
                error_message: Some(e.to_string()),
                execution_time_ms: start_time.elapsed().as_millis() as u64,
            }),
        }
    }

    fn id(&self) -> &str {
        &self.id
    }

    fn hook_type(&self) -> HookType {
        self.hook_type.clone()
    }
}
