//! Hook system for CLI operations
//!
//! Supports pre and post operation hooks with configurable script execution.

#![allow(dead_code)]

use anyhow::{anyhow, Result};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::Path;
use std::process::Command;

/// Hook event types
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub enum HookEvent {
    /// Pre-initialization hook
    PreInit,
    /// Post-initialization hook
    PostInit,
    /// Pre-phase transition hook
    PrePhaseTransition,
    /// Post-phase transition hook
    PostPhaseTransition,
    /// Pre-security assessment hook
    PreSecurityAssess,
    /// Post-security assessment hook
    PostSecurityAssess,
    /// Pre-checklist update hook
    PreChecklistUpdate,
    /// Post-checklist update hook
    PostChecklistUpdate,
    /// Pre-project backup hook
    PreBackup,
    /// Post-project backup hook
    PostBackup,
}

impl HookEvent {
    /// Get event name as string
    pub fn name(&self) -> &'static str {
        match self {
            Self::PreInit => "pre_init",
            Self::PostInit => "post_init",
            Self::PrePhaseTransition => "pre_phase_transition",
            Self::PostPhaseTransition => "post_phase_transition",
            Self::PreSecurityAssess => "pre_security_assess",
            Self::PostSecurityAssess => "post_security_assess",
            Self::PreChecklistUpdate => "pre_checklist_update",
            Self::PostChecklistUpdate => "post_checklist_update",
            Self::PreBackup => "pre_backup",
            Self::PostBackup => "post_backup",
        }
    }
}

/// Hook configuration entry
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HookConfig {
    /// Hook script or command to execute
    pub script: String,
    /// Whether the hook should block execution on failure
    #[serde(default = "default_blocking")]
    pub blocking: bool,
    /// Environment variables to pass to hook
    #[serde(default)]
    pub env: HashMap<String, String>,
    /// Timeout in seconds (0 = no timeout)
    #[serde(default)]
    pub timeout: u64,
}

fn default_blocking() -> bool {
    true
}

/// Hook registry managing all hooks
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct HookRegistry {
    /// Registered hooks per event
    hooks: HashMap<HookEvent, Vec<HookConfig>>,
}

impl HookRegistry {
    /// Create a new hook registry
    pub fn new() -> Self {
        Self::default()
    }

    /// Load hooks from a TOML file
    pub fn from_file(path: impl AsRef<Path>) -> Result<Self> {
        let content = std::fs::read_to_string(path)?;
        let hooks = toml::from_str(&content)?;
        Ok(hooks)
    }

    /// Register a hook for an event
    pub fn register(&mut self, event: HookEvent, config: HookConfig) {
        self.hooks.entry(event).or_default().push(config);
    }

    /// Get all hooks for an event
    pub fn get_hooks(&self, event: HookEvent) -> Option<&[HookConfig]> {
        self.hooks.get(&event).map(|v| v.as_slice())
    }

    /// Execute all hooks for an event
    pub fn execute(&self, event: HookEvent, context: &HookContext) -> Result<()> {
        if let Some(hooks) = self.get_hooks(event) {
            for hook in hooks {
                self.execute_hook(hook, event, context)?;
            }
        }
        Ok(())
    }

    /// Execute a single hook
    fn execute_hook(
        &self,
        hook: &HookConfig,
        event: HookEvent,
        context: &HookContext,
    ) -> Result<()> {
        let mut cmd = Command::new("sh");
        cmd.arg("-c").arg(&hook.script);

        // Set environment variables
        for (key, val) in &hook.env {
            cmd.env(key, val);
        }

        // Set default context variables
        cmd.env("HOOK_EVENT", event.name());
        if let Some(project_path) = &context.project_path {
            cmd.env("PROJECT_PATH", project_path);
        }
        if let Some(phase) = &context.phase {
            cmd.env("CURRENT_PHASE", phase);
        }
        if let Some(new_phase) = &context.new_phase {
            cmd.env("NEW_PHASE", new_phase);
        }

        let output = cmd.output()?;

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            if hook.blocking {
                return Err(anyhow!("Hook '{}' failed: {}", event.name(), stderr));
            } else {
                eprintln!(
                    "Warning: Non-blocking hook '{}' failed: {}",
                    event.name(),
                    stderr
                );
            }
        }

        Ok(())
    }

    /// Clear all hooks
    pub fn clear(&mut self) {
        self.hooks.clear();
    }

    /// Get number of registered hooks
    pub fn count(&self) -> usize {
        self.hooks.values().map(|v| v.len()).sum()
    }
}

/// Context provided to hooks during execution
#[derive(Debug, Clone, Default)]
pub struct HookContext {
    /// Project path
    pub project_path: Option<String>,
    /// Current phase
    pub phase: Option<String>,
    /// New phase (for transition hooks)
    pub new_phase: Option<String>,
    /// Additional metadata
    pub metadata: HashMap<String, String>,
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_hook_event_names() {
        assert_eq!(HookEvent::PreInit.name(), "pre_init");
        assert_eq!(HookEvent::PostInit.name(), "post_init");
        assert_eq!(HookEvent::PrePhaseTransition.name(), "pre_phase_transition");
    }

    #[test]
    fn test_hook_registry_register() {
        let mut registry = HookRegistry::new();
        let hook = HookConfig {
            script: "echo 'test'".to_string(),
            blocking: true,
            env: HashMap::new(),
            timeout: 0,
        };

        registry.register(HookEvent::PostInit, hook);
        assert_eq!(registry.count(), 1);
        assert!(registry.get_hooks(HookEvent::PostInit).is_some());
    }

    #[test]
    fn test_hook_registry_multiple_hooks() {
        let mut registry = HookRegistry::new();
        let hook1 = HookConfig {
            script: "echo 'test1'".to_string(),
            blocking: true,
            env: HashMap::new(),
            timeout: 0,
        };
        let hook2 = HookConfig {
            script: "echo 'test2'".to_string(),
            blocking: false,
            env: HashMap::new(),
            timeout: 0,
        };

        registry.register(HookEvent::PostInit, hook1);
        registry.register(HookEvent::PostInit, hook2);
        assert_eq!(registry.count(), 2);

        let hooks = registry.get_hooks(HookEvent::PostInit).unwrap();
        assert_eq!(hooks.len(), 2);
    }

    #[test]
    fn test_hook_context() {
        let ctx = HookContext {
            project_path: Some("/path/to/project".to_string()),
            phase: Some("planning".to_string()),
            new_phase: Some("design".to_string()),
            ..Default::default()
        };

        assert_eq!(ctx.project_path, Some("/path/to/project".to_string()));
        assert_eq!(ctx.phase, Some("planning".to_string()));
        assert_eq!(ctx.new_phase, Some("design".to_string()));
    }

    #[test]
    fn test_hook_registry_clear() {
        let mut registry = HookRegistry::new();
        let hook = HookConfig {
            script: "echo 'test'".to_string(),
            blocking: true,
            env: HashMap::new(),
            timeout: 0,
        };

        registry.register(HookEvent::PostInit, hook);
        assert_eq!(registry.count(), 1);

        registry.clear();
        assert_eq!(registry.count(), 0);
    }
}
