use std::path::PathBuf;

use anyhow::Context;
use quinn::ServerConfig;
use quinn_proto::crypto::rustls::QuicServerConfig;
use rcgen::generate_simple_self_signed;
use rustls::pki_types::{CertificateDer, PrivateKeyDer};
use rustls::version::TLS13;

/// Ensure a self-signed certificate exists on disk and return a QUIC server config.
/// When `production` is true, cert and key must already exist (no auto-generation).
pub fn build_server_config(
    cert_path: &PathBuf,
    key_path: &PathBuf,
    production: bool,
) -> anyhow::Result<ServerConfig> {
    if !cert_path.exists() || !key_path.exists() {
        if production {
            anyhow::bail!(
                "TLS cert or key missing at {:?} / {:?}; production mode forbids auto-generation",
                cert_path,
                key_path
            );
        }
        generate_self_signed_cert(cert_path, key_path)?;
    }

    let cert_bytes = std::fs::read(cert_path).context("read cert")?;
    let key_bytes = std::fs::read(key_path).context("read key")?;

    // Validate certificate expiry and warn about self-signed certs.
    validate_certificate(&cert_bytes)?;

    let cert_chain = vec![CertificateDer::from(cert_bytes)];
    let key = PrivateKeyDer::try_from(key_bytes).map_err(|_| anyhow::anyhow!("invalid key"))?;

    let mut tls = rustls::ServerConfig::builder_with_protocol_versions(&[&TLS13])
        .with_no_client_auth()
        .with_single_cert(cert_chain, key)?;
    tls.alpn_protocols = vec![b"capnp".to_vec()];

    let crypto = QuicServerConfig::try_from(tls)
        .map_err(|e| anyhow::anyhow!("invalid server TLS config: {e}"))?;

    Ok(ServerConfig::with_crypto(std::sync::Arc::new(crypto)))
}

fn generate_self_signed_cert(cert_path: &PathBuf, key_path: &PathBuf) -> anyhow::Result<()> {
    if let Some(parent) = cert_path.parent() {
        std::fs::create_dir_all(parent).context("create cert dir")?;
    }
    if let Some(parent) = key_path.parent() {
        std::fs::create_dir_all(parent).context("create key dir")?;
    }

    let subject_alt_names = vec![
        "localhost".to_string(),
        "127.0.0.1".to_string(),
        "::1".to_string(),
    ];

    let issued = generate_simple_self_signed(subject_alt_names)?;
    let key_der = issued.key_pair.serialize_der();

    std::fs::write(cert_path, issued.cert.der()).context("write cert")?;
    std::fs::write(key_path, &key_der).context("write key")?;
    #[cfg(unix)]
    {
        use std::os::unix::fs::PermissionsExt;
        let perms = std::fs::Permissions::from_mode(0o600);
        std::fs::set_permissions(key_path, perms).context("set key permissions")?;
    }

    tracing::info!(
        cert = %cert_path.display(),
        key = %key_path.display(),
        "generated self-signed TLS certificate"
    );

    Ok(())
}

/// Validate a DER-encoded X.509 certificate: bail if expired, warn if expiring
/// soon or self-signed.
fn validate_certificate(der_bytes: &[u8]) -> anyhow::Result<()> {
    use x509_parser::prelude::*;

    let (_, cert) = X509Certificate::from_der(der_bytes)
        .map_err(|e| anyhow::anyhow!("failed to parse X.509 certificate: {e}"))?;

    let validity = cert.validity();
    let now = ASN1Time::now();

    if !validity.is_valid_at(now) {
        anyhow::bail!(
            "TLS certificate expired (not_after: {})",
            validity.not_after
        );
    }

    // Warn if expiring within 30 days.
    let thirty_days = std::time::Duration::from_secs(30 * 24 * 60 * 60);
    let cutoff = now.timestamp() + thirty_days.as_secs() as i64;
    if validity.not_after.timestamp() < cutoff {
        tracing::warn!(
            not_after = %validity.not_after,
            "TLS certificate expires within 30 days"
        );
    }

    // Warn if self-signed (issuer == subject).
    if cert.issuer() == cert.subject() {
        tracing::warn!("TLS certificate is self-signed (issuer == subject)");
    }

    Ok(())
}
