//! Health drift helpers used by both read and write paths.
//!
//! The drift model: successful writes reset `consecutive_failures` to 0 and
//! promote `Degraded` → `Healthy`. Write failures bump the counter and drift
//! to `Degraded { reason }`; after K consecutive failures, the registration
//! drifts to `Unavailable { reason }`, which causes read walks to skip it.
//!
//! Read successes rescue `Degraded` → `Healthy` but don't touch the failure
//! counter (writes are the authoritative health signal). Read failures drift
//! `Healthy` → `Degraded` but never to `Unavailable` by themselves (a truly
//! broken backend will be caught on the next write attempt).

use chrono::Utc;

use crate::backend::HealthStatus;
use crate::registry::ArchiveRegistration;

const FAILURE_THRESHOLD: u32 = 5;

impl crate::coordinator::Archivist {
    pub(crate) async fn record_write_success(&self, reg: &ArchiveRegistration) {
        *reg.consecutive_failures.write().await = 0;
        let mut h = reg.last_health.write().await;
        if !matches!(*h, HealthStatus::Healthy) {
            *h = HealthStatus::Healthy;
        }
    }

    pub(crate) async fn record_read_success(&self, reg: &ArchiveRegistration) {
        // Reads don't reset the failure counter — writes are the authoritative
        // health signal. But reads DO recover from `Degraded` to `Healthy`.
        let mut h = reg.last_health.write().await;
        if matches!(*h, HealthStatus::Degraded { .. }) {
            *h = HealthStatus::Healthy;
        }
    }

    pub(crate) async fn record_write_failure(
        &self,
        reg: &ArchiveRegistration,
        reason: &str,
    ) {
        let mut n = reg.consecutive_failures.write().await;
        *n = n.saturating_add(1);
        *reg.last_error.write().await = Some((Utc::now(), reason.to_string()));
        let mut h = reg.last_health.write().await;
        if *n >= FAILURE_THRESHOLD {
            *h = HealthStatus::Unavailable {
                reason: format!("{} consecutive failures: {reason}", *n),
            };
        } else {
            *h = HealthStatus::Degraded {
                reason: reason.to_string(),
            };
        }
    }

    pub(crate) async fn record_read_failure(&self, reg: &ArchiveRegistration) {
        // Reads alone do not drift to Unavailable. Only drift to Degraded.
        let mut h = reg.last_health.write().await;
        if matches!(*h, HealthStatus::Healthy) {
            *h = HealthStatus::Degraded {
                reason: "read failure".into(),
            };
        }
    }

    #[allow(dead_code)]
    pub(crate) async fn current_health(&self, reg: &ArchiveRegistration) -> HealthStatus {
        reg.last_health.read().await.clone()
    }
}
