use futures_util::future::try_join_all;
use rustls::sign::CertifiedKey;
use std::time::Duration;
use thiserror::Error;
use crate::{
    acme::{Account, AcmeError, Auth, Directory, Identifier, Order},
    cache::AcmeCache,
    crypto::{gen_acme_cert, get_cert_duration_left, CertBuilder},
};
#[cfg(feature = "use_async_std")]
use async_std::task::sleep;
#[cfg(feature = "use_tokio")]
use tokio::time::sleep;
pub async fn order<C, F>(
    set_auth_key: F,
    directory_url: &str,
    domains: &[String],
    cache: Option<&C>,
    contact: &[String],
) -> Result<CertifiedKey, OrderError>
where
    C: AcmeCache,
    F: Fn(String, CertifiedKey) -> Result<(), AcmeError>,
{
    let directory = Directory::discover(directory_url).await?;
    let account = Account::load_or_create(directory, cache, contact).await?;
    let (c, key_pem, cert_pem) = drive_order(set_auth_key, domains.to_vec(), account).await?;
    if let Some(dir) = cache {
        dir.write_certificate(domains, directory_url, &key_pem, &cert_pem)
            .await
            .map_err(AcmeError::cache)?;
    };
    Ok(c)
}
pub async fn drive_order<F>(
    set_auth_key: F,
    domains: Vec<String>,
    account: Account,
) -> Result<(CertifiedKey, String, String), OrderError>
where
    F: Fn(String, CertifiedKey) -> Result<(), AcmeError>,
{
    let cert = CertBuilder::gen_new(domains.clone())?;
    let mut order = account.new_order(domains).await?;
    loop {
        order = match order {
            Order::Pending {
                authorizations,
                finalize,
            } => {
                let auth_futures = authorizations
                    .iter()
                    .map(|url| authorize(&set_auth_key, &account, url));
                try_join_all(auth_futures).await?;
                log::info!("completed all authorizations");
                Order::Ready { finalize }
            }
            Order::Ready { finalize } => {
                log::info!("sending csr");
                let csr = cert.get_csr()?;
                account.send_csr(finalize, csr).await?
            }
            Order::Valid { certificate } => {
                log::info!("download certificate");
                let acme_cert_pem = account.obtain_certificate(certificate).await?;
                let rd = acme_cert_pem.as_bytes();
                let pkey_pem = cert.private_key_as_pem_pkcs8();
                let cert_key = cert.sign(rd).map_err(|_| {
                    AcmeError::Io(std::io::Error::new(
                        std::io::ErrorKind::InvalidData,
                        "could not parse certificate",
                    ))
                })?;
                return Ok((cert_key, pkey_pem, acme_cert_pem));
            }
            Order::Invalid => return Err(OrderError::BadOrder(order)),
        }
    }
}
async fn authorize<F>(set_auth_key: &F, account: &Account, url: &str) -> Result<(), OrderError>
where
    F: Fn(String, CertifiedKey) -> Result<(), AcmeError>,
{
    let (domain, challenge_url) = match account.check_auth(url).await? {
        Auth::Pending {
            identifier,
            challenges,
        } => {
            let Identifier::Dns(domain) = identifier;
            log::info!("trigger challenge for {}", &domain);
            let (challenge, key_auth) = account.tls_alpn_01(&challenges)?;
            let auth_key = gen_acme_cert(vec![domain.clone()], key_auth.as_ref())?;
            set_auth_key(domain.clone(), auth_key)?;
            account.trigger_challenge(&challenge.url).await?;
            (domain, challenge.url.clone())
        }
        Auth::Valid => return Ok(()),
        auth => return Err(OrderError::BadAuth(auth)),
    };
    for i in 0u8..5 {
        sleep(Duration::from_secs(1u64 << i)).await;
        match account.check_auth(url).await? {
            Auth::Pending { .. } => {
                log::info!("authorization for {} still pending", &domain);
                account.trigger_challenge(&challenge_url).await?
            }
            Auth::Valid => return Ok(()),
            auth => return Err(OrderError::BadAuth(auth)),
        }
    }
    Err(OrderError::TooManyAttemptsAuth(domain))
}
pub fn duration_until_renewal_attempt(cert_key: Option<&CertifiedKey>, err_cnt: usize) -> Duration {
    let valid_until = cert_key
        .and_then(|cert_key| cert_key.cert.first())
        .and_then(|cert| get_cert_duration_left(cert.0.as_slice()).ok())
        .unwrap_or_default();
    let wait_secs = valid_until / 2;
    match err_cnt {
        0 => wait_secs,
        err_cnt => wait_secs.max(Duration::from_secs(1 << err_cnt)),
    }
}
#[derive(Error, Debug)]
pub enum OrderError {
    #[error("acme error: {0}")]
    Acme(#[from] AcmeError),
    #[cfg(feature = "use_rustls")]
    #[error("certificate generation error: {0}")]
    Rcgen(#[from] rcgen::RcgenError),
    #[error("bad order object: {0:?}")]
    BadOrder(Order),
    #[error("bad auth object: {0:?}")]
    BadAuth(Auth),
    #[error("authorization for {0} failed too many times")]
    TooManyAttemptsAuth(String),
}