use crate::data_model::oid::{
    OID_EC_PUBLIC_KEY, OID_EXTENDED_KEY_USAGE_RAW, OID_KP_BGPSEC_ROUTER, OID_SECP256R1,
};
use crate::data_model::rc::{
    AsIdOrRange, AsIdentifierChoice, ResourceCertKind, ResourceCertificate,
    ResourceCertificateParseError, ResourceCertificateParsed, ResourceCertificateProfileError,
    ResourceCertificateRole,
};
use crate::validation::cert_path::{CertPathError, validate_ee_cert_path_with_predecoded_ee};
use x509_parser::extensions::ParsedExtension;
use x509_parser::prelude::{FromDer, X509Certificate};
use x509_parser::public_key::PublicKey;
use x509_parser::x509::SubjectPublicKeyInfo;

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BgpsecRouterCertificateParsed {
    pub rc_parsed: ResourceCertificateParsed,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BgpsecRouterCertificate {
    pub raw_der: Vec<u8>,
    pub resource_cert: ResourceCertificate,
    pub subject_key_identifier: Vec<u8>,
    pub spki_der: Vec<u8>,
    pub asns: Vec<u32>,
}

#[derive(Debug, thiserror::Error)]
pub enum BgpsecRouterCertificateParseError {
    #[error("resource certificate parse error: {0} (RFC 5280 §4.1; RFC 6487 §4; RFC 8209 §3.1)")]
    ResourceCertificate(#[from] ResourceCertificateParseError),

    #[error("X.509 parse error: {0} (RFC 5280 §4.1; RFC 8209 §3.1)")]
    X509(String),

    #[error("trailing bytes after router certificate DER: {0} bytes (DER; RFC 5280 §4.1)")]
    TrailingBytes(usize),

    #[error("router SubjectPublicKeyInfo parse error: {0} (RFC 5280 §4.1.2.7; RFC 8208 §3.1)")]
    SpkiParse(String),

    #[error("trailing bytes after router SubjectPublicKeyInfo DER: {0} bytes (DER; RFC 8208 §3.1)")]
    SpkiTrailingBytes(usize),
}

#[derive(Debug, thiserror::Error)]
pub enum BgpsecRouterCertificateProfileError {
    #[error("resource certificate profile error: {0} (RFC 6487 §4; RFC 8209 §3.1)")]
    ResourceCertificate(#[from] ResourceCertificateProfileError),

    #[error("BGPsec router certificate must be an EE certificate (RFC 8209 §3.1)")]
    NotEe,

    #[error(
        "BGPsec router certificate must contain SubjectKeyIdentifier (RFC 6487 §4.8.2; RFC 8209 §3.3)"
    )]
    MissingSki,

    #[error(
        "BGPsec router certificate must include ExtendedKeyUsage (RFC 8209 §3.1.3.2; RFC 8209 §3.3)"
    )]
    MissingExtendedKeyUsage,

    #[error(
        "BGPsec router certificate ExtendedKeyUsage must be non-critical (RFC 6487 §4.8.4; RFC 8209 §3.1.3.2)"
    )]
    ExtendedKeyUsageCriticality,

    #[error(
        "BGPsec router certificate ExtendedKeyUsage must contain id-kp-bgpsec-router ({OID_KP_BGPSEC_ROUTER}) (RFC 8209 §3.1.3.2; RFC 8209 §3.3)"
    )]
    MissingBgpsecRouterEku,

    #[error(
        "BGPsec router certificate MUST NOT include Subject Information Access (RFC 8209 §3.1.3.3; RFC 8209 §3.3)"
    )]
    SubjectInfoAccessPresent,

    #[error(
        "BGPsec router certificate MUST NOT include IP resources extension (RFC 8209 §3.1.3.4; RFC 8209 §3.3)"
    )]
    IpResourcesPresent,

    #[error(
        "BGPsec router certificate MUST include AS resources extension (RFC 8209 §3.1.3.5; RFC 8209 §3.3)"
    )]
    AsResourcesMissing,

    #[error(
        "BGPsec router certificate AS resources MUST include one or more ASNs (RFC 8209 §3.1.3.5)"
    )]
    AsResourcesAsnumMissing,

    #[error("BGPsec router certificate AS resources MUST NOT use inherit (RFC 8209 §3.1.3.5)")]
    AsResourcesInherit,

    #[error(
        "BGPsec router certificate AS resources MUST contain explicit ASNs, not ranges (RFC 8209 §3.1.3.5)"
    )]
    AsResourcesRangeNotAllowed,

    #[error(
        "BGPsec router certificate subjectPublicKeyInfo.algorithm must be id-ecPublicKey ({OID_EC_PUBLIC_KEY}) (RFC 8208 §3.1)"
    )]
    SpkiAlgorithmNotEcPublicKey,

    #[error(
        "BGPsec router certificate subjectPublicKeyInfo.parameters must be secp256r1 ({OID_SECP256R1}) (RFC 8208 §3.1)"
    )]
    SpkiWrongCurve,

    #[error(
        "BGPsec router certificate subjectPublicKeyInfo.parameters missing or invalid (RFC 8208 §3.1)"
    )]
    SpkiParametersMissingOrInvalid,

    #[error(
        "BGPsec router certificate subjectPublicKey MUST be uncompressed P-256 ECPoint (RFC 8208 §3.1)"
    )]
    SpkiEcPointNotUncompressedP256,
}

#[derive(Debug, thiserror::Error)]
pub enum BgpsecRouterCertificateDecodeError {
    #[error("{0}")]
    Parse(#[from] BgpsecRouterCertificateParseError),

    #[error("{0}")]
    Validate(#[from] BgpsecRouterCertificateProfileError),
}

#[derive(Debug, thiserror::Error)]
pub enum BgpsecRouterCertificatePathError {
    #[error("{0}")]
    Decode(#[from] BgpsecRouterCertificateDecodeError),

    #[error("{0}")]
    CertPath(#[from] CertPathError),
}

impl BgpsecRouterCertificate {
    pub fn parse_der(
        der: &[u8],
    ) -> Result<BgpsecRouterCertificateParsed, BgpsecRouterCertificateParseError> {
        let (rem, cert) = X509Certificate::from_der(der)
            .map_err(|e| BgpsecRouterCertificateParseError::X509(e.to_string()))?;
        if !rem.is_empty() {
            return Err(BgpsecRouterCertificateParseError::TrailingBytes(rem.len()));
        }
        let (spki_rem, _spki) =
            SubjectPublicKeyInfo::from_der(cert.tbs_certificate.subject_pki.raw)
                .map_err(|e| BgpsecRouterCertificateParseError::SpkiParse(e.to_string()))?;
        if !spki_rem.is_empty() {
            return Err(BgpsecRouterCertificateParseError::SpkiTrailingBytes(
                spki_rem.len(),
            ));
        }
        let rc_parsed = ResourceCertificate::parse_der(der)?;
        Ok(BgpsecRouterCertificateParsed { rc_parsed })
    }

    pub fn validate_profile(&self) -> Result<(), BgpsecRouterCertificateProfileError> {
        Ok(())
    }

    pub fn decode_der(der: &[u8]) -> Result<Self, BgpsecRouterCertificateDecodeError> {
        Ok(Self::parse_der(der)?.validate_profile()?)
    }

    pub fn from_der(der: &[u8]) -> Result<Self, BgpsecRouterCertificateDecodeError> {
        Self::decode_der(der)
    }

    pub fn validate_path_with_prevalidated_issuer(
        der: &[u8],
        issuer_ca: &ResourceCertificate,
        issuer_spki: &SubjectPublicKeyInfo<'_>,
        issuer_crl: &crate::data_model::crl::RpkixCrl,
        issuer_crl_revoked_serials: &std::collections::HashSet<Vec<u8>>,
        issuer_ca_rsync_uri: Option<&str>,
        issuer_crl_rsync_uri: Option<&str>,
        validation_time: time::OffsetDateTime,
    ) -> Result<Self, BgpsecRouterCertificatePathError> {
        let cert = Self::decode_der(der)?;
        validate_ee_cert_path_with_predecoded_ee(
            &cert.resource_cert,
            der,
            issuer_ca,
            issuer_spki,
            issuer_crl,
            issuer_crl_revoked_serials,
            issuer_ca_rsync_uri,
            issuer_crl_rsync_uri,
            validation_time,
        )?;
        Ok(cert)
    }
}

impl BgpsecRouterCertificateParsed {
    pub fn validate_profile(
        self,
    ) -> Result<BgpsecRouterCertificate, BgpsecRouterCertificateProfileError> {
        let rc = self.rc_parsed.validate_profile()?;
        rc.validate_rfc6487_profile(ResourceCertificateRole::RouterEe)?;
        if rc.kind != ResourceCertKind::Ee {
            return Err(BgpsecRouterCertificateProfileError::NotEe);
        }
        let ski = rc
            .tbs
            .extensions
            .subject_key_identifier
            .clone()
            .ok_or(BgpsecRouterCertificateProfileError::MissingSki)?;

        if rc.tbs.extensions.subject_info_access.is_some() {
            return Err(BgpsecRouterCertificateProfileError::SubjectInfoAccessPresent);
        }
        if rc.tbs.extensions.ip_resources.is_some() {
            return Err(BgpsecRouterCertificateProfileError::IpResourcesPresent);
        }
        let as_resources = rc
            .tbs
            .extensions
            .as_resources
            .as_ref()
            .ok_or(BgpsecRouterCertificateProfileError::AsResourcesMissing)?;
        let asns = extract_router_asns(as_resources)?;

        let (rem, cert) = X509Certificate::from_der(&rc.raw_der).map_err(|e| {
            BgpsecRouterCertificateProfileError::ResourceCertificate(
                ResourceCertificateProfileError::InvalidCertificatePolicy(e.to_string()),
            )
        })?;
        if !rem.is_empty() {
            return Err(BgpsecRouterCertificateProfileError::ResourceCertificate(
                ResourceCertificateProfileError::InvalidCertificatePolicy(format!(
                    "trailing bytes after router certificate DER: {}",
                    rem.len()
                )),
            ));
        }
        validate_router_eku(&cert)?;
        validate_router_spki(&rc.tbs.subject_public_key_info)?;

        Ok(BgpsecRouterCertificate {
            raw_der: rc.raw_der.clone(),
            resource_cert: rc.clone(),
            subject_key_identifier: ski,
            spki_der: rc.tbs.subject_public_key_info.clone(),
            asns,
        })
    }
}

fn extract_router_asns(
    as_resources: &crate::data_model::rc::AsResourceSet,
) -> Result<Vec<u32>, BgpsecRouterCertificateProfileError> {
    let asnum = as_resources
        .asnum
        .as_ref()
        .ok_or(BgpsecRouterCertificateProfileError::AsResourcesAsnumMissing)?;
    if matches!(asnum, AsIdentifierChoice::Inherit)
        || matches!(as_resources.rdi.as_ref(), Some(AsIdentifierChoice::Inherit))
    {
        return Err(BgpsecRouterCertificateProfileError::AsResourcesInherit);
    }
    let AsIdentifierChoice::AsIdsOrRanges(items) = asnum else {
        return Err(BgpsecRouterCertificateProfileError::AsResourcesInherit);
    };
    if items.is_empty() {
        return Err(BgpsecRouterCertificateProfileError::AsResourcesAsnumMissing);
    }
    let mut asns = Vec::with_capacity(items.len());
    for item in items {
        match item {
            AsIdOrRange::Id(v) => asns.push(*v),
            AsIdOrRange::Range { .. } => {
                return Err(BgpsecRouterCertificateProfileError::AsResourcesRangeNotAllowed);
            }
        }
    }
    asns.sort_unstable();
    asns.dedup();
    Ok(asns)
}

fn validate_router_eku(
    cert: &X509Certificate<'_>,
) -> Result<(), BgpsecRouterCertificateProfileError> {
    let mut matches = cert
        .tbs_certificate
        .extensions()
        .iter()
        .filter(|ext| ext.oid.as_bytes() == OID_EXTENDED_KEY_USAGE_RAW);
    let Some(ext) = matches.next() else {
        return Err(BgpsecRouterCertificateProfileError::MissingExtendedKeyUsage);
    };
    if matches.next().is_some() {
        return Err(BgpsecRouterCertificateProfileError::MissingExtendedKeyUsage);
    }
    if ext.critical {
        return Err(BgpsecRouterCertificateProfileError::ExtendedKeyUsageCriticality);
    }
    let ParsedExtension::ExtendedKeyUsage(eku) = ext.parsed_extension() else {
        return Err(BgpsecRouterCertificateProfileError::MissingExtendedKeyUsage);
    };
    let found = eku
        .other
        .iter()
        .any(|oid| oid.to_id_string() == OID_KP_BGPSEC_ROUTER);
    if !found {
        return Err(BgpsecRouterCertificateProfileError::MissingBgpsecRouterEku);
    }
    Ok(())
}

fn validate_router_spki(spki_der: &[u8]) -> Result<(), BgpsecRouterCertificateProfileError> {
    let (rem, spki) = SubjectPublicKeyInfo::from_der(spki_der)
        .map_err(|_| BgpsecRouterCertificateProfileError::SpkiParametersMissingOrInvalid)?;
    if !rem.is_empty() {
        return Err(BgpsecRouterCertificateProfileError::SpkiParametersMissingOrInvalid);
    }
    if spki.algorithm.algorithm.to_id_string() != OID_EC_PUBLIC_KEY {
        return Err(BgpsecRouterCertificateProfileError::SpkiAlgorithmNotEcPublicKey);
    }
    let Some(params) = spki.algorithm.parameters.as_ref() else {
        return Err(BgpsecRouterCertificateProfileError::SpkiParametersMissingOrInvalid);
    };
    if params.header.tag().0 != 0x06 {
        return Err(BgpsecRouterCertificateProfileError::SpkiParametersMissingOrInvalid);
    }
    let mut der = Vec::with_capacity(params.data.len() + 2);
    der.push(0x06);
    if params.data.len() >= 0x80 {
        return Err(BgpsecRouterCertificateProfileError::SpkiParametersMissingOrInvalid);
    }
    der.push(params.data.len() as u8);
    der.extend_from_slice(params.data);
    let (prem, oid) = der_parser::der::parse_der_oid(&der)
        .map_err(|_| BgpsecRouterCertificateProfileError::SpkiParametersMissingOrInvalid)?;
    if !prem.is_empty() {
        return Err(BgpsecRouterCertificateProfileError::SpkiParametersMissingOrInvalid);
    }
    let curve = oid
        .as_oid_val()
        .map_err(|_| BgpsecRouterCertificateProfileError::SpkiParametersMissingOrInvalid)?
        .to_string();
    if curve != OID_SECP256R1 {
        return Err(BgpsecRouterCertificateProfileError::SpkiWrongCurve);
    }
    let parsed = spki
        .parsed()
        .map_err(|_| BgpsecRouterCertificateProfileError::SpkiEcPointNotUncompressedP256)?;
    let PublicKey::EC(ec) = parsed else {
        return Err(BgpsecRouterCertificateProfileError::SpkiAlgorithmNotEcPublicKey);
    };
    if ec.data().len() != 65 || ec.data().first() != Some(&0x04) {
        return Err(BgpsecRouterCertificateProfileError::SpkiEcPointNotUncompressedP256);
    }
    Ok(())
}
