use std::path::Path;
use std::sync::Arc;

use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier};
use rustls::pki_types::pem::PemObject;
use rustls::pki_types::{CertificateDer, ServerName, UnixTime};
use rustls::{DigitallySignedStruct, Error as TlsError, RootCertStore, SignatureScheme};
use sha2::{Digest, Sha256};

use base64::Engine;

use crate::error::{BzrError, Result};
use crate::tls::fingerprint::{compute_fingerprint, parse_pin};

/// A rustls `ServerCertVerifier` that validates the leaf certificate's
/// SHA-256 fingerprint against a pinned value, bypassing CA chain
/// verification entirely.
#[derive(Debug)]
pub(crate) struct PinnedCertVerifier {
    /// The expected SHA-256 hash of the leaf certificate DER bytes.
    pin_hash: [u8; 32],
    /// The full `sha256//<base64>` pin string, kept for error messages.
    pin_str: String,
    /// Optional expected issuer DN string for change detection
    /// (legacy fallback for pins created before DER comparison).
    pin_issuer: Option<String>,
    /// Raw DER bytes of the expected issuer SEQUENCE for tamper-proof
    /// comparison. Takes precedence over `pin_issuer` string.
    pin_issuer_der: Option<Vec<u8>>,
    /// The server name this verifier was created for (for errors).
    server_name: String,
    /// Delegate for cryptographic signature verification.
    sig_verifier: Arc<dyn ServerCertVerifier>,
}

impl PinnedCertVerifier {
    /// Build a pinned certificate verifier.
    ///
    /// `pin_sha256` must be in `sha256//<base64>` format.
    /// `pin_issuer_der_b64` is the base64-encoded raw DER of the issuer
    /// SEQUENCE, used for tamper-proof issuer comparison. Falls back to
    /// `pin_issuer` string comparison when `None` (backward compat).
    pub(crate) fn new(
        pin_sha256: &str,
        pin_issuer: Option<String>,
        pin_issuer_der_b64: Option<&str>,
        server_name: &str,
    ) -> Result<Self> {
        let pin_hash = parse_pin(pin_sha256)?;
        let provider = super::default_provider();

        let pin_issuer_der = pin_issuer_der_b64
            .map(|b64| {
                base64::engine::general_purpose::STANDARD
                    .decode(b64)
                    .map_err(|e| {
                        BzrError::config(format!("invalid base64 in tls_pin_issuer_der: {e}"))
                    })
            })
            .transpose()?;

        let mut root_store = RootCertStore::empty();
        root_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());

        let sig_verifier = rustls::client::WebPkiServerVerifier::builder_with_provider(
            Arc::new(root_store),
            provider,
        )
        .build()
        .map_err(|e| BzrError::config(format!("failed to build signature verifier: {e}")))?;

        Ok(Self {
            pin_hash,
            pin_str: pin_sha256.to_owned(),
            pin_issuer,
            pin_issuer_der,
            server_name: server_name.to_owned(),
            sig_verifier,
        })
    }

    /// Check whether the leaf certificate's issuer matches the pinned issuer.
    /// Returns `Err(TlsError::General(..))` with an `ISSUER_CHANGED` message
    /// when the issuer differs; `Ok(Some(actual_issuer))` when the legacy
    /// string-pin branch ran (so the caller can reuse the computed string in
    /// a `PIN_MISMATCH` error message); `Ok(None)` when no issuer pin is
    /// configured or the DER-preferred branch ran.
    ///
    /// Prefers raw DER comparison (tamper-proof). Falls back to string
    /// comparison for legacy pins created before DER storage was added.
    fn check_issuer_change(
        &self,
        leaf_der: &[u8],
    ) -> std::result::Result<Option<String>, TlsError> {
        if let Some(expected_der) = &self.pin_issuer_der {
            if let Some(actual_der) = extract_issuer_der(leaf_der) {
                if *expected_der != actual_der {
                    return Err(TlsError::General(format!(
                        "ISSUER_CHANGED for {}: issuer DER mismatch \
                         (expected {} bytes, got {} bytes)",
                        self.server_name,
                        expected_der.len(),
                        actual_der.len()
                    )));
                }
            }
            return Ok(None);
        }
        if let Some(expected_issuer) = &self.pin_issuer {
            let actual_issuer = extract_issuer_dn(leaf_der);
            if actual_issuer != *expected_issuer {
                return Err(TlsError::General(format!(
                    "ISSUER_CHANGED for {}: expected \"{}\", \
                     got \"{}\"",
                    self.server_name, expected_issuer, actual_issuer
                )));
            }
            return Ok(Some(actual_issuer));
        }
        Ok(None)
    }
}

impl ServerCertVerifier for PinnedCertVerifier {
    fn verify_server_cert(
        &self,
        end_entity: &CertificateDer<'_>,
        _intermediates: &[CertificateDer<'_>],
        _server_name: &ServerName<'_>,
        _ocsp_response: &[u8],
        _now: UnixTime,
    ) -> std::result::Result<ServerCertVerified, TlsError> {
        let actual_hash: [u8; 32] = Sha256::digest(end_entity.as_ref()).into();

        if actual_hash == self.pin_hash {
            return Ok(ServerCertVerified::assertion());
        }

        let cached_issuer = self.check_issuer_change(end_entity.as_ref())?;

        let actual_fp = compute_fingerprint(end_entity.as_ref());
        let actual_issuer = cached_issuer.unwrap_or_else(|| extract_issuer_dn(end_entity.as_ref()));
        Err(TlsError::General(format!(
            "PIN_MISMATCH for {}: expected {}, got {}, issuer {}",
            self.server_name, self.pin_str, actual_fp, actual_issuer
        )))
    }

    fn verify_tls12_signature(
        &self,
        message: &[u8],
        cert: &CertificateDer<'_>,
        dss: &DigitallySignedStruct,
    ) -> std::result::Result<HandshakeSignatureValid, TlsError> {
        self.sig_verifier.verify_tls12_signature(message, cert, dss)
    }

    fn verify_tls13_signature(
        &self,
        message: &[u8],
        cert: &CertificateDer<'_>,
        dss: &DigitallySignedStruct,
    ) -> std::result::Result<HandshakeSignatureValid, TlsError> {
        self.sig_verifier.verify_tls13_signature(message, cert, dss)
    }

    fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
        self.sig_verifier.supported_verify_schemes()
    }
}

/// Build a `rustls::ClientConfig` that trusts system roots plus any
/// additional CA certificates from a PEM file on disk.
pub(crate) fn build_ca_cert_config(ca_pem_path: &Path) -> Result<rustls::ClientConfig> {
    let pem_data = std::fs::read(ca_pem_path).map_err(|e| {
        BzrError::config(format!(
            "failed to read CA certificate file {}: {e}",
            ca_pem_path.display()
        ))
    })?;

    let mut root_store = RootCertStore::empty();

    // Add system roots.
    let native_certs = rustls_native_certs::load_native_certs();
    for cert in native_certs.certs {
        let _ = root_store.add(cert);
    }

    // Parse and add custom CA certs from the PEM file.
    let custom_certs: Vec<CertificateDer<'static>> = CertificateDer::pem_slice_iter(&pem_data)
        .collect::<std::result::Result<Vec<_>, _>>()
        .map_err(|e| {
            BzrError::config(format!(
                "failed to parse PEM certificates from {}: {e}",
                ca_pem_path.display()
            ))
        })?;

    if custom_certs.is_empty() {
        return Err(BzrError::config(format!(
            "no valid PEM certificates found in {}",
            ca_pem_path.display()
        )));
    }

    for cert in custom_certs {
        root_store.add(cert).map_err(|e| {
            BzrError::config(format!(
                "failed to add CA certificate from {}: {e}",
                ca_pem_path.display()
            ))
        })?;
    }

    let config = super::base_tls_builder("protocol versions")?
        .with_root_certificates(root_store)
        .with_no_client_auth();

    Ok(config)
}

/// Build a `rustls::ClientConfig` that uses a `PinnedCertVerifier`
/// for certificate pinning instead of CA chain validation.
pub(crate) fn build_pinned_config(
    pin_sha256: &str,
    pin_issuer: Option<String>,
    pin_issuer_der: Option<&str>,
    server_name: &str,
) -> Result<rustls::ClientConfig> {
    let verifier = PinnedCertVerifier::new(pin_sha256, pin_issuer, pin_issuer_der, server_name)?;

    let config = super::base_tls_builder("protocol versions")?
        .dangerous()
        .with_custom_certificate_verifier(Arc::new(verifier))
        .with_no_client_auth();

    Ok(config)
}

/// Navigate to the start of the issuer field within a DER-encoded
/// X.509 certificate, returning the remaining bytes starting at the
/// issuer SEQUENCE. Shared by both `extract_issuer_der` and
/// `extract_issuer_dn`.
fn navigate_to_issuer(cert_der: &[u8]) -> Option<&[u8]> {
    let (_, content) = parse_der_sequence(cert_der)?;
    let (_, tbs) = parse_der_sequence(content)?;
    let mut pos = tbs;
    // Skip optional version [0] EXPLICIT
    if pos.first()? & 0xe0 == 0xa0 {
        let (rest, _) = skip_der_element(pos)?;
        pos = rest;
    }
    // Skip serialNumber INTEGER
    let (rest, _) = skip_der_element(pos)?;
    pos = rest;
    // Skip signature AlgorithmIdentifier SEQUENCE
    let (rest, _) = skip_der_element(pos)?;
    Some(rest)
}

/// Extract the raw DER bytes of the issuer SEQUENCE (tag + length +
/// content) from a DER-encoded X.509 certificate.
pub(crate) fn extract_issuer_der(cert_der: &[u8]) -> Option<Vec<u8>> {
    let pos = navigate_to_issuer(cert_der)?;
    let (rest_after_issuer, _) = skip_der_element(pos)?;
    let issuer_len = pos.len() - rest_after_issuer.len();
    Some(pos[..issuer_len].to_vec())
}

/// Best-effort extraction of issuer information from DER-encoded
/// certificate bytes. Returns a fallback string if parsing fails.
pub(crate) fn extract_issuer_dn(der: &[u8]) -> String {
    // X.509 DER structure (simplified):
    // SEQUENCE {
    //   SEQUENCE {                    -- TBSCertificate
    //     [0] EXPLICIT version       -- optional
    //     INTEGER serialNumber
    //     SEQUENCE signature
    //     SEQUENCE issuer            -- what we want
    //     ...
    //   }
    //   ...
    // }
    //
    // This is a best-effort parser that walks the outer SEQUENCE,
    // the TBSCertificate SEQUENCE, skips version/serial/signature,
    // and returns the raw bytes of the issuer field as a hex string.
    // A proper ASN.1 parser will replace this later.
    parse_issuer_from_tbs(der).unwrap_or_else(|| format!("<raw DER, {} bytes>", der.len()))
}

/// Try to extract a human-readable issuer string from DER bytes.
/// Returns `None` if parsing fails at any point.
fn parse_issuer_from_tbs(der: &[u8]) -> Option<String> {
    let pos = navigate_to_issuer(der)?;
    let (_, issuer_bytes) = parse_der_sequence(pos)?;

    // Walk the RDN SEQUENCEs and extract OID=value pairs
    extract_rdns(issuer_bytes)
}

/// Parse a DER SEQUENCE tag+length, returning (rest after, content).
fn parse_der_sequence(data: &[u8]) -> Option<(&[u8], &[u8])> {
    if data.first()? != &0x30 {
        return None;
    }
    let (rest, content_len) = parse_der_length(&data[1..])?;
    if rest.len() < content_len {
        return None;
    }
    Some((&rest[content_len..], &rest[..content_len]))
}

/// Skip one DER element (tag + length + value), returning the
/// remainder of the slice.
fn skip_der_element(data: &[u8]) -> Option<(&[u8], &[u8])> {
    if data.is_empty() {
        return None;
    }
    let (rest, content_len) = parse_der_length(&data[1..])?;
    if rest.len() < content_len {
        return None;
    }
    Some((&rest[content_len..], &rest[..content_len]))
}

/// Parse a DER length encoding, returning (rest, length value).
fn parse_der_length(data: &[u8]) -> Option<(&[u8], usize)> {
    let first = *data.first()?;
    if first < 0x80 {
        Some((&data[1..], first as usize))
    } else {
        let num_bytes = (first & 0x7f) as usize;
        if num_bytes == 0 || num_bytes > 4 || data.len() < 1 + num_bytes {
            return None;
        }
        let mut len: usize = 0;
        for &b in &data[1..=num_bytes] {
            len = len.checked_shl(8)?.checked_add(b as usize)?;
        }
        Some((&data[1 + num_bytes..], len))
    }
}

/// Walk RDN SET/SEQUENCE structures and produce "CN=foo, O=bar" style
/// output. Falls back to hex if UTF-8 decoding fails.
fn extract_rdns(mut data: &[u8]) -> Option<String> {
    let mut parts = Vec::new();

    while !data.is_empty() {
        // Each RDN is a SET
        let set_tag = *data.first()?;
        if set_tag != 0x31 {
            break;
        }
        let (rest, set_content) = skip_der_element(data)?;
        data = rest;

        // Inside the SET is a SEQUENCE of OID + value
        if let Some((_, seq_content)) = parse_der_sequence(set_content) {
            if let Some(part) = parse_attribute_type_and_value(seq_content) {
                parts.push(part);
            }
        }
    }

    if parts.is_empty() {
        None
    } else {
        Some(parts.join(", "))
    }
}

/// Parse an `AttributeTypeAndValue` (OID + string value).
fn parse_attribute_type_and_value(data: &[u8]) -> Option<String> {
    // OID tag = 0x06
    if data.first()? != &0x06 {
        return None;
    }
    let (rest, oid_bytes) = skip_der_element(data)?;
    let oid_name = oid_short_name(oid_bytes);

    // Value is a string type (UTF8String 0x0C, PrintableString 0x13,
    // IA5String 0x16, etc.)
    let (_, value_bytes) = skip_der_element(rest)?;
    let value =
        String::from_utf8(value_bytes.to_vec()).unwrap_or_else(|_| hex::encode(value_bytes));

    Some(format!("{oid_name}={value}"))
}

/// Map common X.500 OID byte sequences to short names.
fn oid_short_name(oid: &[u8]) -> &'static str {
    match oid {
        // 2.5.4.3 — CN
        [0x55, 0x04, 0x03] => "CN",
        // 2.5.4.6 — C
        [0x55, 0x04, 0x06] => "C",
        // 2.5.4.7 — L
        [0x55, 0x04, 0x07] => "L",
        // 2.5.4.8 — ST
        [0x55, 0x04, 0x08] => "ST",
        // 2.5.4.10 — O
        [0x55, 0x04, 0x0a] => "O",
        // 2.5.4.11 — OU
        [0x55, 0x04, 0x0b] => "OU",
        _ => "OID",
    }
}

/// Simple hex encoder to avoid adding a dependency just for error
/// messages in the DER parser fallback path.
mod hex {
    use std::fmt::Write as _;

    pub(super) fn encode(data: &[u8]) -> String {
        let mut s = String::with_capacity(data.len() * 2);
        for b in data {
            let _ = write!(s, "{b:02x}");
        }
        s
    }
}

#[cfg(test)]
#[path = "verifier_tests.rs"]
mod tests;
