use chrono::{DateTime, Utc};

pub fn iso_to_ms(iso_string: &str) -> Option<i64> {
    DateTime::parse_from_rfc3339(iso_string)
        .ok()
        .map(|dt| dt.timestamp_millis())
}

pub fn ms_to_iso(ms: i64) -> String {
    DateTime::from_timestamp_millis(ms)
        .map(|dt| dt.to_rfc3339())
        .unwrap_or_else(|| "Invalid timestamp".to_string())
}

pub fn now_ms() -> i64 {
    Utc::now().timestamp_millis()
}

pub fn parse_since(input: &str) -> Option<i64> {
    parse_since_from(input, now_ms())
}

/// Like `parse_since` but durations are relative to `reference_ms` instead of now.
/// Absolute dates/timestamps are returned as-is regardless of `reference_ms`.
pub fn parse_since_from(input: &str, reference_ms: i64) -> Option<i64> {
    let input = input.trim();

    if let Some(num_str) = input.strip_suffix('d') {
        let days: i64 = num_str.parse().ok()?;
        return Some(reference_ms - (days * 24 * 60 * 60 * 1000));
    }

    if let Some(num_str) = input.strip_suffix('w') {
        let weeks: i64 = num_str.parse().ok()?;
        return Some(reference_ms - (weeks * 7 * 24 * 60 * 60 * 1000));
    }

    if let Some(num_str) = input.strip_suffix('m') {
        let months: i64 = num_str.parse().ok()?;
        return Some(reference_ms - (months * 30 * 24 * 60 * 60 * 1000));
    }

    if input.len() == 10 && input.chars().filter(|&c| c == '-').count() == 2 {
        let iso_full = format!("{input}T00:00:00Z");
        return iso_to_ms(&iso_full);
    }

    iso_to_ms(input)
}

pub fn iso_to_ms_strict(iso_string: &str) -> Result<i64, String> {
    DateTime::parse_from_rfc3339(iso_string)
        .map(|dt| dt.timestamp_millis())
        .map_err(|_| format!("Invalid timestamp: {}", iso_string))
}

pub fn iso_to_ms_opt_strict(iso_string: &Option<String>) -> Result<Option<i64>, String> {
    match iso_string {
        Some(s) => iso_to_ms_strict(s).map(Some),
        None => Ok(None),
    }
}

pub fn format_full_datetime(ms: i64) -> String {
    DateTime::from_timestamp_millis(ms)
        .map(|dt| dt.format("%Y-%m-%d %H:%M UTC").to_string())
        .unwrap_or_else(|| "Unknown".to_string())
}

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

    #[test]
    fn test_iso_to_ms() {
        let ms = iso_to_ms("2024-01-15T10:30:00Z").unwrap();
        assert!(ms > 0);
    }

    #[test]
    fn test_ms_to_iso() {
        let iso = ms_to_iso(1705315800000);
        assert!(iso.contains("2024-01-15"));
    }

    #[test]
    fn test_now_ms() {
        let now = now_ms();
        assert!(now > 1700000000000);
    }

    #[test]
    fn test_parse_since_days() {
        let now = now_ms();
        let seven_days = parse_since("7d").unwrap();
        let expected = now - (7 * 24 * 60 * 60 * 1000);
        assert!((seven_days - expected).abs() < 1000);
    }

    #[test]
    fn test_parse_since_weeks() {
        let now = now_ms();
        let two_weeks = parse_since("2w").unwrap();
        let expected = now - (14 * 24 * 60 * 60 * 1000);
        assert!((two_weeks - expected).abs() < 1000);
    }

    #[test]
    fn test_parse_since_months() {
        let now = now_ms();
        let one_month = parse_since("1m").unwrap();
        let expected = now - (30 * 24 * 60 * 60 * 1000);
        assert!((one_month - expected).abs() < 1000);
    }

    #[test]
    fn test_parse_since_iso_date() {
        let ms = parse_since("2024-01-15").unwrap();
        assert!(ms > 0);
        let expected = iso_to_ms("2024-01-15T00:00:00Z").unwrap();
        assert_eq!(ms, expected);
    }

    #[test]
    fn test_parse_since_invalid() {
        assert!(parse_since("invalid").is_none());
        assert!(parse_since("").is_none());
    }

    #[test]
    fn test_format_full_datetime() {
        let dt = format_full_datetime(1705315800000);
        assert!(dt.contains("2024-01-15"));
        assert!(dt.contains("UTC"));
    }
}
