use crate::RamaTlsRustlsCrateMarker;
use rama_core::conversion::{RamaFrom, RamaTryFrom};
use rama_core::error::BoxErrorExt as _;
use rama_core::error::{BoxError, ErrorContext, ErrorExt};
use rama_net::address::{Domain, Host};
use rama_tls::{
    ApplicationProtocol, CipherSuite, ProtocolVersion, SignatureScheme,
    client::{ClientHello, ClientHelloExtension},
};
use std::net::IpAddr;

macro_rules! enum_from_rustls {
    ($t:ty => $($name:ident),+$(,)?) => {
        $(
            impl RamaFrom<rustls::$name, RamaTlsRustlsCrateMarker> for rama_tls::$name {
                fn rama_from(value: ::rustls::$name) -> Self {
                    let n: $t = value.into();
                    n.into()
                }
            }

            impl RamaFrom<rama_tls::$name, RamaTlsRustlsCrateMarker> for rustls::$name {
                fn rama_from(value: rama_tls::$name) -> Self {
                    let n: $t = value.into();
                    n.into()
                }
            }
        )+
    };
}

enum_from_rustls!(u16 => ProtocolVersion, CipherSuite, SignatureScheme);

impl RamaTryFrom<ProtocolVersion, RamaTlsRustlsCrateMarker> for &rustls::SupportedProtocolVersion {
    type Error = ProtocolVersion;

    fn rama_try_from(value: ProtocolVersion) -> Result<Self, Self::Error> {
        match value {
            ProtocolVersion::TLSv1_2 => Ok(&rustls::version::TLS12),
            ProtocolVersion::TLSv1_3 => Ok(&rustls::version::TLS13),
            other => Err(other),
        }
    }
}

impl<'a> RamaTryFrom<rustls::pki_types::ServerName<'a>, RamaTlsRustlsCrateMarker> for Host {
    type Error = BoxError;

    fn rama_try_from(value: rustls::pki_types::ServerName<'a>) -> Result<Self, Self::Error> {
        match value {
            rustls::pki_types::ServerName::DnsName(name) => {
                Ok(Domain::try_from(name.as_ref().to_owned())?.into())
            }
            rustls::pki_types::ServerName::IpAddress(ip) => Ok(Self::from(IpAddr::from(ip))),
            _ => Err(
                BoxError::from_static_str("unrecognised rustls (PKI) server name")
                    .with_context_debug_field("server_name", || value.to_owned()),
            ),
        }
    }
}
impl RamaTryFrom<Host, RamaTlsRustlsCrateMarker> for rustls::pki_types::ServerName<'_> {
    type Error = BoxError;

    fn rama_try_from(value: Host) -> Result<Self, Self::Error> {
        // Try IP first (cheaper — no allocation, no IDN). `Uninterpreted`
        // bridges via the typed accessors; non-promotable inputs
        // (sub-delim reg-name, IPvFuture) error — rustls's ServerName
        // grammar doesn't model them.
        if let Ok(ip) = value.try_as_ip() {
            return Ok(rustls::pki_types::ServerName::IpAddress(ip.into()));
        }
        let domain = value
            .try_into_domain()
            .context("host is not a domain or IP for rustls ServerName")?;
        Ok(rustls::pki_types::ServerName::DnsName(
            rustls::pki_types::DnsName::try_from(domain.as_str().to_owned())
                .context("convert domain to rustls (PKI) ServerName")?,
        ))
    }
}

impl<'a> RamaTryFrom<&rustls::pki_types::ServerName<'a>, RamaTlsRustlsCrateMarker> for Host {
    type Error = BoxError;

    fn rama_try_from(value: &rustls::pki_types::ServerName<'a>) -> Result<Self, Self::Error> {
        match value {
            rustls::pki_types::ServerName::DnsName(name) => {
                Ok(Domain::try_from(name.as_ref().to_owned())?.into())
            }
            rustls::pki_types::ServerName::IpAddress(ip) => Ok(Self::from(IpAddr::from(*ip))),
            _ => Err(
                BoxError::from_static_str("urecognised rustls (PKI) server name")
                    .with_context_debug_field("value", || value.to_owned()),
            ),
        }
    }
}

impl<'a> RamaTryFrom<&'a Host, RamaTlsRustlsCrateMarker> for rustls::pki_types::ServerName<'a> {
    type Error = BoxError;

    fn rama_try_from(value: &'a Host) -> Result<Self, Self::Error> {
        // Try IP first; fall through to Domain. `Uninterpreted` bridges
        // via the typed accessors.
        if let Ok(ip) = value.try_as_ip() {
            return Ok(rustls::pki_types::ServerName::IpAddress(ip.into()));
        }
        let domain = value
            .try_as_domain()
            .context("host is not a domain or IP for rustls ServerName")?;
        // `Cow::Borrowed` (Name) → `DnsName::try_from(&str)` zero-copy.
        // `Cow::Owned` (Uninterpreted bridge) → pass through the same
        // owned-string path. `Cow::as_ref().as_str()` produces the
        // right `&str` for either branch.
        Ok(rustls::pki_types::ServerName::DnsName(
            rustls::pki_types::DnsName::try_from(domain.as_str().to_owned())
                .context("convert domain to rustls (PKI) ServerName")?,
        ))
    }
}

impl<'a> RamaFrom<rustls::server::ClientHello<'a>, RamaTlsRustlsCrateMarker> for ClientHello {
    fn rama_from(value: rustls::server::ClientHello<'a>) -> Self {
        let cipher_suites = value
            .cipher_suites()
            .iter()
            .map(|cs| CipherSuite::rama_from(*cs))
            .collect();

        let mut extensions = Vec::with_capacity(3);

        extensions.push(ClientHelloExtension::SignatureAlgorithms(
            value
                .signature_schemes()
                .iter()
                .map(|sc| SignatureScheme::rama_from(*sc))
                .collect(),
        ));

        if let Some(domain) = value.server_name().and_then(|d| d.parse().ok()) {
            extensions.push(ClientHelloExtension::ServerName(Some(domain)));
        }

        if let Some(alpn) = value.alpn() {
            extensions.push(ClientHelloExtension::ApplicationLayerProtocolNegotiation(
                alpn.map(ApplicationProtocol::from).collect(),
            ));
        }

        Self::new(
            // TODO: support if rustls can handle this
            ProtocolVersion::Unknown(0),
            cipher_suites,
            vec![],
            extensions,
        )
    }
}

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

    #[test]
    fn test_rustls_to_common_to_rustls() {
        let p = rustls::ProtocolVersion::TLSv1_3;
        let p = ProtocolVersion::rama_from(p);
        assert_eq!(p, ProtocolVersion::TLSv1_3);
        let p = rustls::ProtocolVersion::rama_from(p);
        assert_eq!(p, rustls::ProtocolVersion::TLSv1_3);
    }

    // ---- Uninterpreted host promotion fallback (audit M3 / C13) ----------
    //
    // Both the owned and borrowed `Host → ServerName` conversions retry
    // `Domain::try_from` on `Uninterpreted` hosts so a pct-encoded or
    // IDN reg-name reaches the TLS layer as a proper `DnsName` rather
    // than tripping the AddressTypeNotSupported / OpaqueError path.

    #[test]
    fn owned_host_uninterpreted_recovers_to_dns_name() {
        let host = Host::try_from("exa%6Dple.com").unwrap();
        assert!(matches!(host, Host::Uninterpreted(_)));
        let sn = rustls::pki_types::ServerName::rama_try_from(host).unwrap();
        match sn {
            rustls::pki_types::ServerName::DnsName(dns) => {
                assert_eq!(dns.as_ref(), "example.com");
            }
            other => panic!("expected DnsName from pct-encoded Uninterpreted, got {other:?}"),
        }
    }

    #[test]
    fn borrowed_host_uninterpreted_recovers_to_dns_name() {
        let host = Host::try_from("exa%6Dple.com").unwrap();
        // Borrowed-input conversion (the audit added this recovery
        // branch — used to error unconditionally).
        let sn = rustls::pki_types::ServerName::rama_try_from(&host).unwrap();
        match sn {
            rustls::pki_types::ServerName::DnsName(dns) => {
                assert_eq!(dns.as_ref(), "example.com");
            }
            other => panic!("expected DnsName from pct-encoded Uninterpreted, got {other:?}"),
        }
    }

    #[test]
    fn host_uninterpreted_bracketed_ipvfuture_still_errors() {
        // No typed recovery is possible for IPvFuture — must surface
        // the conversion error rather than emitting a bogus DnsName.
        let host = Host::try_from("[v1.fe80::a]").unwrap();
        rustls::pki_types::ServerName::rama_try_from(host).unwrap_err();
    }
}
