//! MCP tools for generic scheduled timers: `schedule_timer`, `list_timers`,
//! `cancel_timer`.

use chrono::Utc;
use serde_json::Value;

use crate::http::context::AppContext;
use crate::service::timer::scheduled::{
    ScheduledTimer, TimerAction, TimerMode, parse_interval_str, parse_when_str,
};

// ── schedule_timer ────────────────────────────────────────────────────────────

/// Register a new scheduled timer.
pub fn tool_schedule_timer(args: &Value, ctx: &AppContext) -> Result<String, String> {
    let when = args
        .get("when")
        .and_then(|v| v.as_str())
        .ok_or("Missing required parameter: 'when'")?;

    let action_val = args
        .get("action")
        .ok_or("Missing required parameter: 'action'")?;

    let action_type = action_val
        .get("type")
        .and_then(|v| v.as_str())
        .ok_or("'action' must have a 'type' field: 'mcp' or 'prompt'")?;

    let action = match action_type {
        "mcp" => {
            let method = action_val
                .get("method")
                .and_then(|v| v.as_str())
                .ok_or("'mcp' action requires 'method'")?
                .to_string();
            let args_val = action_val
                .get("args")
                .cloned()
                .unwrap_or(serde_json::json!({}));
            TimerAction::Mcp {
                method,
                args: args_val,
            }
        }
        "prompt" => {
            let text = action_val
                .get("text")
                .and_then(|v| v.as_str())
                .ok_or("'prompt' action requires 'text'")?
                .to_string();
            TimerAction::Prompt { text }
        }
        other => {
            return Err(format!(
                "Unknown action type '{other}'. Use 'mcp' or 'prompt'."
            ));
        }
    };

    let mode_str = args.get("mode").and_then(|v| v.as_str()).unwrap_or("once");

    let now = Utc::now();
    let (fire_at, inferred_interval) = parse_when_str(when, now)?;

    let mode = match mode_str {
        "once" => TimerMode::Once,
        "recurring" => {
            // Prefer explicit `interval` arg; fall back to interval inferred from
            // the relative `when` duration.
            let interval_secs = if let Some(iv) = args
                .get("interval")
                .and_then(|v| v.as_str())
                .and_then(parse_interval_str)
            {
                iv
            } else if let Some(iv) = inferred_interval {
                iv
            } else {
                return Err(
                    "Recurring timers with an absolute 'when' require an 'interval' \
                     parameter (e.g. '2h', '15 minutes')."
                        .to_string(),
                );
            };
            TimerMode::Recurring { interval_secs }
        }
        other => {
            return Err(format!(
                "Unknown mode '{other}'. Use 'once' or 'recurring'."
            ));
        }
    };

    let label = args
        .get("label")
        .and_then(|v| v.as_str())
        .map(str::to_string);

    let timer = ScheduledTimer {
        id: ScheduledTimer::new_id(),
        label: label.clone(),
        fire_at,
        action,
        mode,
        created_at: now,
    };

    let id = timer.id.clone();
    ctx.scheduled_timer_store.add(timer)?;

    let label_str = label.map(|l| format!(": {l}")).unwrap_or_default();
    Ok(format!(
        "Timer `{id}`{label_str} scheduled to fire at {fire_at} (UTC)."
    ))
}

// ── list_timers ───────────────────────────────────────────────────────────────

/// List all pending scheduled timers.
pub fn tool_list_timers(ctx: &AppContext) -> Result<String, String> {
    let timers = ctx.scheduled_timer_store.list();
    if timers.is_empty() {
        return Ok("No pending scheduled timers.".to_string());
    }
    let mut lines = vec![format!("**Pending scheduled timers ({}):**", timers.len())];
    for t in &timers {
        let label = t
            .label
            .as_deref()
            .map(|l| format!(" ({l})"))
            .unwrap_or_default();
        let mode_str = match &t.mode {
            TimerMode::Once => "once".to_string(),
            TimerMode::Recurring { interval_secs } => {
                format!("recurring every {interval_secs}s")
            }
        };
        let action_str = match &t.action {
            TimerAction::Mcp { method, .. } => format!("mcp:{method}"),
            TimerAction::Prompt { text } => {
                let preview: String = text.chars().take(40).collect();
                format!("prompt:{preview}")
            }
        };
        lines.push(format!(
            "- `{}` {}{} — fires at {} UTC [{}] action={}",
            t.id,
            mode_str,
            label,
            t.fire_at.format("%Y-%m-%dT%H:%M:%SZ"),
            mode_str,
            action_str
        ));
    }
    Ok(lines.join("\n"))
}

// ── cancel_timer ──────────────────────────────────────────────────────────────

/// Cancel a scheduled timer by ID.
pub fn tool_cancel_timer(args: &Value, ctx: &AppContext) -> Result<String, String> {
    let id = args
        .get("id")
        .and_then(|v| v.as_str())
        .ok_or("Missing required parameter: 'id'")?;

    if ctx.scheduled_timer_store.remove_by_id(id) {
        Ok(format!("Timer `{id}` cancelled."))
    } else {
        Err(format!("No scheduled timer found with id '{id}'."))
    }
}

// ── Tests ─────────────────────────────────────────────────────────────────────

#[cfg(test)]
mod tests {
    use super::*;
    use crate::http::test_helpers::test_ctx;

    #[test]
    fn schedule_timer_mcp_action_once() {
        let dir = tempfile::tempdir().unwrap();
        let ctx = test_ctx(dir.path());
        let args = serde_json::json!({
            "when": "in 1 hour",
            "action": { "type": "mcp", "method": "get_pipeline_status", "args": {} },
            "mode": "once",
            "label": "hourly check"
        });
        let result = tool_schedule_timer(&args, &ctx).unwrap();
        assert!(result.contains("tm-"), "expected timer id: {result}");
        assert!(
            result.contains("scheduled to fire"),
            "expected schedule msg: {result}"
        );
        assert_eq!(ctx.scheduled_timer_store.list().len(), 1);
    }

    #[test]
    fn schedule_timer_prompt_action() {
        let dir = tempfile::tempdir().unwrap();
        let ctx = test_ctx(dir.path());
        let args = serde_json::json!({
            "when": "30m",
            "action": { "type": "prompt", "text": "standup time!" }
        });
        let result = tool_schedule_timer(&args, &ctx).unwrap();
        assert!(result.contains("scheduled"), "unexpected: {result}");
        let list = ctx.scheduled_timer_store.list();
        assert_eq!(list.len(), 1);
        assert!(matches!(&list[0].action, TimerAction::Prompt { .. }));
    }

    #[test]
    fn schedule_timer_recurring_relative() {
        let dir = tempfile::tempdir().unwrap();
        let ctx = test_ctx(dir.path());
        let args = serde_json::json!({
            "when": "in 2 hours",
            "action": { "type": "prompt", "text": "reminder" },
            "mode": "recurring"
        });
        let result = tool_schedule_timer(&args, &ctx).unwrap();
        assert!(result.contains("scheduled"), "unexpected: {result}");
        let list = ctx.scheduled_timer_store.list();
        assert_eq!(list.len(), 1);
        assert!(
            matches!(
                &list[0].mode,
                TimerMode::Recurring {
                    interval_secs: 7200
                }
            ),
            "expected 7200s interval: {:?}",
            list[0].mode
        );
    }

    #[test]
    fn schedule_timer_recurring_absolute_requires_interval() {
        let dir = tempfile::tempdir().unwrap();
        let ctx = test_ctx(dir.path());
        let args = serde_json::json!({
            "when": "2026-12-01T09:00:00Z",
            "action": { "type": "prompt", "text": "reminder" },
            "mode": "recurring"
            // no interval
        });
        assert!(tool_schedule_timer(&args, &ctx).is_err());
    }

    #[test]
    fn schedule_timer_recurring_absolute_with_interval() {
        let dir = tempfile::tempdir().unwrap();
        let ctx = test_ctx(dir.path());
        let args = serde_json::json!({
            "when": "2026-12-01T09:00:00Z",
            "action": { "type": "prompt", "text": "daily" },
            "mode": "recurring",
            "interval": "24h"
        });
        let result = tool_schedule_timer(&args, &ctx).unwrap();
        assert!(result.contains("scheduled"), "unexpected: {result}");
        let list = ctx.scheduled_timer_store.list();
        assert!(
            matches!(
                &list[0].mode,
                TimerMode::Recurring {
                    interval_secs: 86400
                }
            ),
            "expected 86400s (24h): {:?}",
            list[0].mode
        );
    }

    #[test]
    fn list_timers_empty() {
        let dir = tempfile::tempdir().unwrap();
        let ctx = test_ctx(dir.path());
        let result = tool_list_timers(&ctx).unwrap();
        assert!(result.contains("No pending"), "unexpected: {result}");
    }

    #[test]
    fn list_timers_shows_entries() {
        let dir = tempfile::tempdir().unwrap();
        let ctx = test_ctx(dir.path());
        let args = serde_json::json!({
            "when": "1h",
            "action": { "type": "prompt", "text": "foo" },
            "label": "my-timer"
        });
        tool_schedule_timer(&args, &ctx).unwrap();
        let result = tool_list_timers(&ctx).unwrap();
        assert!(result.contains("my-timer"), "unexpected: {result}");
        assert!(result.contains("tm-"), "expected timer id: {result}");
    }

    #[test]
    fn cancel_timer_removes_it() {
        let dir = tempfile::tempdir().unwrap();
        let ctx = test_ctx(dir.path());
        let args = serde_json::json!({
            "when": "1h",
            "action": { "type": "prompt", "text": "foo" }
        });
        tool_schedule_timer(&args, &ctx).unwrap();
        let id = ctx.scheduled_timer_store.list()[0].id.clone();

        let result = tool_cancel_timer(&serde_json::json!({ "id": &id }), &ctx).unwrap();
        assert!(result.contains("cancelled"), "unexpected: {result}");
        assert!(ctx.scheduled_timer_store.list().is_empty());
    }

    #[test]
    fn cancel_timer_not_found_is_err() {
        let dir = tempfile::tempdir().unwrap();
        let ctx = test_ctx(dir.path());
        assert!(tool_cancel_timer(&serde_json::json!({ "id": "tm-notexist" }), &ctx).is_err());
    }
}
