//! TLS helpers (rustls)

use crate::errors::{FalcornError, Result};
use std::fs::File;
use std::io::BufReader;
use std::sync::Arc;

#[cfg(feature = "ssl")]
use rustls::{ServerConfig, pki_types::CertificateDer, pki_types::PrivateKeyDer};

#[cfg(feature = "ssl")]
use rustls_pemfile::{Item, certs, read_one};

// Load rustls configuration from certificate and key files.
#[cfg(feature = "ssl")]
pub fn load_rustls_config(cert_path: &str, key_path: &str) -> Result<Arc<ServerConfig>> {
    let certs = load_certs(cert_path)?;
    let key = load_private_key(key_path)?;

    let mut config = ServerConfig::builder()
        .with_no_client_auth()
        .with_single_cert(certs, key)
        .map_err(|e| FalcornError::runtime(format!("TLS config error: {}", e)))?;

    config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec(), b"http/1.0".to_vec()];

    Ok(Arc::new(config))
}

// Load certificate files.
#[cfg(feature = "ssl")]
fn load_certs(path: &str) -> Result<Vec<CertificateDer<'static>>> {
    let f = File::open(path).map_err(FalcornError::IoError)?;
    let mut reader = BufReader::new(f);
    let certs = certs(&mut reader)
        // .into_iter()
        // .map(CertificateDer::from)
        .collect::<std::result::Result<Vec<_>, _>>()
        .map_err(|e| FalcornError::runtime(format!("Failed to read certs: {}", e)))?;
    Ok(certs)
}

// Load private key files.
#[cfg(feature = "ssl")]
fn load_private_key(path: &str) -> Result<PrivateKeyDer<'static>> {
    let f = File::open(path).map_err(FalcornError::IoError)?;
    let mut reader = BufReader::new(f);

    loop {
        match read_one(&mut reader) {
            Ok(Some(Item::Pkcs8Key(k))) => return Ok(PrivateKeyDer::Pkcs8(k)),
            Ok(Some(Item::Pkcs1Key(k))) => return Ok(PrivateKeyDer::Pkcs1(k)),
            Ok(Some(Item::Sec1Key(k))) => return Ok(PrivateKeyDer::Sec1(k)),
            Ok(Some(_)) => continue,
            Ok(None) => break,
            Err(e) => {
                return Err(FalcornError::runtime(format!(
                    "Failed to read private key: {}",
                    e
                )));
            }
        }
    }

    Err(FalcornError::runtime("No private key found in key file"))
}

#[cfg(not(feature = "ssl"))]
pub fn load_rustls_config(_cert_path: &str, _key_path: &str) -> Result<()> {
    Err(FalcornError::runtime(
        "TLS support disabled (compile with feature 'ssl')",
    ))
}
