//! Pure parsing logic for timer bot-commands and time display formatting.
//!
//! No side effects: no filesystem access, no clock reads.

use chrono::{DateTime, Local, Utc};
use chrono_tz::Tz;

use crate::chat::util::strip_bot_mention;

// ── Command types ──────────────────────────────────────────────────────────

/// A parsed `timer` command.
#[derive(Debug, PartialEq)]
pub enum TimerCommand {
    /// `timer <story_id_or_number> <HH:MM>` — schedule a deferred start.
    Schedule {
        story_number_or_id: String,
        hhmm: String,
    },
    /// `timer list` — list all pending timers.
    List,
    /// `timer cancel <story_id_or_number>` — remove a pending timer.
    Cancel { story_number_or_id: String },
    /// Malformed arguments.
    BadArgs,
}

// ── Command extraction ─────────────────────────────────────────────────────

/// Parse a `timer` command from a raw message body.
///
/// Strips the bot mention prefix and matches the `timer` keyword.
/// Returns `None` when the message is not a timer command at all.
pub fn extract_timer_command(
    message: &str,
    bot_name: &str,
    bot_user_id: &str,
) -> Option<TimerCommand> {
    let stripped = strip_bot_mention(message, bot_name, bot_user_id);
    let trimmed = stripped
        .trim()
        .trim_start_matches(|c: char| !c.is_alphanumeric());

    let (cmd, args) = match trimmed.split_once(char::is_whitespace) {
        Some((c, a)) => (c, a.trim()),
        None => (trimmed, ""),
    };

    if !cmd.eq_ignore_ascii_case("timer") {
        return None;
    }

    // `timer` with no args or `timer list`
    if args.is_empty() || args.eq_ignore_ascii_case("list") {
        return Some(TimerCommand::List);
    }

    let (sub, rest) = match args.split_once(char::is_whitespace) {
        Some((s, r)) => (s, r.trim()),
        None => (args, ""),
    };

    // `timer cancel <id>`
    if sub.eq_ignore_ascii_case("cancel") {
        if rest.is_empty() {
            return Some(TimerCommand::BadArgs);
        }
        return Some(TimerCommand::Cancel {
            story_number_or_id: rest.to_string(),
        });
    }

    // `timer <id> <HH:MM>`
    if rest.is_empty() {
        return Some(TimerCommand::BadArgs);
    }

    Some(TimerCommand::Schedule {
        story_number_or_id: sub.to_string(),
        hhmm: rest.to_string(),
    })
}

// ── Display helpers ────────────────────────────────────────────────────────

/// Format a UTC instant for display in the given timezone (or local time when
/// `timezone` is `None`).  Returns `(formatted_string, label)` where `label`
/// is either the IANA timezone name or `"server local time"`.
pub(super) fn format_in_timezone(dt: DateTime<Utc>, timezone: Option<&str>) -> (String, String) {
    match timezone.and_then(|s| s.parse::<Tz>().ok()) {
        Some(tz) => {
            let tz_time = dt.with_timezone(&tz);
            (tz_time.format("%Y-%m-%d %H:%M").to_string(), tz.to_string())
        }
        None => {
            let local_time = dt.with_timezone(&Local);
            (
                local_time.format("%Y-%m-%d %H:%M").to_string(),
                "server local time".to_string(),
            )
        }
    }
}

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

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

    #[test]
    fn non_timer_command_returns_none() {
        assert!(extract_timer_command("Timmy help", "Timmy", "@bot:home").is_none());
    }

    #[test]
    fn timer_list_no_args() {
        assert_eq!(
            extract_timer_command("Timmy timer", "Timmy", "@bot:home"),
            Some(TimerCommand::List)
        );
    }

    #[test]
    fn timer_list_explicit() {
        assert_eq!(
            extract_timer_command("Timmy timer list", "Timmy", "@bot:home"),
            Some(TimerCommand::List)
        );
    }

    #[test]
    fn timer_cancel_story_id() {
        assert_eq!(
            extract_timer_command("Timmy timer cancel 421_story_foo", "Timmy", "@bot:home"),
            Some(TimerCommand::Cancel {
                story_number_or_id: "421_story_foo".to_string()
            })
        );
    }

    #[test]
    fn timer_cancel_no_arg_is_bad_args() {
        assert_eq!(
            extract_timer_command("Timmy timer cancel", "Timmy", "@bot:home"),
            Some(TimerCommand::BadArgs)
        );
    }

    #[test]
    fn timer_schedule_with_story_id() {
        assert_eq!(
            extract_timer_command("Timmy timer 421_story_foo 14:30", "Timmy", "@bot:home"),
            Some(TimerCommand::Schedule {
                story_number_or_id: "421_story_foo".to_string(),
                hhmm: "14:30".to_string(),
            })
        );
    }

    #[test]
    fn timer_schedule_with_number() {
        assert_eq!(
            extract_timer_command("Timmy timer 421 14:30", "Timmy", "@bot:home"),
            Some(TimerCommand::Schedule {
                story_number_or_id: "421".to_string(),
                hhmm: "14:30".to_string(),
            })
        );
    }

    #[test]
    fn timer_schedule_missing_time_is_bad_args() {
        assert_eq!(
            extract_timer_command("Timmy timer 421_story_foo", "Timmy", "@bot:home"),
            Some(TimerCommand::BadArgs)
        );
    }
}
