//! TLS configuration types.

use crate::mtls::MtlsConfig;
use crate::on_demand::OnDemandConfig;
use serde::Deserialize;

/// Top-level TLS operating mode.
#[derive(Debug, Clone, Default, Deserialize, PartialEq, Eq)]
#[serde(tag = "mode", rename_all = "snake_case")]
pub enum TlsMode {
    /// Automatic certificate provisioning via ACME (Let's Encrypt / ZeroSSL).
    Auto,
    /// Generate a self-signed certificate on startup (dev/testing).
    SelfSigned,
    /// Load certificate and key from disk.
    Manual { cert_path: String, key_path: String },
    /// TLS disabled — plain HTTP.
    #[default]
    Off,
}

/// Which ACME CA to use for automatic provisioning.
#[derive(Debug, Clone, Default, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum AcmeCa {
    /// Let's Encrypt production.
    #[default]
    Letsencrypt,
    /// Let's Encrypt staging (rate-limit-free, untrusted certs).
    LetsencryptStaging,
    /// ZeroSSL production.
    Zerossl,
    /// Custom ACME directory URL.
    Custom(String),
}

impl AcmeCa {
    /// Return the ACME directory URL for this CA.
    pub fn directory_url(&self) -> &str {
        match self {
            AcmeCa::Letsencrypt => "https://acme-v02.api.letsencrypt.org/directory",
            AcmeCa::LetsencryptStaging => "https://acme-staging-v02.api.letsencrypt.org/directory",
            AcmeCa::Zerossl => "https://acme.zerossl.com/v2/DV90",
            AcmeCa::Custom(url) => url.as_str(),
        }
    }
}

/// ACME challenge type.
#[derive(Debug, Clone, Default, Deserialize, PartialEq, Eq)]
pub enum ChallengeType {
    /// HTTP-01: serve a file at `/.well-known/acme-challenge/<token>`.
    #[default]
    #[serde(rename = "http-01")]
    Http01,
    /// TLS-ALPN-01: present a special self-signed cert on the `acme-tls/1` ALPN.
    #[serde(rename = "tls-alpn-01")]
    TlsAlpn01,
}

/// Minimum TLS protocol version.
///
/// Defaults to TLS 1.3 for best security. Set to `"1.2"` in config if you
/// need to support older clients that don't support TLS 1.3.
#[derive(Debug, Clone, Copy, Default, Deserialize, PartialEq, Eq)]
pub enum MinTlsVersion {
    #[serde(rename = "1.2")]
    Tls12,
    #[default]
    #[serde(rename = "1.3")]
    Tls13,
}

/// Full TLS configuration.
#[derive(Debug, Clone, Deserialize)]
#[serde(default)]
pub struct TlsConfig {
    /// Operating mode.
    #[serde(flatten)]
    pub mode: TlsMode,

    /// Contact e-mail for ACME account registration.
    pub acme_email: Option<String>,

    /// Which ACME CA to use (only relevant in `Auto` mode).
    pub acme_ca: AcmeCa,

    /// ACME challenge type.
    pub challenge: ChallengeType,

    /// How many days before expiry to trigger renewal.
    pub renew_before_days: u32,

    /// Whether to enable OCSP stapling.
    pub ocsp_stapling: bool,

    /// Minimum TLS version.
    pub min_version: MinTlsVersion,

    /// Path to the SQLite certificate store database.
    /// Defaults to `"certs.db"`.
    pub store_path: String,

    /// Domains to provision certificates for (used in Auto/SelfSigned modes).
    pub domains: Vec<String>,

    /// On-demand TLS settings.
    pub on_demand: Option<OnDemandConfig>,

    /// Mutual TLS (mTLS) settings.
    pub mtls: Option<MtlsConfig>,
}

impl Default for TlsConfig {
    fn default() -> Self {
        Self {
            mode: TlsMode::default(),
            acme_email: None,
            acme_ca: AcmeCa::default(),
            challenge: ChallengeType::default(),
            renew_before_days: 30,
            ocsp_stapling: true,
            min_version: MinTlsVersion::default(),
            store_path: "certs.db".to_string(),
            domains: Vec::new(),
            on_demand: None,
            mtls: None,
        }
    }
}

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

    #[test]
    fn default_tls_mode_is_off() {
        let mode = TlsMode::default();
        assert_eq!(mode, TlsMode::Off);
    }

    #[test]
    fn default_config_values() {
        let cfg = TlsConfig::default();
        assert_eq!(cfg.mode, TlsMode::Off);
        assert!(cfg.acme_email.is_none());
        assert_eq!(cfg.acme_ca, AcmeCa::Letsencrypt);
        assert_eq!(cfg.challenge, ChallengeType::Http01);
        assert_eq!(cfg.renew_before_days, 30);
        assert!(cfg.ocsp_stapling);
        assert_eq!(cfg.min_version, MinTlsVersion::Tls13);
        assert_eq!(cfg.store_path, "certs.db");
        assert!(cfg.domains.is_empty());
    }

    #[test]
    fn acme_ca_directory_urls() {
        assert_eq!(
            AcmeCa::Letsencrypt.directory_url(),
            "https://acme-v02.api.letsencrypt.org/directory"
        );
        assert_eq!(
            AcmeCa::LetsencryptStaging.directory_url(),
            "https://acme-staging-v02.api.letsencrypt.org/directory"
        );
        assert_eq!(
            AcmeCa::Zerossl.directory_url(),
            "https://acme.zerossl.com/v2/DV90"
        );
        let custom = AcmeCa::Custom("https://my-ca.example/dir".into());
        assert_eq!(custom.directory_url(), "https://my-ca.example/dir");
    }

    #[test]
    fn deserialize_auto_mode_toml() {
        let toml_str = r#"
mode = "auto"
acme_email = "admin@example.com"
acme_ca = "letsencrypt_staging"
challenge = "tls-alpn-01"
renew_before_days = 14
ocsp_stapling = false
min_version = "1.3"
store_path = "/tmp/certs.db"
domains = ["example.com", "www.example.com"]
"#;
        let cfg: TlsConfig = toml::from_str(toml_str).expect("parse TOML");
        assert_eq!(cfg.mode, TlsMode::Auto);
        assert_eq!(cfg.acme_email.as_deref(), Some("admin@example.com"));
        assert_eq!(cfg.acme_ca, AcmeCa::LetsencryptStaging);
        assert_eq!(cfg.challenge, ChallengeType::TlsAlpn01);
        assert_eq!(cfg.renew_before_days, 14);
        assert!(!cfg.ocsp_stapling);
        assert_eq!(cfg.min_version, MinTlsVersion::Tls13);
        assert_eq!(cfg.store_path, "/tmp/certs.db");
        assert_eq!(cfg.domains, vec!["example.com", "www.example.com"]);
    }

    #[test]
    fn deserialize_self_signed_mode() {
        let toml_str = r#"mode = "self_signed""#;
        let cfg: TlsConfig = toml::from_str(toml_str).expect("parse TOML");
        assert_eq!(cfg.mode, TlsMode::SelfSigned);
    }

    #[test]
    fn deserialize_manual_mode() {
        let toml_str = r#"
mode = "manual"
cert_path = "/etc/ssl/cert.pem"
key_path = "/etc/ssl/key.pem"
"#;
        let cfg: TlsConfig = toml::from_str(toml_str).expect("parse TOML");
        match &cfg.mode {
            TlsMode::Manual {
                cert_path,
                key_path,
            } => {
                assert_eq!(cert_path, "/etc/ssl/cert.pem");
                assert_eq!(key_path, "/etc/ssl/key.pem");
            }
            other => panic!("expected Manual, got {:?}", other),
        }
    }

    #[test]
    fn deserialize_off_mode() {
        let toml_str = r#"mode = "off""#;
        let cfg: TlsConfig = toml::from_str(toml_str).expect("parse TOML");
        assert_eq!(cfg.mode, TlsMode::Off);
    }

    #[test]
    fn default_challenge_is_http01() {
        assert_eq!(ChallengeType::default(), ChallengeType::Http01);
    }

    #[test]
    fn default_min_version_is_tls13() {
        assert_eq!(MinTlsVersion::default(), MinTlsVersion::Tls13);
    }

    #[test]
    fn custom_acme_ca_preserves_url() {
        let url = "https://internal-ca.corp.example/acme";
        let ca = AcmeCa::Custom(url.to_string());
        assert_eq!(ca.directory_url(), url);
    }
}
