//! Consecutive-failure health drift for streams (K=5 threshold).
//!
//! Mirrors the archivist's drift logic but tracks a single stream's
//! outcomes. Re-exports the shared `HealthStatus` enum from the
//! archivist's backend module to avoid duplication.

pub use dirigent_archivist::backend::HealthStatus;

/// Number of consecutive failures before a stream drifts from `Degraded`
/// to `Unavailable`. Matches the archivist's backend drift threshold.
pub const FAILURE_THRESHOLD: u32 = 5;

/// Update health state after a successful event delivery.
/// Resets consecutive-failure counter; lifts Degraded → Healthy.
pub fn record_success(health: &mut HealthStatus, consecutive_failures: &mut u32) {
    *consecutive_failures = 0;
    if matches!(health, HealthStatus::Degraded { .. }) {
        *health = HealthStatus::Healthy;
    }
}

/// Update health state after a failed event delivery.
/// Increments counter; drifts Healthy → Degraded → Unavailable at K=5.
pub fn record_failure(
    health: &mut HealthStatus,
    consecutive_failures: &mut u32,
    reason: String,
) {
    *consecutive_failures += 1;
    if *consecutive_failures >= FAILURE_THRESHOLD {
        *health = HealthStatus::Unavailable {
            reason: format!("{} failures: {}", *consecutive_failures, reason),
        };
    } else {
        *health = HealthStatus::Degraded { reason };
    }
}

// ─── Tests ───────────────────────────────────────────────────────────────────

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

    #[test]
    fn record_success_promotes_degraded_to_healthy() {
        let mut health = HealthStatus::Degraded {
            reason: "earlier hiccup".to_string(),
        };
        let mut counter: u32 = 3;
        record_success(&mut health, &mut counter);
        assert_eq!(counter, 0);
        assert_eq!(health, HealthStatus::Healthy);
    }

    #[test]
    fn record_success_keeps_healthy_and_resets_counter() {
        let mut health = HealthStatus::Healthy;
        let mut counter: u32 = 2;
        record_success(&mut health, &mut counter);
        assert_eq!(counter, 0);
        assert_eq!(health, HealthStatus::Healthy);
    }

    #[test]
    fn record_success_does_not_rescue_unavailable() {
        // The archivist rule is: once Unavailable, only operational events
        // rescue. Success on a stream does clear the counter but we leave
        // the final clearing decision to the caller; document the current
        // behaviour: we only downgrade Degraded, not Unavailable.
        let mut health = HealthStatus::Unavailable {
            reason: "still broken".to_string(),
        };
        let mut counter: u32 = 7;
        record_success(&mut health, &mut counter);
        assert_eq!(counter, 0);
        assert!(matches!(health, HealthStatus::Unavailable { .. }));
    }

    #[test]
    fn record_failure_once_moves_healthy_to_degraded() {
        let mut health = HealthStatus::Healthy;
        let mut counter: u32 = 0;
        record_failure(&mut health, &mut counter, "boom".to_string());
        assert_eq!(counter, 1);
        match health {
            HealthStatus::Degraded { reason } => assert_eq!(reason, "boom"),
            other => panic!("expected Degraded, got {:?}", other),
        }
    }

    #[test]
    fn record_failure_five_times_drifts_to_unavailable() {
        let mut health = HealthStatus::Healthy;
        let mut counter: u32 = 0;
        for i in 0..5 {
            record_failure(&mut health, &mut counter, format!("err-{i}"));
        }
        assert_eq!(counter, 5);
        match health {
            HealthStatus::Unavailable { reason } => {
                assert!(reason.contains("5 failures"), "reason: {reason}");
            }
            other => panic!("expected Unavailable after 5 failures, got {:?}", other),
        }
    }

    #[test]
    fn record_failure_from_degraded_drifts_to_unavailable_at_threshold() {
        let mut health = HealthStatus::Degraded {
            reason: "early".to_string(),
        };
        let mut counter: u32 = 4;
        record_failure(&mut health, &mut counter, "final".to_string());
        assert_eq!(counter, 5);
        assert!(matches!(health, HealthStatus::Unavailable { .. }));
    }
}
