use chrono::{DateTime as ChronoDateTime, TimeZone, Utc};

/// Canonical UTC timestamp type used across the engine.
pub type DateTime = ChronoDateTime<Utc>;

/// Unix epoch (UTC).
pub fn unix_epoch() -> DateTime {
    Utc.timestamp_opt(0, 0)
        .single()
        .expect("unix epoch timestamp is always valid")
}

/// Best-effort conversion from unix milliseconds to a UTC timestamp.
pub fn from_unix_timestamp_ms(timestamp_ms: i64) -> DateTime {
    Utc.timestamp_millis_opt(timestamp_ms)
        .single()
        .unwrap_or_else(unix_epoch)
}

// ============================================================================
// rkyv adapters
// ============================================================================

/// rkyv adapter for `chrono::DateTime<Utc>` that archives as unix milliseconds (`i64`).
///
/// Note: rkyv requires archived multi-byte integers to use `rkyv::primitive::*` types (portability).
pub struct DateTimeUtcAsUnixMillis;

impl rkyv::with::ArchiveWith<DateTime> for DateTimeUtcAsUnixMillis {
    type Archived = rkyv::primitive::ArchivedI64;
    type Resolver = ();

    fn resolve_with(field: &DateTime, _: Self::Resolver, out: rkyv::place::Place<Self::Archived>) {
        out.write(rkyv::primitive::ArchivedI64::from_native(
            field.timestamp_millis(),
        ));
    }
}

impl<S: rkyv::rancor::Fallible + ?Sized> rkyv::with::SerializeWith<DateTime, S>
    for DateTimeUtcAsUnixMillis
{
    fn serialize_with(_: &DateTime, _: &mut S) -> Result<Self::Resolver, S::Error> {
        Ok(())
    }
}

impl<D: rkyv::rancor::Fallible + ?Sized>
    rkyv::with::DeserializeWith<rkyv::primitive::ArchivedI64, DateTime, D>
    for DateTimeUtcAsUnixMillis
{
    fn deserialize_with(
        field: &rkyv::primitive::ArchivedI64,
        _: &mut D,
    ) -> Result<DateTime, D::Error> {
        let timestamp_ms = field.to_native();
        Ok(Utc
            .timestamp_millis_opt(timestamp_ms)
            .single()
            .expect("archived unix millis must be a valid chrono timestamp"))
    }
}
