//! Background certificate renewal manager.
//!
//! Periodically checks the certificate store for certs expiring within
//! `renew_before_days` and re-provisions them via ACME, then hot-swaps
//! the new certificates into the SNI resolver.

use crate::acme::AcmeClient;
use crate::config::{TlsConfig, TlsMode};
use crate::resolver::SniCertResolver;
use crate::store::CertStore;
use chrono::Utc;
use std::sync::{Arc, Mutex};
use tokio::task::JoinHandle;
use tokio::time::{self, Duration};

/// How often the renewal task checks for expiring certificates.
const CHECK_INTERVAL: Duration = Duration::from_secs(12 * 3600); // 12 hours

/// Background certificate renewal manager.
pub struct RenewalManager {
    store: Arc<Mutex<CertStore>>,
    resolver: Arc<SniCertResolver>,
    config: TlsConfig,
}

impl RenewalManager {
    /// Create a new renewal manager.
    ///
    /// The `CertStore` is wrapped in a `Mutex` because `rusqlite::Connection`
    /// is not `Send`. All database access happens synchronously inside the
    /// lock.
    pub fn new(
        store: Arc<Mutex<CertStore>>,
        resolver: Arc<SniCertResolver>,
        config: TlsConfig,
    ) -> Self {
        Self {
            store,
            resolver,
            config,
        }
    }

    /// Spawn the background renewal loop.
    ///
    /// Returns a `JoinHandle` that runs indefinitely (until the task is
    /// cancelled or the server shuts down).
    pub fn start(self) -> JoinHandle<()> {
        tokio::spawn(async move {
            tracing::info!(
                renew_before_days = self.config.renew_before_days,
                "certificate renewal manager started"
            );

            let mut interval = time::interval(CHECK_INTERVAL);

            loop {
                interval.tick().await;
                self.check_and_renew().await;
            }
        })
    }

    /// Spawn with a custom check interval (for testing).
    pub fn start_with_interval(self, check_interval: Duration) -> JoinHandle<()> {
        tokio::spawn(async move {
            tracing::info!(
                renew_before_days = self.config.renew_before_days,
                interval_secs = check_interval.as_secs(),
                "certificate renewal manager started (custom interval)"
            );

            let mut interval = time::interval(check_interval);

            loop {
                interval.tick().await;
                self.check_and_renew().await;
            }
        })
    }

    /// Check for expiring certificates and renew them.
    async fn check_and_renew(&self) {
        let expiring = {
            let store = match self.store.lock() {
                Ok(s) => s,
                Err(e) => {
                    tracing::error!(error = %e, "failed to lock cert store");
                    return;
                }
            };
            match store.get_expiring(self.config.renew_before_days) {
                Ok(certs) => certs,
                Err(e) => {
                    tracing::error!(error = %e, "failed to query expiring certificates");
                    return;
                }
            }
        };

        if expiring.is_empty() {
            tracing::debug!("no certificates need renewal");
            return;
        }

        tracing::info!(count = expiring.len(), "found certificates needing renewal");

        // Only attempt ACME renewal if mode is Auto.
        let is_auto = matches!(self.config.mode, TlsMode::Auto);

        for cert in &expiring {
            let days_left = (cert.not_after - Utc::now()).num_days();
            tracing::info!(
                domain = %cert.domain,
                days_left,
                "certificate needs renewal"
            );

            if !is_auto {
                tracing::warn!(
                    domain = %cert.domain,
                    "TLS mode is not Auto — skipping ACME renewal (manual action required)"
                );
                continue;
            }

            match self.renew_single(&cert.domain).await {
                Ok(()) => {
                    tracing::info!(domain = %cert.domain, "certificate renewed successfully");
                }
                Err(e) => {
                    tracing::error!(
                        domain = %cert.domain,
                        error = %e,
                        "failed to renew certificate"
                    );
                }
            }
        }
    }

    /// Renew a single domain's certificate via ACME.
    async fn renew_single(&self, domain: &str) -> anyhow::Result<()> {
        let client = AcmeClient::new(&self.config)?.with_store(Arc::clone(&self.store));
        let domains = vec![domain.to_string()];

        let (cert_pem, key_pem) = client
            .provision_cert(&domains, |_challenge| {
                // In a real deployment this callback would set up the HTTP-01
                // or TLS-ALPN-01 challenge response on the running server.
                // The challenge setup is handled by the server integration layer
                // that wires this into actix-web.
                tracing::debug!(
                    domain,
                    "renewal challenge callback invoked — server integration should handle this"
                );
                Ok(())
            })
            .await?;

        // Parse the certificate to extract validity dates.
        let (not_before, not_after) = extract_validity(&cert_pem)?;

        // Persist to store.
        {
            let store = self
                .store
                .lock()
                .map_err(|e| anyhow::anyhow!("lock store: {e}"))?;
            store.store_cert(domain, &cert_pem, &key_pem, not_before, not_after)?;
        }

        // Hot-swap into the resolver.
        self.resolver.add_cert(domain, &cert_pem, &key_pem)?;

        Ok(())
    }
}

/// Extract not_before / not_after from a PEM certificate.
fn extract_validity(
    cert_pem: &[u8],
) -> anyhow::Result<(chrono::DateTime<Utc>, chrono::DateTime<Utc>)> {
    use x509_parser::pem::parse_x509_pem;

    let (_, pem) =
        parse_x509_pem(cert_pem).map_err(|e| anyhow::anyhow!("parse PEM for validity: {e}"))?;

    let (_, cert) = x509_parser::parse_x509_certificate(&pem.contents)
        .map_err(|e| anyhow::anyhow!("parse X.509 for validity: {e}"))?;

    let not_before = chrono::DateTime::from_timestamp(cert.validity().not_before.timestamp(), 0)
        .unwrap_or_else(Utc::now);

    let not_after = chrono::DateTime::from_timestamp(cert.validity().not_after.timestamp(), 0)
        .unwrap_or_else(Utc::now);

    Ok((not_before, not_after))
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::self_signed::generate_self_signed;

    #[test]
    fn extract_validity_from_self_signed() {
        let out = generate_self_signed(&["test.com".into()]).unwrap();
        let (not_before, not_after) = extract_validity(&out.cert_pem).unwrap();

        let now = Utc::now();
        // not_before should be close to now (within a few seconds).
        assert!((now - not_before).num_seconds().abs() < 60);
        // not_after should be ~365 days from now.
        let days = (not_after - not_before).num_days();
        assert!((364..=366).contains(&days));
    }

    #[test]
    fn extract_validity_invalid_pem() {
        let result = extract_validity(b"not a certificate");
        assert!(result.is_err());
    }

    #[test]
    fn renewal_manager_creation() {
        let store = Arc::new(Mutex::new(CertStore::new(":memory:").unwrap()));
        let resolver = Arc::new(SniCertResolver::new());
        let config = TlsConfig::default();

        let _manager = RenewalManager::new(store, resolver, config);
        // Just verifying construction doesn't panic.
    }

    #[tokio::test]
    async fn renewal_check_with_no_expiring_certs() {
        let store = Arc::new(Mutex::new(CertStore::new(":memory:").unwrap()));
        let resolver = Arc::new(SniCertResolver::new());
        let config = TlsConfig::default();

        let manager = RenewalManager::new(store, resolver, config);

        // Should complete without error (no certs to renew).
        manager.check_and_renew().await;
    }

    #[tokio::test]
    async fn renewal_check_skips_when_not_auto_mode() {
        let store = Arc::new(Mutex::new(CertStore::new(":memory:").unwrap()));
        let resolver = Arc::new(SniCertResolver::new());
        let config = TlsConfig {
            mode: TlsMode::SelfSigned,
            renew_before_days: 365, // Everything would be "expiring"
            ..Default::default()
        };

        // Store a cert that expires within 365 days.
        {
            let out = generate_self_signed(&["test.com".into()]).unwrap();
            let now = Utc::now();
            let s = store.lock().unwrap();
            s.store_cert(
                "test.com",
                &out.cert_pem,
                &out.key_pem,
                now,
                now + chrono::Duration::days(30),
            )
            .unwrap();
        }

        let manager = RenewalManager::new(store, resolver, config);

        // Should not attempt ACME renewal since mode is SelfSigned.
        manager.check_and_renew().await;
        // No panic / no error expected.
    }

    #[tokio::test]
    async fn start_and_cancel() {
        let store = Arc::new(Mutex::new(CertStore::new(":memory:").unwrap()));
        let resolver = Arc::new(SniCertResolver::new());
        let config = TlsConfig::default();

        let manager = RenewalManager::new(store, resolver, config);
        let handle = manager.start_with_interval(Duration::from_secs(3600));

        // Give it a moment, then abort.
        tokio::time::sleep(Duration::from_millis(50)).await;
        handle.abort();

        // Should not panic.
        let _ = handle.await;
    }

    #[test]
    fn check_interval_is_12_hours() {
        assert_eq!(CHECK_INTERVAL, Duration::from_secs(43200));
    }
}
