Documentation
use chrono::Utc;
use faststr::FastStr;
use rustls_pemfile::{certs, private_key};
use rustls_pki_types::{CertificateDer, PrivateKeyDer};
use x509_parser::{extensions::GeneralName, parse_x509_certificate};

use crate::{Error, Result};

// rustls 证书
#[derive(Debug)]
pub struct Cert {
  pub fullchain: Vec<CertificateDer<'static>>,
  pub key: PrivateKeyDer<'static>,
  pub host_li: Vec<FastStr>,
  pub expire: i64,
}

impl Cert {
  pub fn new(fullchain: impl AsRef<str>, key: impl AsRef<str>) -> Result<Self> {
    let mut cert_reader = std::io::Cursor::new(fullchain.as_ref().as_bytes());
    let fullchain = certs(&mut cert_reader)
      .collect::<std::result::Result<Vec<_>, _>>()
      .map_err(|e| Error::CertParse(format!("证书解析失败: {e:?}")))?;

    if fullchain.is_empty() {
      return Err(Error::CertNotFound(
        "exist cert str , but no cert".to_string(),
      ));
    }

    let mut key_reader = std::io::Cursor::new(key.as_ref().as_bytes());
    let key = private_key(&mut key_reader)
      .map_err(|e| Error::CertParse(format!("私钥解析失败: {e:?}")))?
      .ok_or(Error::PrivateKeyNotFound)?;

    let end_entity_cert = &fullchain[0];
    let (_, x509) = parse_x509_certificate(end_entity_cert.as_ref())
      .map_err(|e| Error::CertParse(format!("x509解析失败: {e}")))?;
    let host_li = if let Ok(Some(san)) = x509.subject_alternative_name() {
      san
        .value
        .general_names
        .iter()
        .filter_map(|name| match name {
          GeneralName::DNSName(s) => Some(FastStr::from(s.to_string())),
          _ => None,
        })
        .collect::<Vec<FastStr>>()
    } else {
      x509
        .subject()
        .iter_common_name()
        .filter_map(|cn| {
          cn.attr_value()
            .as_str()
            .ok()
            .map(|s| FastStr::from(s.to_string()))
        })
        .collect::<Vec<FastStr>>()
    };

    if host_li.is_empty() {
      return Err(Error::CertParse("证书没有域名".to_string()));
    }

    let expire = x509.validity().not_after.timestamp();

    Ok(Cert {
      fullchain,
      key,
      host_li,
      expire,
    })
  }

  pub fn is_expired(&self, days: i64) -> bool {
    self.expire < Utc::now().timestamp() + days * 24 * 60 * 60
  }
}