//! Timestamp conversion utilities for HFT system
//!
//! Provides unified timestamp conversion between HardwareTimestamp, i64 nanoseconds,
//! and `DateTime<Utc>` for the Foxhunt trading system. This is the SINGLE source of truth
//! for all timestamp conversions to eliminate timing bugs in HFT operations.

use crate::timing::HardwareTimestamp;
use chrono::{DateTime, TimeZone, Utc};

/// Convert `HardwareTimestamp` to `i64` nanoseconds for protobuf compatibility
#[must_use]
#[allow(clippy::cast_possible_wrap)]
pub const fn hardware_timestamp_to_i64(timestamp: &HardwareTimestamp) -> i64 {
    timestamp.as_nanos() as i64
}

/// Convert `i64` nanoseconds to `HardwareTimestamp`
#[inline]
#[must_use]
pub const fn i64_to_hardware_timestamp(nanos: i64) -> HardwareTimestamp {
    // Convert i64 to u64, handling negative values as 0
    #[allow(clippy::cast_sign_loss)]
    let nanos_u64 = if nanos >= 0 { nanos as u64 } else { 0 };
    HardwareTimestamp::from_nanos(nanos_u64)
}

/// Convert `HardwareTimestamp` to `DateTime<Utc>`
#[must_use]
#[allow(clippy::cast_possible_wrap)]
pub fn hardware_timestamp_to_datetime(timestamp: &HardwareTimestamp) -> DateTime<Utc> {
    let nanos = timestamp.as_nanos();
    let secs = nanos.saturating_div(1_000_000_000);
    let nsecs = u32::try_from(nanos % 1_000_000_000).unwrap_or(0);
    Utc.timestamp_opt(secs as i64, nsecs)
        .single()
        .unwrap_or_else(Utc::now)
}

/// Convert `DateTime<Utc>` to `HardwareTimestamp`
#[must_use]
pub fn datetime_to_hardware_timestamp(dt: DateTime<Utc>) -> HardwareTimestamp {
    let nanos = dt.timestamp_nanos_opt().unwrap_or_else(|| {
        // Fallback for dates outside i64 range
        dt.timestamp()
            .checked_mul(1_000_000_000)
            .unwrap_or(0)
            .checked_add(i64::from(dt.timestamp_subsec_nanos()))
            .unwrap_or(0)
    });
    i64_to_hardware_timestamp(nanos)
}

/// Convert `DateTime<Utc>` to i64 nanoseconds (for protobuf)
#[must_use]
pub fn datetime_to_i64(dt: DateTime<Utc>) -> i64 {
    dt.timestamp_nanos_opt().unwrap_or_else(|| {
        // Fallback for dates outside i64 range
        dt.timestamp()
            .checked_mul(1_000_000_000)
            .unwrap_or(0)
            .checked_add(i64::from(dt.timestamp_subsec_nanos()))
            .unwrap_or(0)
    })
}

/// Convert `i64` nanoseconds to `DateTime<Utc>` (from protobuf)
#[must_use]
#[allow(clippy::modulo_arithmetic)]
pub fn i64_to_datetime(nanos: i64) -> DateTime<Utc> {
    let secs = nanos.saturating_div(1_000_000_000);
    let nsecs = u32::try_from(if nanos >= 0 {
        nanos.rem_euclid(1_000_000_000)
    } else {
        1_000_000_000 - (-nanos).rem_euclid(1_000_000_000)
    })
    .unwrap_or(0);
    Utc.timestamp_opt(secs, nsecs)
        .single()
        .unwrap_or_else(Utc::now)
}

/// Get current time as `HardwareTimestamp` (canonical type)
#[inline]
#[must_use]
pub fn now() -> HardwareTimestamp {
    HardwareTimestamp::now()
}

/// Get current time as `i64` nanoseconds (for protobuf)
#[inline]
#[must_use]
pub fn now_i64() -> i64 {
    hardware_timestamp_to_i64(&HardwareTimestamp::now())
}

/// Get current time as `DateTime<Utc>`
#[inline]
#[must_use]
pub fn now_datetime() -> DateTime<Utc> {
    hardware_timestamp_to_datetime(&HardwareTimestamp::now())
}

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

    #[test]
    fn test_hardware_timestamp_i64_roundtrip() {
        let original = HardwareTimestamp::now();
        let i64_val = hardware_timestamp_to_i64(&original);
        let converted_back = i64_to_hardware_timestamp(i64_val);

        // Should be exactly the same
        assert_eq!(original.as_nanos(), converted_back.as_nanos());
    }

    #[test]
    fn test_datetime_conversions() {
        let now = Utc::now();
        let hardware_ts = datetime_to_hardware_timestamp(now);
        let converted_back = hardware_timestamp_to_datetime(&hardware_ts);

        // Should be very close (within 1 second due to precision)
        assert!((now.timestamp() - converted_back.timestamp()).abs() <= 1);
    }

    #[test]
    fn test_i64_datetime_roundtrip() {
        let now = Utc::now();
        let i64_val = datetime_to_i64(now);
        let converted_back = i64_to_datetime(i64_val);

        // Should be very close
        assert!((now.timestamp() - converted_back.timestamp()).abs() <= 1);
    }

    #[test]
    fn test_unified_conversion_chain() {
        let original_dt = Utc::now();

        // DateTime -> HardwareTimestamp -> i64 -> DateTime
        let hardware_ts = datetime_to_hardware_timestamp(original_dt);
        let i64_val = hardware_timestamp_to_i64(&hardware_ts);
        let final_dt = i64_to_datetime(i64_val);

        // Should maintain precision
        assert!((original_dt.timestamp() - final_dt.timestamp()).abs() <= 1);
    }

    #[test]
    fn test_negative_i64_handling() {
        let negative_nanos = -1000000_i64;
        let hardware_ts = i64_to_hardware_timestamp(negative_nanos);

        // Should convert negative to 0
        assert_eq!(hardware_ts.as_nanos(), 0);
    }
}
