use crate::app_config::AppConfig;
use crate::devices::device::{DeviceId, DeviceInfo};
use crate::energy::energy_actor;
use crate::energy::energy_actor::RelaySyncStatus;
use crate::ota::ota_manager;
use crate::ota::ota_update::OTAPlatform;
use crate::server::WebEnergyActor;
use crate::server::custom_error::HttpResult;
use crate::server::devices_api::jwt_parser::JWTRequest;
use actix_web::{HttpResponse, web};
use openssl::nid::Nid;
use openssl::x509::X509Req;
use std::str::FromStr;

#[derive(Debug, serde::Deserialize)]
pub struct EnrollRequest {
    /// Device CSR
    csr: String,
    /// Associated device information
    info: DeviceInfo,
}

/// Enroll a new device
pub async fn enroll(req: web::Json<EnrollRequest>, actor: WebEnergyActor) -> HttpResult {
    // Check device information
    if let Some(e) = req.info.error() {
        log::error!("Failed to validate device information! {e}");
        return Ok(HttpResponse::BadRequest().json(e));
    }

    // Check CSR
    let csr = match X509Req::from_pem(req.csr.as_bytes()) {
        Ok(r) => r,
        Err(e) => {
            log::error!("Failed to parse given CSR! {e}");
            return Ok(HttpResponse::BadRequest().json("Failed to parse given CSR!"));
        }
    };

    if !csr.verify(csr.public_key()?.as_ref())? {
        log::error!("Invalid CSR signature!");
        return Ok(HttpResponse::BadRequest().json("Could not verify CSR signature!"));
    }

    let cn = match csr.subject_name().entries_by_nid(Nid::COMMONNAME).next() {
        None => {
            log::error!("Missing Common Name in CSR!");
            return Ok(HttpResponse::BadRequest().json("Missing Common Name in CSR!"));
        }
        Some(cn) => cn.data().as_utf8()?.to_string(),
    };

    if !lazy_regex::regex!("[a-zA-Z0-9 ]{1,100}").is_match(&cn) {
        log::error!("Given Common Name is invalid!");
        return Ok(HttpResponse::BadRequest().json("Invalid Common Name in CSR!"));
    }

    let device_id = DeviceId(cn);
    log::info!(
        "Received enrollment request for device with ID {device_id:?} - {:#?}",
        req.info
    );

    if actor
        .send(energy_actor::CheckDeviceExists(device_id.clone()))
        .await?
    {
        log::error!("Device could not be enrolled: it already exists!");
        return Ok(
            HttpResponse::Conflict().json("A device with the same ID has already been enrolled!")
        );
    }

    actor
        .send(energy_actor::EnrollDevice(device_id, req.0.info, csr))
        .await??;

    Ok(HttpResponse::Accepted().json("Device successfully enrolled"))
}

#[derive(serde::Deserialize)]
pub struct ReqWithDevID {
    id: DeviceId,
}

#[derive(serde::Serialize)]
#[serde(tag = "status")]
enum EnrollmentDeviceStatus {
    Unknown,
    Pending,
    Validated,
}

/// Check device enrollment status
pub async fn enrollment_status(
    query: web::Query<ReqWithDevID>,
    actor: WebEnergyActor,
) -> HttpResult {
    let dev = actor
        .send(energy_actor::GetSingleDevice(query.id.clone()))
        .await?;

    let status = match dev {
        None => EnrollmentDeviceStatus::Unknown,
        Some(d) if d.validated => EnrollmentDeviceStatus::Validated,
        _ => EnrollmentDeviceStatus::Pending,
    };

    Ok(HttpResponse::Ok().json(status))
}

/// Get device certificate
pub async fn get_certificate(query: web::Query<ReqWithDevID>, actor: WebEnergyActor) -> HttpResult {
    let dev = actor
        .send(energy_actor::GetSingleDevice(query.id.clone()))
        .await?;

    let dev = match dev {
        Some(d) if d.validated => d,
        _ => {
            log::error!("Device attempted to retrieve an unavailable certificate!");
            return Ok(HttpResponse::UnprocessableEntity().json("Certificate not available yet!"));
        }
    };

    let cert = std::fs::read(AppConfig::get().device_cert_path(&dev.id))?;

    Ok(HttpResponse::Ok()
        .content_type("application/x-pem-file")
        .body(cert))
}

#[derive(Debug, serde::Serialize, serde::Deserialize)]
struct Claims {
    info: DeviceInfo,
}

#[derive(serde::Serialize)]
struct SyncResult {
    relays: Vec<RelaySyncStatus>,
    available_update: Option<semver::Version>,
}

/// Synchronize device
pub async fn sync_device(body: web::Json<JWTRequest>, actor: WebEnergyActor) -> HttpResult {
    let (device, claims) = body.0.parse_jwt::<Claims>(actor.clone()).await?;

    let relays = actor
        .send(energy_actor::SynchronizeDevice(
            device.id,
            claims.info.clone(),
        ))
        .await??;

    let mut available_update = None;

    // Check if the version is available
    if let Some(desired) = device.desired_version {
        if claims.info.version < desired
            && ota_manager::update_exists(OTAPlatform::from_str(&claims.info.reference)?, &desired)?
        {
            available_update = Some(desired);
        }
    }

    Ok(HttpResponse::Ok().json(SyncResult {
        relays,
        available_update,
    }))
}