// Copyright (c) Subzero Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

use serde::{Deserialize, Serialize};

/// Health status enumeration for node health checks
/// This follows the Solana RPC health check specification:
/// - "ok" when healthy
/// - "unhealthy" when experiencing issues
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "lowercase")]
pub enum HealthStatus {
    /// Node is healthy and operating normally
    #[default]
    Ok,
    /// Node is unhealthy or experiencing issues
    Unhealthy,
}

impl HealthStatus {
    /// Create a healthy status
    pub fn ok() -> Self {
        Self::Ok
    }

    /// Create an unhealthy status
    pub fn unhealthy() -> Self {
        Self::Unhealthy
    }

    /// Returns true if the node is healthy.
    pub fn is_ok(&self) -> bool {
        self == &Self::Ok
    }
}

/// The response message for a getHealth request.
/// Returns the current health status of the node.
#[derive(Debug, Serialize, Deserialize)]
#[serde(untagged)]
pub enum GetHealthResponse {
    /// Simple status response (backward compatible)
    Simple(HealthStatus),
    /// Detailed health response with additional information
    Detailed {
        status: HealthStatus,
        #[serde(skip_serializing_if = "Option::is_none")]
        details: Option<HealthDetails>,
    },
}

/// REX health status enumeration
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum RexHealthStatus {
    /// REX is healthy and responding (HTTP 200)
    Healthy,
    /// REX is unhealthy or not responding
    Unhealthy,
    /// REX health is unknown or not applicable (e.g., for non-validators)
    Unknown,
}

/// Detailed health information about the node
#[derive(Debug, Serialize, Deserialize)]
pub struct HealthDetails {
    /// Whether the node is a validator
    pub is_validator: bool,
    /// Whether consensus is running (for validators)
    pub consensus_running: bool,
    /// Whether subdag processing is active
    pub subdag_processing_active: bool,
    /// REX service health status
    pub rex_health: RexHealthStatus,
    /// Latest commit round
    pub latest_commit_round: u64,
    /// Latest bank slot (deprecated: use `latest_bank_block_height` instead)
    #[deprecated(since = "0.1.0", note = "use `latest_bank_block_height` instead")]
    pub latest_bank_slot: u64,
    /// Latest bank block height
    pub latest_bank_block_height: u64,
}

impl GetHealthResponse {
    pub fn simple(status: HealthStatus) -> Self {
        Self::Simple(status)
    }

    pub fn ok() -> Self {
        Self::Simple(HealthStatus::Ok)
    }

    pub fn unhealthy() -> Self {
        Self::Simple(HealthStatus::Unhealthy)
    }

    pub fn detailed(status: HealthStatus, details: HealthDetails) -> Self {
        Self::Detailed {
            status,
            details: Some(details),
        }
    }

    pub fn status(&self) -> HealthStatus {
        match self {
            Self::Simple(status) => status.clone(),
            Self::Detailed { status, .. } => status.clone(),
        }
    }
}

/// Request for getHealth RPC call
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct GetHealthRequest {
    /// Whether to return detailed health information
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub detailed: Option<bool>,
}

impl GetHealthRequest {
    pub fn new() -> Self {
        Self { detailed: None }
    }

    pub fn with_detailed(detailed: bool) -> Self {
        Self {
            detailed: Some(detailed),
        }
    }
}

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

/// Response type for the HTTP /health endpoint.
///
/// This is separate from the RPC getHealth method and includes
/// git information for deployment tracking.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HealthEndpointResponse {
    /// Current health status ("ok" or "unhealthy")
    pub status: String,
    /// Git commit hash of the running binary
    #[serde(rename = "git-hash")]
    pub git_hash: String,
    /// Optional release tag
    #[serde(rename = "release-tag", skip_serializing_if = "Option::is_none")]
    pub release_tag: Option<String>,
}

impl HealthEndpointResponse {
    /// Create a new health endpoint response.
    pub fn new(is_healthy: bool, git_hash: String, release_tag: Option<String>) -> Self {
        Self {
            status: if is_healthy {
                "ok".to_string()
            } else {
                "unhealthy".to_string()
            },
            git_hash,
            release_tag,
        }
    }

    /// Returns true if the status indicates healthy.
    pub fn is_healthy(&self) -> bool {
        self.status == "ok"
    }
}

#[cfg(test)]
mod tests {
    use serde_json::{from_str, to_string};

    use super::*;

    #[test]
    fn test_health_status_ok_serialization() {
        let status = HealthStatus::Ok;
        let json = to_string(&status).unwrap();
        assert_eq!(json, "\"ok\"");

        let deserialized: HealthStatus = from_str(&json).unwrap();
        assert_eq!(deserialized, HealthStatus::Ok);
    }

    #[test]
    fn test_health_status_unhealthy_serialization() {
        let status = HealthStatus::Unhealthy;
        let json = to_string(&status).unwrap();
        assert_eq!(json, "\"unhealthy\"");

        let deserialized: HealthStatus = from_str(&json).unwrap();
        assert_eq!(deserialized, HealthStatus::Unhealthy);
    }

    #[test]
    fn test_get_health_response_ok_serialization() {
        let response = GetHealthResponse::ok();
        let json = to_string(&response).unwrap();
        assert_eq!(json, "\"ok\"");

        let deserialized: GetHealthResponse = from_str(&json).unwrap();
        match deserialized {
            GetHealthResponse::Simple(status) => assert_eq!(status, HealthStatus::Ok),
            _ => panic!("Expected simple response"),
        }
    }

    #[test]
    fn test_get_health_response_unhealthy_serialization() {
        let response = GetHealthResponse::unhealthy();
        let json = to_string(&response).unwrap();
        assert_eq!(json, "\"unhealthy\"");

        let deserialized: GetHealthResponse = from_str(&json).unwrap();
        match deserialized {
            GetHealthResponse::Simple(status) => assert_eq!(status, HealthStatus::Unhealthy),
            _ => panic!("Expected simple response"),
        }
    }

    #[test]
    #[allow(deprecated)]
    fn test_get_health_response_detailed_serialization() {
        let details = HealthDetails {
            is_validator: true,
            consensus_running: true,
            subdag_processing_active: true,
            rex_health: RexHealthStatus::Healthy,
            latest_commit_round: 100,
            latest_bank_slot: 200,
            latest_bank_block_height: 150,
        };
        let response = GetHealthResponse::detailed(HealthStatus::Ok, details);
        let json = to_string(&response).unwrap();

        let deserialized: GetHealthResponse = from_str(&json).unwrap();
        match deserialized {
            GetHealthResponse::Detailed { status, details } => {
                assert_eq!(status, HealthStatus::Ok);
                let details = details.expect("Expected details");
                assert!(details.is_validator);
                assert!(details.consensus_running);
                assert_eq!(details.rex_health, RexHealthStatus::Healthy);
                assert_eq!(details.latest_commit_round, 100);
            }
            _ => panic!("Expected detailed response"),
        }
    }
}
