use rocket::{get, post, routes, serde::json::Json, State};

pub struct ServerState {
    primary_name: String,
    map: std::collections::HashMap<
        String,
        std::sync::Arc<tokio::sync::RwLock<crate::controller::CommonData>>,
    >,
}

impl ServerState {
    pub fn new(
        primary_name: &impl ToString,
        map: std::collections::HashMap<
            String,
            std::sync::Arc<tokio::sync::RwLock<crate::controller::CommonData>>,
        >,
    ) -> Self {
        let primary_name = primary_name.to_string();
        Self { primary_name, map }
    }
}

pub fn rocket(state: ServerState) -> rocket::Rocket<rocket::Build> {
    rocket::build().manage(state).mount(
        "/",
        routes![
            metrics,
            interfaces,
            all_interfaces,
            primary_interface,
            interface,
            enable_control,
            disable_control
        ],
    )
}

#[get("/interfaces")]
fn interfaces(state: &State<ServerState>) -> Json<Vec<String>> {
    Json(state.map.keys().cloned().collect())
}

#[get("/interfaces/primary")]
async fn primary_interface(
    state: &State<ServerState>,
) -> Result<Json<crate::controller::CommonData>, ServerError> {
    let s = state
        .map
        .get(&state.primary_name)
        .ok_or(ServerError::InvalidPrimaryName)?
        .read()
        .await
        .clone();

    Ok(Json(s))
}

#[get("/interfaces/data")]
async fn all_interfaces(
    state: &State<ServerState>,
) -> Json<Vec<(String, crate::controller::CommonData)>> {
    let mut data = Vec::new();

    for (k, v) in &state.map {
        data.push((k.clone(), v.read().await.clone()));
    }

    Json(data)
}

#[get("/interface/<name>")]
async fn interface(
    name: &str,
    state: &State<ServerState>,
) -> Result<Json<crate::controller::CommonData>, ServerError> {
    let data = state
        .map
        .get(name)
        .ok_or(ServerError::NotFound)?
        .read()
        .await
        .clone();
    Ok(Json(data))
}

#[get("/metrics")]
fn metrics() -> Result<String, ServerError> {
    Ok(
        prometheus::TextEncoder::new()
            .encode_to_string(&prometheus::default_registry().gather())?,
    )
}

#[post("/enable-control")]
async fn enable_control() {
    log::warn!("enabling control");
    crate::config::write_to_config()
        .await
        .enable_secondary_control = true;
}

#[post("/disable-control")]
async fn disable_control() {
    log::warn!("disabling control");
    crate::config::write_to_config()
        .await
        .enable_secondary_control = false;
}

enum ServerError {
    Prometheus,
    NotFound,
    InvalidPrimaryName,
}

impl From<prometheus::Error> for ServerError {
    fn from(_: prometheus::Error) -> Self {
        Self::Prometheus
    }
}

impl<'a> rocket::response::Responder<'a, 'a> for ServerError {
    fn respond_to(self, _: &'a rocket::Request<'_>) -> rocket::response::Result<'a> {
        Err(match self {
            Self::Prometheus => rocket::http::Status::InternalServerError,
            Self::NotFound => rocket::http::Status::NotFound,
            Self::InvalidPrimaryName => rocket::http::Status::ServiceUnavailable,
        })
    }
}