use anyhow::{Context, Result};
use rcgen::{CertificateParams, DistinguishedName, DnType, KeyPair, SanType};
use std::net::IpAddr;
use std::path::Path;

pub fn issue_node_cert(
    node_id: &str,
    hostname: &str,
    ip: &str,
    ca_cert_path: Option<&str>,
    ca_key_path: Option<&str>,
) -> Result<(String, String)> {
    let mut dns_names = Vec::new();
    if !node_id.trim().is_empty() {
        dns_names.push(node_id.to_string());
    }
    if !hostname.trim().is_empty() && hostname != node_id {
        dns_names.push(hostname.to_string());
    }
    if dns_names.is_empty() {
        dns_names.push("ultracloud-node".to_string());
    }

    let mut params =
        CertificateParams::new(dns_names).context("failed to create certificate params")?;
    let mut distinguished_name = DistinguishedName::new();
    if !node_id.trim().is_empty() {
        distinguished_name.push(DnType::CommonName, node_id);
    }
    params.distinguished_name = distinguished_name;

    if let Ok(ip_addr) = ip.parse::<IpAddr>() {
        params.subject_alt_names.push(SanType::IpAddress(ip_addr));
    }

    let key_pair = KeyPair::generate().context("failed to generate TLS key pair")?;

    if let (Some(ca_cert_path), Some(ca_key_path)) = (ca_cert_path, ca_key_path) {
        let ca_cert_pem = std::fs::read_to_string(Path::new(ca_cert_path))
            .with_context(|| format!("failed to read CA cert from {}", ca_cert_path))?;
        let ca_key_pem = std::fs::read_to_string(Path::new(ca_key_path))
            .with_context(|| format!("failed to read CA key from {}", ca_key_path))?;

        let ca_key_pair =
            KeyPair::from_pem(&ca_key_pem).context("failed to parse CA key pair from PEM")?;
        let ca_params = CertificateParams::from_ca_cert_pem(&ca_cert_pem)
            .context("failed to parse CA certificate")?;
        let ca_cert = ca_params
            .self_signed(&ca_key_pair)
            .context("failed to build CA certificate for signing")?;

        let cert = params
            .signed_by(&key_pair, &ca_cert, &ca_key_pair)
            .context("failed to sign node certificate")?;
        let cert_pem = cert.pem();
        let key_pem = key_pair.serialize_pem();
        return Ok((cert_pem, key_pem));
    }

    let cert = params
        .self_signed(&key_pair)
        .context("failed to self-sign node certificate")?;
    let cert_pem = cert.pem();
    let key_pem = key_pair.serialize_pem();
    Ok((cert_pem, key_pem))
}
