use rcgen::{
    BasicConstraints, CertificateParams, DistinguishedName, DnType, ExtendedKeyUsagePurpose,
    IsCa, KeyPair, KeyUsagePurpose, SanType,
};
use time::{Duration, OffsetDateTime};

use crate::error::Error;
use crate::node::Node;

/// Ed25519 PKCS#8 v1 DER prefix (RFC 8410).
///
/// The full encoding is this 16-byte header followed by the 32-byte private key seed.
const PKCS8_ED25519_PREFIX: [u8; 16] = [
    0x30, 0x2e, // SEQUENCE (46 bytes)
    0x02, 0x01, 0x00, // INTEGER 0 (version)
    0x30, 0x05, // SEQUENCE (5 bytes)
    0x06, 0x03, 0x2b, 0x65, 0x70, // OID 1.3.101.112 (Ed25519)
    0x04, 0x22, // OCTET STRING (34 bytes)
    0x04, 0x20, // OCTET STRING (32 bytes) -- the actual key follows
];

/// Parameters for X.509 certificate generation.
pub struct X509Params {
    /// Common Name (CN) for the subject.
    pub common_name: String,
    /// Optional Organization (O).
    pub organization: Option<String>,
    /// Optional Country (C), two-letter ISO code.
    pub country: Option<String>,
    /// Certificate validity in days from now (default: 365).
    pub validity_days: u32,
    /// Whether this certificate is a CA.
    pub is_ca: bool,
    /// DNS Subject Alternative Names.
    pub san_dns_names: Vec<String>,
    /// Email Subject Alternative Names.
    pub san_emails: Vec<String>,
}

impl Default for X509Params {
    fn default() -> Self {
        Self {
            common_name: String::new(),
            organization: None,
            country: None,
            validity_days: 365,
            is_ca: false,
            san_dns_names: Vec::new(),
            san_emails: Vec::new(),
        }
    }
}

/// A generated X.509 certificate with its PEM and DER representations.
pub struct X509Certificate {
    /// PEM-encoded certificate.
    pub cert_pem: String,
    /// DER-encoded certificate bytes.
    pub cert_der: Vec<u8>,
    /// PEM-encoded private key (PKCS#8).
    pub key_pem: String,
}

impl Node {
    /// Generate a self-signed X.509 certificate from this node's derived key material.
    pub fn x509_self_signed(&self, params: &X509Params) -> Result<X509Certificate, Error> {
        let key_pair = self.rcgen_key_pair()?;
        let cert_params = build_cert_params(params)?;
        let cert = cert_params
            .self_signed(&key_pair)
            .map_err(|e| Error::X509(e.to_string()))?;

        Ok(X509Certificate {
            cert_pem: cert.pem(),
            cert_der: cert.der().to_vec(),
            key_pem: key_pair.serialize_pem(),
        })
    }

    /// Generate an X.509 certificate signed by an issuer node.
    ///
    /// The `issuer` node acts as the Certificate Authority. The issuer's own
    /// certificate (`issuer_cert_pem`) must be provided so rcgen can set the
    /// issuer distinguished name and authority key identifier.
    pub fn x509_signed_by(
        &self,
        issuer: &Node,
        issuer_cert_pem: &str,
        params: &X509Params,
    ) -> Result<X509Certificate, Error> {
        let subject_kp = self.rcgen_key_pair()?;
        let issuer_kp = issuer.rcgen_key_pair()?;

        let issuer_params = CertificateParams::from_ca_cert_pem(issuer_cert_pem)
            .map_err(|e| Error::X509(e.to_string()))?;
        let issuer_cert = issuer_params
            .self_signed(&issuer_kp)
            .map_err(|e| Error::X509(e.to_string()))?;

        let subject_params = build_cert_params(params)?;
        let cert = subject_params
            .signed_by(&subject_kp, &issuer_cert, &issuer_kp)
            .map_err(|e| Error::X509(e.to_string()))?;

        Ok(X509Certificate {
            cert_pem: cert.pem(),
            cert_der: cert.der().to_vec(),
            key_pem: subject_kp.serialize_pem(),
        })
    }

    fn rcgen_key_pair(&self) -> Result<KeyPair, Error> {
        let mut pkcs8_der = Vec::with_capacity(48);
        pkcs8_der.extend_from_slice(&PKCS8_ED25519_PREFIX);
        pkcs8_der.extend_from_slice(&self.seed);
        KeyPair::try_from(pkcs8_der).map_err(|e| Error::X509(e.to_string()))
    }
}

fn build_cert_params(params: &X509Params) -> Result<CertificateParams, Error> {
    let mut dn = DistinguishedName::new();
    dn.push(DnType::CommonName, &params.common_name);
    if let Some(org) = &params.organization {
        dn.push(DnType::OrganizationName, org);
    }
    if let Some(country) = &params.country {
        dn.push(DnType::CountryName, country);
    }

    let mut san = Vec::new();
    for name in &params.san_dns_names {
        san.push(SanType::DnsName(
            name.clone()
                .try_into()
                .map_err(|e: rcgen::Error| Error::X509(e.to_string()))?,
        ));
    }
    for email in &params.san_emails {
        san.push(SanType::Rfc822Name(
            email
                .clone()
                .try_into()
                .map_err(|e: rcgen::Error| Error::X509(e.to_string()))?,
        ));
    }

    let mut cp = CertificateParams::new(Vec::<String>::new())
        .map_err(|e| Error::X509(e.to_string()))?;
    cp.distinguished_name = dn;
    cp.subject_alt_names = san;
    cp.not_before = OffsetDateTime::now_utc();
    cp.not_after = OffsetDateTime::now_utc() + Duration::days(params.validity_days as i64);

    if params.is_ca {
        cp.is_ca = IsCa::Ca(BasicConstraints::Unconstrained);
        cp.key_usages = vec![
            KeyUsagePurpose::KeyCertSign,
            KeyUsagePurpose::CrlSign,
            KeyUsagePurpose::DigitalSignature,
        ];
    } else {
        cp.is_ca = IsCa::NoCa;
        cp.key_usages = vec![
            KeyUsagePurpose::DigitalSignature,
            KeyUsagePurpose::KeyAgreement,
        ];
        cp.extended_key_usages = vec![
            ExtendedKeyUsagePurpose::ServerAuth,
            ExtendedKeyUsagePurpose::ClientAuth,
        ];
    }

    Ok(cp)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn self_signed_certificate() {
        let seed = b"test-seed-for-x509-generation";
        let node = Node::master(seed).unwrap();
        let child = node
            .derive("m/ssh/user:alice/example.com/bastion/1700000000")
            .unwrap();

        let params = X509Params {
            common_name: "alice@example.com".to_string(),
            organization: Some("Example Corp".to_string()),
            validity_days: 365,
            san_dns_names: vec!["bastion.example.com".to_string()],
            ..Default::default()
        };

        let cert = child.x509_self_signed(&params).unwrap();
        assert!(cert.cert_pem.contains("BEGIN CERTIFICATE"));
        assert!(cert.key_pem.contains("BEGIN PRIVATE KEY"));
        assert!(!cert.cert_der.is_empty());
    }

    #[test]
    fn ca_signed_certificate() {
        let seed = b"ca-signing-test-seed-material";
        let root = Node::master(seed).unwrap();
        let ca_node = root
            .derive("m/ssh/user:ca/example.com/ca.example.com/1")
            .unwrap();
        let leaf_node = root
            .derive("m/ssh/user:web/example.com/web.example.com/1")
            .unwrap();

        let ca_params = X509Params {
            common_name: "Example CA".to_string(),
            organization: Some("Example Corp".to_string()),
            is_ca: true,
            validity_days: 3650,
            ..Default::default()
        };

        let ca_cert = ca_node.x509_self_signed(&ca_params).unwrap();

        let leaf_params = X509Params {
            common_name: "web.example.com".to_string(),
            san_dns_names: vec!["web.example.com".to_string()],
            validity_days: 90,
            ..Default::default()
        };

        let leaf_cert = leaf_node
            .x509_signed_by(&ca_node, &ca_cert.cert_pem, &leaf_params)
            .unwrap();
        assert!(leaf_cert.cert_pem.contains("BEGIN CERTIFICATE"));
    }

    #[test]
    fn deterministic_key_material() {
        let seed = b"deterministic-x509-key-test!!";
        let node = Node::master(seed).unwrap();
        let child = node
            .derive("m/ssh/user:test/example.com/host.example.com/1")
            .unwrap();

        let params = X509Params {
            common_name: "test".to_string(),
            validity_days: 1,
            ..Default::default()
        };

        let cert1 = child.x509_self_signed(&params).unwrap();
        let cert2 = child.x509_self_signed(&params).unwrap();

        // Key material is deterministic even though cert timestamps may differ
        assert_eq!(cert1.key_pem, cert2.key_pem);
    }
}
