use serde::{Deserialize, Serialize};
use std::cmp::Ordering;
use std::time::Duration;

#[derive(Debug, PartialEq, Clone, Copy, Serialize, Deserialize)]
pub struct Rate {
    value: f64,
}

impl Eq for Rate {}

impl Ord for Rate {
    fn cmp(&self, other: &Self) -> Ordering {
        // Compare the underlying value directly. `total_cmp` gives a total
        // order over all f64 (including NaN), which is required for the `Eq`
        // + `Ord` impls to be consistent and is relied upon by
        // `calculate_estimated_usage_rate`'s `sort_unstable`.
        //
        // NOTE: `cmp` MUST NOT delegate to `partial_cmp` here — `partial_cmp`
        // delegates back to `cmp`, so any mutual delegation infinite-loops
        // into a stack overflow. This was a latent bug that only surfaced
        // once the topology meter started being fed real samples in
        // production (#3453): the first `Rate` comparison (via `sort_unstable`)
        // recursed until the stack overflowed.
        self.value.total_cmp(&other.value)
    }
}

impl PartialOrd for Rate {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        Some(self.cmp(other))
    }
}

impl PartialOrd for RateProportion {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        self.value.partial_cmp(&other.value)
    }
}

impl std::ops::Add for Rate {
    type Output = Self;

    fn add(self, other: Self) -> Self {
        Rate {
            value: self.value + other.value,
        }
    }
}

impl std::ops::Sub for Rate {
    type Output = Self;

    fn sub(self, other: Self) -> Self {
        Rate {
            value: self.value - other.value,
        }
    }
}

impl std::ops::AddAssign for Rate {
    fn add_assign(&mut self, other: Self) {
        self.value += other.value;
    }
}

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

    #[test]
    fn test_rate() {
        let rate = Rate::new(100.0, Duration::from_secs(2));
        assert_eq!(rate.per_second(), 50.0);
    }

    #[test]
    fn test_rate_per_second() {
        let rate = Rate::new_per_second(100.0);
        assert_eq!(rate.per_second(), 100.0);
    }

    #[test]
    fn test_add() {
        let rate1 = Rate::new(100.0, Duration::from_secs(2));
        let rate2 = Rate::new(200.0, Duration::from_secs(2));
        let rate3 = rate1 + rate2;
        assert_eq!(rate3.per_second(), 150.0);
    }

    #[test]
    fn test_add_assign() {
        let mut rate1 = Rate::new(100.0, Duration::from_secs(2));
        let rate2 = Rate::new(200.0, Duration::from_secs(2));
        rate1 += rate2;
        assert_eq!(rate1.per_second(), 150.0);
    }

    #[test]
    fn test_sub() {
        let rate1 = Rate::new(100.0, Duration::from_secs(2));
        let rate2 = Rate::new(200.0, Duration::from_secs(2));
        let rate3 = rate2 - rate1;
        assert_eq!(rate3.per_second(), 50.0);
    }

    /// Regression for #3453: `Rate::cmp` and `Rate::partial_cmp` used to
    /// delegate to each other, infinite-looping into a stack overflow on the
    /// FIRST comparison. This was latent until the topology meter started
    /// being fed real samples in production, at which point
    /// `calculate_estimated_usage_rate`'s `sort_unstable` over `Vec<Rate>`
    /// triggered the overflow and aborted every node. These comparisons must
    /// terminate and return the correct ordering.
    #[test]
    fn test_ord_does_not_recurse() {
        use std::cmp::Ordering;

        let lo = Rate::new_per_second(1.0);
        let hi = Rate::new_per_second(2.0);

        // Each of these would stack-overflow (SIGABRT, not a test failure)
        // under the old mutually-recursive impls.
        assert_eq!(lo.cmp(&hi), Ordering::Less);
        assert_eq!(hi.cmp(&lo), Ordering::Greater);
        assert_eq!(lo.cmp(&lo), Ordering::Equal);
        assert_eq!(lo.partial_cmp(&hi), Some(Ordering::Less));
        assert!(lo < hi);
        assert!(hi > lo);

        // Sorting is the exact production trigger (sort_unstable in
        // calculate_estimated_usage_rate).
        let mut rates = [
            Rate::new_per_second(3.0),
            Rate::new_per_second(1.0),
            Rate::new_per_second(2.0),
        ];
        rates.sort_unstable();
        let sorted: Vec<f64> = rates.iter().map(|r| r.per_second()).collect();
        assert_eq!(sorted, vec![1.0, 2.0, 3.0]);
    }
}

#[derive(Debug, PartialEq, Clone, Copy)]
pub struct RateProportion {
    value: f64,
}

impl Rate {
    pub fn new(value: f64, divisor: Duration) -> Self {
        debug_assert!(value >= 0.0, "Value must be non-negative");
        let divisor_as_secs = divisor.as_secs_f64();
        debug_assert!(divisor_as_secs > 0.0, "Divisor must be greater than 0");
        Rate {
            value: value / divisor_as_secs,
        }
    }

    pub const fn new_per_second(value: f64) -> Self {
        Rate { value }
    }

    pub const fn per_second(&self) -> f64 {
        self.value
    }

    pub fn proportion_of(&self, other: &Rate) -> RateProportion {
        debug_assert!(
            other.value > 0.0,
            "Cannot calculate proportion of zero rate"
        );
        RateProportion {
            value: self.value / other.value,
        }
    }
}

impl RateProportion {
    pub(crate) fn new(value: f64) -> Self {
        debug_assert!(
            (0.0..=1.0).contains(&value),
            "Proportion must be between 0 and 1"
        );
        RateProportion { value }
    }
}
