use crate::B64_URL_SAFE_NO_PAD;
use async_trait::async_trait;
use base64::Engine;
use std::{
    io::{Error as IoError, ErrorKind},
    path::Path,
};
#[cfg(feature = "use_async_std")]
use async_std::{
    fs::{create_dir_all as cdall, read, OpenOptions},
    io::WriteExt,
    os::unix::fs::OpenOptionsExt,
};
#[cfg(feature = "use_tokio")]
use tokio::{
    fs::{create_dir_all, read, OpenOptions},
    io::AsyncWriteExt,
};
use crate::crypto::sha256_hasher;
#[async_trait]
pub trait AcmeCache {
    type Error: CacheError;
    async fn read_account(&self, contacts: &[&str]) -> Result<Option<Vec<u8>>, Self::Error>;
    async fn write_account(&self, contacts: &[&str], data: &[u8]) -> Result<(), Self::Error>;
    async fn write_certificate(
        &self,
        domains: &[String],
        directory_url: &str,
        key_pem: &str,
        certificate_pem: &str,
    ) -> Result<(), Self::Error>;
}
#[async_trait]
impl<P> AcmeCache for P
where
    P: AsRef<Path> + Send + Sync,
{
    type Error = IoError;
    async fn read_account(&self, contacts: &[&str]) -> Result<Option<Vec<u8>>, Self::Error> {
        let file = cached_key_file_name(contacts);
        let mut path = self.as_ref().to_path_buf();
        path.push(file);
        match read(path).await {
            Ok(content) => Ok(Some(content)),
            Err(err) => match err.kind() {
                ErrorKind::NotFound => Ok(None),
                _ => Err(err),
            },
        }
    }
    async fn write_account(&self, contacts: &[&str], contents: &[u8]) -> Result<(), Self::Error> {
        let mut path = self.as_ref().to_path_buf();
        create_dir_all(&path).await?;
        path.push(cached_key_file_name(contacts));
        Ok(write(path, contents).await?)
    }
    async fn write_certificate(
        &self,
        domains: &[String],
        directory_url: &str,
        key_pem: &str,
        certificate_pem: &str,
    ) -> Result<(), Self::Error> {
        let hash = {
            let mut ctx = sha256_hasher();
            for domain in domains {
                ctx.update(domain.as_ref());
                ctx.update(&[0])
            }
            ctx.update(directory_url.as_bytes());
            B64_URL_SAFE_NO_PAD.encode(ctx.finish())
        };
        let file = AsRef::<Path>::as_ref(self).join(&format!("cached_cert_{}", hash));
        let content = format!("{}\n{}", key_pem, certificate_pem);
        write(&file, &content).await?;
        Ok(())
    }
}
pub trait CacheError: std::error::Error + Send + Sync + 'static {}
impl<T> CacheError for T where T: std::error::Error + Send + Sync + 'static {}
#[cfg(feature = "use_async_std")]
async fn create_dir_all(a: impl AsRef<Path>) -> Result<(), IoError> {
    let p = a.as_ref();
    let p = <&async_std::path::Path>::from(p);
    cdall(p).await
}
#[cfg(not(any(feature = "use_tokio", feature = "use_async_std")))]
async fn create_dir_all(_a: impl AsRef<Path>) -> Result<(), IoError> {
    Err(IoError::new(
        ErrorKind::NotFound,
        "no async backend selected",
    ))
}
#[cfg(not(any(feature = "use_tokio", feature = "use_async_std")))]
async fn read(_a: impl AsRef<Path>) -> Result<Vec<u8>, IoError> {
    Err(IoError::new(
        ErrorKind::NotFound,
        "no async backend selected",
    ))
}
#[cfg(not(any(feature = "use_tokio", feature = "use_async_std")))]
async fn write(_a: impl AsRef<Path>, _c: impl AsRef<[u8]>) -> Result<(), IoError> {
    Err(IoError::new(
        ErrorKind::NotFound,
        "no async backend selected",
    ))
}
#[cfg(any(feature = "use_tokio", feature = "use_async_std"))]
async fn write(file_path: impl AsRef<Path>, content: impl AsRef<[u8]>) -> Result<(), IoError> {
    let mut file = OpenOptions::new();
    file.write(true).create(true).truncate(true);
    #[cfg(unix)]
    file.mode(0o600); let mut buffer = file.open(file_path.as_ref()).await?;
    buffer.write_all(content.as_ref()).await?;
    Ok(())
}
fn cached_key_file_name(contact: &[&str]) -> String {
    let mut ctx = sha256_hasher();
    for el in contact {
        ctx.update(el.as_ref());
        ctx.update(&[0])
    }
    let hash = B64_URL_SAFE_NO_PAD.encode(ctx.finish());
    format!("cached_account_{}", hash)
}