//! @docs ARCHITECTURE:Agent
//!
//! ### AI Assist Note
//! **Agent Hooks**: Orchestrates event-driven interceptors for mission
//! lifecycle events (e.g., pre-validation, post-analysis). Automatically
//! handles **Platform Portability** by executing `.ps1` on Windows and
//! `.sh` on Linux. Injects `AGENT_CONTEXT` and `TOOL_PARAMS` as environment
//! variables to enable decoupled metrics collection, slack notifications,
//! or custom environment setup without modifying the core Rust runner.
//!
//! ### 🔍 Debugging & Observability
//! - **Failure Path**: Hook script timeout (stuck process), permission
//!   denied on the `hooks/` directory, or non-zero exit codes from
//!   side-effect scripts causing mission interruption.
//! - **Trace Scope**: `server-rs::agent::hooks`

use crate::error::AppError;
use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};
use tokio::process::Command;

#[derive(Debug, Clone, Serialize, Deserialize)]
#[allow(dead_code)]
pub struct HookContext {
    pub agent_id: String,
    pub mission_id: Option<String>,
    pub skill: String,
}

pub struct HooksManager {
    #[allow(dead_code)]
    hooks_dir: PathBuf,
}

#[allow(dead_code)]
impl HooksManager {
    pub fn new(data_dir: &Path) -> Self {
        Self {
            hooks_dir: data_dir.join("hooks"),
        }
    }

    /// Executes all scripts in the given hook subdirectory.
    /// Returns an error if any script fails.
    pub async fn trigger_hook(
        &self,
        hook_type: &str,
        ctx: &HookContext,
        params: &serde_json::Value,
    ) -> Result<(), AppError> {
        let safe_type = crate::utils::security::sanitize_id(hook_type);
        let dir = self.hooks_dir.join(safe_type);
        if !dir.exists() {
            return Ok(());
        }

        let mut entries = tokio::fs::read_dir(dir).await.map_err(AppError::Io)?;
        while let Some(entry) = entries.next_entry().await.map_err(AppError::Io)? {
            let path = entry.path();
            if self.is_executable(&path) {
                self.run_script(&path, ctx, params).await?;
            }
        }

        Ok(())
    }

    fn is_executable(&self, path: &Path) -> bool {
        let ext = path.extension().and_then(|e| e.to_str());
        #[cfg(windows)]
        {
            matches!(ext, Some("ps1") | Some("bat") | Some("exe"))
        }
        #[cfg(not(windows))]
        {
            matches!(ext, Some("sh") | None)
        }
    }

    async fn run_script(
        &self,
        path: &Path,
        ctx: &HookContext,
        params: &serde_json::Value,
    ) -> Result<(), AppError> {
        let ctx_json =
            serde_json::to_string(ctx).map_err(|e| AppError::InternalServerError(e.to_string()))?;
        let params_json = serde_json::to_string(params)
            .map_err(|e| AppError::InternalServerError(e.to_string()))?;

        let mut cmd = if cfg!(windows) {
            let mut c = Command::new("powershell");
            c.arg("-File").arg(path);
            c
        } else {
            Command::new(path)
        };

        let output = cmd
            .env("AGENT_CONTEXT", ctx_json)
            .env("TOOL_PARAMS", params_json)
            .output()
            .await?;

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            return Err(AppError::InternalServerError(format!(
                "Hook script failed: {}. Error: {}",
                path.display(),
                stderr
            )));
        }

        Ok(())
    }
}

// Metadata: [hooks]
