use std::collections::BTreeMap;
use std::sync::atomic::{AtomicBool, AtomicU8, Ordering};
use std::sync::{Arc, Mutex};
use std::time::Instant;

use crate::error::GatewayError;

#[path = "health_types.rs"]
mod health_types;

pub use health_types::{
    HealthCode, HealthGateName, HealthGateReport, HealthGateStatus, HealthPhase, HealthReport,
};

const CODE_INITIALIZING: &str = "initializing";

/// Mutable health state shared by the gateway and lifecycle.
#[derive(Clone, Debug)]
pub struct HealthHandle {
    state: Arc<HealthState>,
}

impl Default for HealthHandle {
    fn default() -> Self {
        Self::new()
    }
}

impl HealthHandle {
    /// Creates a new health handle in the starting phase.
    pub fn new() -> Self {
        Self {
            state: Arc::new(HealthState::new()),
        }
    }

    /// Marks the service as starting.
    ///
    /// Phase transitions only move forward; backward calls warn and are ignored.
    pub fn starting(&self) {
        self.state.set_phase(HealthPhase::Starting, None);
    }

    /// Marks the service live and records that startup completed once.
    ///
    /// Phase transitions only move forward; backward calls warn and are ignored.
    pub fn live(&self) {
        if self.state.set_phase(HealthPhase::Live, None) {
            self.state.ever_live.store(true, Ordering::Release);
        }
    }

    /// Marks the service as draining.
    ///
    /// Phase transitions only move forward; backward calls warn and are ignored.
    pub fn draining(&self) {
        self.state.set_phase(HealthPhase::Draining, None);
    }

    /// Marks the service permanently dead.
    ///
    /// Phase transitions only move forward; calls after dead warn and are ignored.
    pub fn dead(&self, code: HealthCode) {
        self.state.set_phase(HealthPhase::Dead, Some(code));
    }

    /// Creates or retrieves a readiness gate.
    ///
    /// # Errors
    /// [`GatewayError`] if the gate cannot be registered.
    pub fn gate(&self, name: HealthGateName) -> Result<HealthGateHandle, GatewayError> {
        self.state.ensure_gate(name.clone())?;
        Ok(HealthGateHandle {
            state: Arc::clone(&self.state),
            name,
        })
    }

    /// Returns true while the process should remain alive.
    pub fn is_live(&self) -> bool {
        self.state.phase() != HealthPhase::Dead
    }

    /// Returns true when the service is live and all gates allow readiness.
    pub fn is_ready(&self) -> bool {
        self.state.phase() == HealthPhase::Live && self.state.gates_ready()
    }

    /// Returns true after the service reached live once.
    pub fn is_started(&self) -> bool {
        self.state.ever_live.load(Ordering::Acquire)
    }

    /// Returns a JSON-ready health report.
    pub fn report(&self) -> HealthReport {
        self.state.report()
    }
}

/// Handle for one readiness dependency.
#[derive(Clone, Debug)]
pub struct HealthGateHandle {
    state: Arc<HealthState>,
    name: HealthGateName,
}

impl HealthGateHandle {
    /// Marks the dependency ready.
    pub fn pass(&self) {
        self.state.set_gate(&self.name, HealthGateStatus::Pass);
    }

    /// Marks the dependency degraded while still allowing readiness.
    pub fn warn(&self, code: HealthCode) {
        self.state
            .set_gate(&self.name, HealthGateStatus::Warn(code));
    }

    /// Marks the dependency unavailable.
    pub fn fail(&self, code: HealthCode) {
        self.state
            .set_gate(&self.name, HealthGateStatus::Fail(code));
    }

    /// Returns the current dependency status.
    pub fn status(&self) -> HealthGateStatus {
        self.state.gate_status(&self.name)
    }
}

#[derive(Debug)]
struct HealthState {
    phase: AtomicU8,
    ever_live: AtomicBool,
    started_at: Instant,
    dead_code: Mutex<Option<HealthCode>>,
    gates: Mutex<BTreeMap<HealthGateName, HealthGateStatus>>,
}

impl HealthState {
    fn new() -> Self {
        Self {
            phase: AtomicU8::new(HealthPhase::Starting as u8),
            ever_live: AtomicBool::new(false),
            started_at: Instant::now(),
            dead_code: Mutex::new(None),
            gates: Mutex::new(BTreeMap::new()),
        }
    }

    fn set_phase(&self, phase: HealthPhase, dead_code: Option<HealthCode>) -> bool {
        let desired = phase as u8;
        let mut current = self.phase.load(Ordering::Acquire);
        loop {
            if desired <= current {
                warn_rejected_phase(HealthPhase::from_u8(current), phase);
                return false;
            }
            match self.phase.compare_exchange_weak(
                current,
                desired,
                Ordering::AcqRel,
                Ordering::Acquire,
            ) {
                Ok(_) => break,
                Err(actual) => current = actual,
            }
        }
        if phase == HealthPhase::Dead {
            *self.dead_code.lock().expect("health mutex poisoned") = dead_code;
        }
        true
    }

    fn phase(&self) -> HealthPhase {
        HealthPhase::from_u8(self.phase.load(Ordering::Acquire))
    }

    fn ensure_gate(&self, name: HealthGateName) -> Result<(), GatewayError> {
        let mut gates = self.gates.lock().expect("health mutex poisoned");
        gates
            .entry(name)
            .or_insert_with(|| HealthGateStatus::Fail(HealthCode::from_static(CODE_INITIALIZING)));
        Ok(())
    }

    fn set_gate(&self, name: &HealthGateName, status: HealthGateStatus) {
        let mut gates = self.gates.lock().expect("health mutex poisoned");
        gates.insert(name.clone(), status);
    }

    fn gate_status(&self, name: &HealthGateName) -> HealthGateStatus {
        let gates = self.gates.lock().expect("health mutex poisoned");
        gates
            .get(name)
            .cloned()
            .unwrap_or_else(|| HealthGateStatus::Fail(HealthCode::from_static(CODE_INITIALIZING)))
    }

    fn gates_ready(&self) -> bool {
        self.gates
            .lock()
            .expect("health mutex poisoned")
            .values()
            .all(HealthGateStatus::allows_readiness)
    }

    fn report(&self) -> HealthReport {
        let phase = self.phase();
        let gates = self.gate_reports();
        HealthReport::new(
            phase,
            phase != HealthPhase::Dead,
            phase == HealthPhase::Live && gates.iter().all(HealthGateReport::is_ready),
            self.ever_live.load(Ordering::Acquire),
            self.started_at.elapsed().as_secs(),
            self.dead_code
                .lock()
                .expect("health mutex poisoned")
                .clone(),
            gates,
        )
    }

    fn gate_reports(&self) -> Vec<HealthGateReport> {
        self.gates
            .lock()
            .expect("health mutex poisoned")
            .iter()
            .map(|(name, status)| HealthGateReport::new(name.clone(), status.clone()))
            .collect()
    }
}

fn warn_rejected_phase(from: HealthPhase, to: HealthPhase) {
    if from == to {
        return;
    }
    tracing::warn!(?from, ?to, "rejected backward health phase transition");
}
