use chrono::{DateTime, Local, TimeZone, Utc};

/// Format a `DateTime<Utc>` as a local timezone time string.
///
/// Converts the timestamp to the system's local timezone and formats it as
/// "YYYY-MM-DD HH:MM:SS".
pub fn format_timestamp(timestamp: DateTime<Utc>) -> String {
    timestamp
        .with_timezone(&Local)
        .format("%Y-%m-%d %H:%M:%S")
        .to_string()
}

/// Parse milliseconds since Unix epoch into a `DateTime<Utc>`.
///
/// Returns `None` if the timestamp is invalid (e.g., out of range for chrono).
pub fn parse_timestamp_millis(millis: i64) -> Option<DateTime<Utc>> {
    Utc.timestamp_millis_opt(millis).single()
}

/// Serde module for serializing `DateTime<Utc>` as Unix milliseconds.
///
/// Use with `#[serde(with = "timestamp_millis")]` on a `DateTime<Utc>` field.
pub mod timestamp_millis {
    use chrono::{DateTime, TimeZone, Utc};
    use serde::{Deserialize, Deserializer, Serializer};

    pub fn serialize<S>(date: &DateTime<Utc>, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.serialize_i64(date.timestamp_millis())
    }

    pub fn deserialize<'de, D>(deserializer: D) -> Result<DateTime<Utc>, D::Error>
    where
        D: Deserializer<'de>,
    {
        let millis = i64::deserialize(deserializer)?;
        Utc.timestamp_millis_opt(millis)
            .single()
            .ok_or_else(|| serde::de::Error::custom("invalid timestamp"))
    }
}

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

    #[test]
    fn test_format_timestamp_valid() -> Result<(), String> {
        // Test a known timestamp (2024-01-01 12:00:00 UTC)
        let timestamp = Utc
            .with_ymd_and_hms(2024, 1, 1, 12, 0, 0)
            .single()
            .ok_or("invalid date")?;
        let result = format_timestamp(timestamp);
        // Result will vary by timezone, but should be a valid format
        assert!(
            result.len() == 19,
            "Expected 19 char date string, got: {}",
            result
        );
        // Verify format pattern: YYYY-MM-DD HH:MM:SS
        let parts: Vec<&str> = result.split(' ').collect();
        assert_eq!(parts.len(), 2, "Expected date and time parts");
        assert_eq!(
            parts[0].matches('-').count(),
            2,
            "Expected 2 dashes in date"
        );
        assert_eq!(
            parts[1].matches(':').count(),
            2,
            "Expected 2 colons in time"
        );
        Ok(())
    }

    #[test]
    fn test_format_timestamp_zero() -> Result<(), String> {
        // Unix epoch (1970-01-01 00:00:00 UTC)
        let timestamp = Utc
            .timestamp_millis_opt(0)
            .single()
            .ok_or("invalid timestamp")?;
        let result = format_timestamp(timestamp);
        assert!(
            result.len() == 19,
            "Expected 19 char date string, got: {}",
            result
        );
        Ok(())
    }

    #[test]
    fn test_format_timestamp_negative() -> Result<(), String> {
        // Test negative timestamps (pre-1970)
        // 1969-12-31 23:59:59 UTC = -1000ms
        let timestamp = Utc
            .timestamp_millis_opt(-1000)
            .single()
            .ok_or("invalid timestamp")?;
        let result = format_timestamp(timestamp);
        // Should handle gracefully
        assert!(!result.is_empty());
        assert!(
            result.len() == 19,
            "Expected 19 char date string, got: {}",
            result
        );
        Ok(())
    }

    #[test]
    fn test_parse_timestamp_millis_valid() -> Result<(), String> {
        let ts = parse_timestamp_millis(1_704_110_400_000).ok_or("failed to parse timestamp")?;
        let expected = Utc
            .with_ymd_and_hms(2024, 1, 1, 12, 0, 0)
            .single()
            .ok_or("invalid expected date")?;
        assert_eq!(ts, expected);
        Ok(())
    }

    #[test]
    fn test_parse_timestamp_millis_zero() -> Result<(), String> {
        let ts = parse_timestamp_millis(0).ok_or("failed to parse timestamp")?;
        let expected = Utc
            .with_ymd_and_hms(1970, 1, 1, 0, 0, 0)
            .single()
            .ok_or("invalid expected date")?;
        assert_eq!(ts, expected);
        Ok(())
    }

    #[test]
    fn test_parse_timestamp_millis_negative() -> Result<(), String> {
        // Pre-1970 timestamp
        let ts = parse_timestamp_millis(-1000).ok_or("failed to parse timestamp")?;
        let epoch = Utc
            .with_ymd_and_hms(1970, 1, 1, 0, 0, 0)
            .single()
            .ok_or("invalid epoch date")?;
        assert!(ts < epoch);
        Ok(())
    }
}
