use serde::{Deserialize, Serialize};

pub use crab_core::hook::HookTrigger;

/// A single hook definition.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Hook {
    /// When this hook fires.
    pub trigger: HookTrigger,
    /// Optional tool name matcher (glob pattern). Only for Pre/PostToolUse.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub tool_name: Option<String>,
    /// Shell command to execute.
    pub command: String,
    /// Timeout in milliseconds. Defaults to 60000 (60s).
    #[serde(default = "default_timeout")]
    pub timeout_ms: u64,
}

fn default_timeout() -> u64 {
    60_000
}

/// Parse hooks from the `hooks` field of settings (a JSON value).
pub fn parse_hooks(value: &serde_json::Value) -> crab_core::Result<Vec<Hook>> {
    let hooks: Vec<Hook> = serde_json::from_value(value.clone())
        .map_err(|e| crab_core::Error::Config(format!("hooks parse error: {e}")))?;
    Ok(hooks)
}

/// Load hooks from a `Config` struct.
pub fn load_hooks(config: &crate::Config) -> crab_core::Result<Vec<Hook>> {
    config
        .hooks
        .as_ref()
        .map_or_else(|| Ok(Vec::new()), parse_hooks)
}

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

    #[test]
    fn hook_trigger_serde_roundtrip() {
        let trigger = HookTrigger::PreToolUse;
        let json = serde_json::to_string(&trigger).unwrap();
        assert_eq!(json, r#""pre_tool_use""#);
        let back: HookTrigger = serde_json::from_str(&json).unwrap();
        assert_eq!(back, trigger);
    }

    #[test]
    fn hook_trigger_all_variants() {
        let triggers = [
            (HookTrigger::PreToolUse, "pre_tool_use"),
            (HookTrigger::PostToolUse, "post_tool_use"),
            (HookTrigger::UserPromptSubmit, "user_prompt_submit"),
            (HookTrigger::PostSampling, "post_sampling"),
            (HookTrigger::Stop, "stop"),
            (HookTrigger::Notification, "notification"),
            (HookTrigger::SessionStart, "session_start"),
            (HookTrigger::SessionEnd, "session_end"),
            (HookTrigger::Setup, "setup"),
            (HookTrigger::FileChanged, "file_changed"),
            (HookTrigger::Compact, "compact"),
        ];
        for (trigger, expected) in triggers {
            let json = serde_json::to_string(&trigger).unwrap();
            assert_eq!(json, format!("\"{expected}\""));
        }
    }

    #[test]
    fn hook_trigger_prompt_submit_alias() {
        let parsed: HookTrigger = serde_json::from_str("\"prompt_submit\"").unwrap();
        assert_eq!(parsed, HookTrigger::UserPromptSubmit);
        let parsed2: HookTrigger = serde_json::from_str("\"user_prompt_submit\"").unwrap();
        assert_eq!(parsed2, HookTrigger::UserPromptSubmit);
    }

    #[test]
    fn parse_hook_definition() {
        let json = serde_json::json!([{
            "trigger": "pre_tool_use",
            "toolName": "bash",
            "command": "echo checking",
            "timeoutMs": 5000
        }]);
        let hooks = parse_hooks(&json).unwrap();
        assert_eq!(hooks.len(), 1);
        assert_eq!(hooks[0].trigger, HookTrigger::PreToolUse);
        assert_eq!(hooks[0].tool_name.as_deref(), Some("bash"));
        assert_eq!(hooks[0].command, "echo checking");
        assert_eq!(hooks[0].timeout_ms, 5000);
    }

    #[test]
    fn parse_hook_default_timeout() {
        let json = serde_json::json!([{
            "trigger": "post_tool_use",
            "command": "echo done"
        }]);
        let hooks = parse_hooks(&json).unwrap();
        assert_eq!(hooks[0].timeout_ms, 60_000);
    }

    #[test]
    fn parse_empty_hooks() {
        let json = serde_json::json!([]);
        let hooks = parse_hooks(&json).unwrap();
        assert!(hooks.is_empty());
    }

    #[test]
    fn load_hooks_from_settings_none() {
        let settings = crate::Config::default();
        let hooks = load_hooks(&settings).unwrap();
        assert!(hooks.is_empty());
    }

    #[test]
    fn load_hooks_from_settings_with_hooks() {
        let settings = crate::Config {
            hooks: Some(serde_json::json!([{
                "trigger": "user_prompt_submit",
                "command": "echo hi"
            }])),
            ..Default::default()
        };
        let hooks = load_hooks(&settings).unwrap();
        assert_eq!(hooks.len(), 1);
        assert_eq!(hooks[0].trigger, HookTrigger::UserPromptSubmit);
    }

    #[test]
    fn load_hooks_prompt_submit_alias_compat() {
        let settings = crate::Config {
            hooks: Some(serde_json::json!([{
                "trigger": "prompt_submit",
                "command": "echo hi"
            }])),
            ..Default::default()
        };
        let hooks = load_hooks(&settings).unwrap();
        assert_eq!(hooks[0].trigger, HookTrigger::UserPromptSubmit);
    }

    #[test]
    fn parse_invalid_hooks_returns_error() {
        let json = serde_json::json!({"not": "an array"});
        let result = parse_hooks(&json);
        assert!(result.is_err());
    }
}
