#![doc(
    html_logo_url = "https://github.com/Layr-Labs/eigensdk-rs/assets/91280922/bd13caec-3c00-4afc-839a-b83d2890beb5",
    issue_tracker_base_url = "https://github.com/Layr-Labs/eigensdk-rs/issues/"
)]
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
pub mod error;
use ntex::web::{self, App, HttpResponse, HttpServer, Responder};
use error::NodeApiError;
use serde::{Deserialize, Serialize};
use tracing::info;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum NodeHealth {
    Healthy,
    PartiallyHealthy,
    Unhealthy,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ServiceStatus {
    Up,
    Down,
    Initializing,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NodeService {
    id: String,
    name: String,
    description: String,
    status: ServiceStatus,
}
#[derive(Clone, Deserialize)]
pub struct NodeApi {
    node_name: String,
    node_version: String,
    health: NodeHealth,
    services: Vec<NodeService>,
}
#[allow(unused)]
impl NodeApi {
    pub fn new(node_name: &str, node_version: &str) -> Self {
        Self {
            node_name: node_name.to_string(),
            node_version: node_version.to_string(),
            health: NodeHealth::Healthy,
            services: Vec::new(),
        }
    }
    pub fn update_health(&mut self, new_health: NodeHealth) {
        self.health = new_health;
    }
    pub fn register_service(
        &mut self,
        id: &str,
        name: &str,
        description: &str,
        status: ServiceStatus,
    ) {
        let mut services = &mut self.services;
        services.push(NodeService {
            id: id.to_string(),
            name: name.to_string(),
            description: description.to_string(),
            status,
        });
    }
    pub fn update_service_status(
        &mut self,
        service_id: &str,
        status: ServiceStatus,
    ) -> Result<(), NodeApiError> {
        let mut services = &mut self.services;
        for service in services.iter_mut() {
            if service.id == service_id {
                service.status = status;
                return Ok(());
            }
        }
        Err(NodeApiError::ServiceIdNotFound(service_id.to_string()))
    }
    pub fn deregister_service(&mut self, service_id: &str) -> Result<(), NodeApiError> {
        let mut services = &mut self.services;
        if let Some(index) = services.iter().position(|s| s.id == service_id) {
            services.remove(index);
            return Ok(());
        }
        Err(NodeApiError::ServiceIdNotFound(service_id.to_string()))
    }
}
#[allow(unused)]
pub async fn node_info(api: web::types::State<NodeApi>) -> impl Responder {
    let response = serde_json::json!({
        "node_name": api.node_name,
        "node_version": api.node_version,
        "spec_version": "v0.0.1",
    });
    HttpResponse::Ok().json(&response)
}
#[allow(unused)]
pub async fn health_check(api: web::types::State<NodeApi>) -> impl Responder {
    let health = &api.health;
    match health {
        NodeHealth::Healthy => HttpResponse::Ok().finish(),
        NodeHealth::PartiallyHealthy => HttpResponse::PartialContent().finish(),
        NodeHealth::Unhealthy => HttpResponse::ServiceUnavailable().finish(),
    }
}
#[allow(unused)]
pub async fn list_services(api: web::types::State<NodeApi>) -> impl Responder {
    let services = &api.services;
    HttpResponse::Ok().json(&serde_json::json!({ "services": *services }))
}
#[allow(unused)]
pub async fn service_health(
    api: web::types::State<NodeApi>,
    path: web::types::Path<String>,
) -> impl Responder {
    let service_id = path.into_inner();
    let services = &api.services;
    if let Some(service) = services.iter().find(|s| s.id == service_id) {
        match service.status {
            ServiceStatus::Up => HttpResponse::Ok().finish(),
            ServiceStatus::Down => HttpResponse::ServiceUnavailable().finish(),
            ServiceStatus::Initializing => HttpResponse::PartialContent().finish(),
        }
    } else {
        HttpResponse::NotFound().finish()
    }
}
pub fn create_server(api: NodeApi, ip_port_addr: String) -> std::io::Result<ntex::server::Server> {
    let server = HttpServer::new(move || {
        App::new()
            .state(api.clone()) .route("/eigen/node", web::get().to(node_info))
            .route("/eigen/node/health", web::get().to(health_check))
            .route("/eigen/node/services", web::get().to(list_services))
            .route(
                "/eigen/node/services/{id}/health",
                web::get().to(service_health),
            )
    })
    .bind(ip_port_addr.clone())?
    .run();
    info!("node api server running at port :{}", ip_port_addr);
    Ok(server)
}
#[cfg(test)]
mod tests {
    use super::*;
    use ntex::{http, web::test};
    use reqwest::Client;
    #[tokio::test]
    async fn test_node_handler() {
        let mut node_api = NodeApi::new("test_avs", "v0.0.1");
        node_api.register_service(
            "test_service_id",
            "test_service_name",
            "test_service_description",
            ServiceStatus::Initializing,
        );
        let app = App::new()
            .state(node_api.clone())
            .route("/eigen/node", web::get().to(node_info));
        let app = test::init_service(app).await;
        let req = test::TestRequest::get().uri("/eigen/node").to_request();
        let resp = app.call(req).await.unwrap();
        assert_eq!(resp.status(), http::StatusCode::OK);
        let body = test::read_body(resp).await;
        let body_str = String::from_utf8_lossy(&body);
        let expected_body =
            "{\"node_name\":\"test_avs\",\"node_version\":\"v0.0.1\",\"spec_version\":\"v0.0.1\"}";
        assert_eq!(body_str, expected_body);
    }
    #[tokio::test]
    async fn test_list_services_handler() {
        let tests = vec![
            (
                NodeApi::new("test_avs", "v0.0.1"),
                http::StatusCode::OK,
                "{\"services\":[]}",
            ),
            (
                {
                    let mut node_api = NodeApi::new("test_avs", "v0.0.1");
                    node_api.register_service(
                        "testServiceId",
                        "testServiceName",
                        "testServiceDescription",
                        ServiceStatus::Up,
                    );
                    node_api
                },
                http::StatusCode::OK,
                "{\"services\":[{\"id\":\"testServiceId\",\"name\":\"testServiceName\",\"description\":\"testServiceDescription\",\"status\":\"Up\"}]}",
            ),
            (
                {
                    let mut node_api = NodeApi::new("test_avs", "v0.0.1");
                    node_api.register_service(
                        "testServiceId",
                        "testServiceName",
                        "testServiceDescription",
                        ServiceStatus::Up,
                    );
                    node_api.register_service(
                        "testServiceId2",
                        "testServiceName2",
                        "testServiceDescription2",
                        ServiceStatus::Down,
                    );
                    node_api
                },
                http::StatusCode::OK,
                "{\"services\":[{\"id\":\"testServiceId\",\"name\":\"testServiceName\",\"description\":\"testServiceDescription\",\"status\":\"Up\"},{\"id\":\"testServiceId2\",\"name\":\"testServiceName2\",\"description\":\"testServiceDescription2\",\"status\":\"Down\"}]}",
            ),
        ];
        for (node_api, expected_status, expected_body) in tests {
            let app = test::init_service(
                App::new()
                    .state(node_api.clone())
                    .route("/eigen/node/services", web::get().to(list_services)),
            )
            .await;
            let req = test::TestRequest::get()
                .uri("/eigen/node/services")
                .to_request();
            let resp = app.call(req).await.unwrap();
            assert_eq!(resp.status(), expected_status);
            let body = test::read_body(resp).await;
            let body_str = String::from_utf8_lossy(&body);
            let actual_json: serde_json::Value = serde_json::from_str(&body_str).unwrap();
            let expected_json: serde_json::Value = serde_json::from_str(expected_body).unwrap();
            assert_eq!(actual_json, expected_json);
        }
    }
    #[tokio::test]
    async fn test_service_health_handler() {
        let mut node_api = NodeApi::new("test_avs", "v0.0.1");
        node_api.register_service(
            "testServiceId",
            "testServiceName",
            "testServiceDescription",
            ServiceStatus::Up,
        );
        let app = test::init_service(App::new().state(node_api.clone()).route(
            "/eigen/node/services/{id}/health",
            web::get().to(service_health),
        ))
        .await;
        let req = test::TestRequest::get()
            .uri("/eigen/node/services/testServiceId/health")
            .to_request();
        let resp = app.call(req).await.unwrap();
        assert_eq!(resp.status(), http::StatusCode::OK); let req = test::TestRequest::get()
            .uri("/eigen/node/services/nonexistentService/health")
            .to_request();
        let resp = app.call(req).await.unwrap();
        assert_eq!(resp.status(), http::StatusCode::NOT_FOUND);
        let body = test::read_body(resp).await;
        assert_eq!(body.len(), 0);
    }
    #[ntex::test]
    async fn test_create_server() -> std::io::Result<()> {
        let mut node_api = NodeApi::new("test_node", "v1.0.0");
        node_api.register_service(
            "test_service",
            "Test Service",
            "Test service description",
            ServiceStatus::Up,
        );
        let ip_port_addr = "127.0.0.1:8081".to_string();
        let server = create_server(node_api.clone(), ip_port_addr.clone())?;
        ntex::rt::spawn(server);
        tokio::time::sleep(std::time::Duration::from_millis(500)).await;
        let client = Client::new();
        let resp = client
            .get(format!("http://{}/eigen/node", ip_port_addr))
            .send()
            .await
            .unwrap();
        assert_eq!(resp.status(), reqwest::StatusCode::OK);
        let resp = client
            .get(format!("http://{}/eigen/node/health", ip_port_addr))
            .send()
            .await
            .unwrap();
        assert_eq!(resp.status(), reqwest::StatusCode::OK);
        let resp = client
            .get(format!("http://{}/eigen/node/services", ip_port_addr))
            .send()
            .await
            .unwrap();
        assert_eq!(resp.status(), reqwest::StatusCode::OK);
        let resp = client
            .get(format!(
                "http://{}/eigen/node/services/test_service/health",
                ip_port_addr
            ))
            .send()
            .await
            .unwrap();
        assert_eq!(resp.status(), reqwest::StatusCode::OK);
        Ok(())
    }
}