use rpki::data_model::crl::{CrlDecodeError, CrlParseError, CrlVerifyError, RpkixCrl};
use x509_parser::prelude::FromDer;
use x509_parser::prelude::X509Certificate;

const TEST_NO_CRLSIGN_CERT_DER_B64: &str = "MIIDATCCAemgAwIBAgIUCyQLQJn92+gyAzvIz22q1F/97OMwDQYJKoZIhvcNAQELBQAwGjEYMBYGA1UEAwwPVGVzdCBObyBDUkxTaWduMB4XDTI2MDEyNzAzNTk1OVoXDTM2MDEyNTAzNTk1OVowGjEYMBYGA1UEAwwPVGVzdCBObyBDUkxTaWduMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr/aoMU8J6cddkM2r6F2snd1rCdQPepgo2T2lrqWFcxnQJdcxBL1OYg3wFi95TJmZSeIHIOGauDaJ2abmjgyOUHOC4U68x66JRg4hLkmLxo1cf3uYHWl9Obph6g2qPRvN80ORq70JPuL6mAfUkNiO9hnwK6oQiTzc/rjCQGIFH8kTESBMXLfNCyUpGi+MNztYH6Ha6bKAQuXgd29OFwIkOlGQnYgGC2qBMvnp86eITvV1gTiuI8Ho9m9nZHCmaD7TylvkMDq8Hk5nkIpRcG0uO60SkR2BiMOYe/TNn5dTmHd6bsdbU2GOvgnq1SnqGq3FOWhKIe3ycUJde0uNfZOqRwIDAQABoz8wPTAOBgNVHQ8BAf8EBAMCBaAwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUFjyzfJCDNhFfKxVr06kjUkE23dMwDQYJKoZIhvcNAQELBQADggEBAK98n2gVlwKA3Ob1YeAm9f+8hm7pbvrt0tA8GW180CILjf09k7fKgiRlxqGdZ9ySXjU52+zCqu3MpBXVbI87ZC+zA6uK05n4y1F0n85MJ9hGR2UEiPcqou85X73LvioynnSOy/OV1PjKJXReUsqF3GgDtgcMyFssPJ9s/5DWuUCScUJY6pu0kuIGOLQ/oXUw4TvxUeyz73gOTiAJshVTQoLpHUhj0595S7lArjwi7oLI1b8m8guTknvhk0Sc3tJZmUqOcIvYIs0guHpaeC+sMoF4K+6UTrxxOBdX+fUEWNpUyYXWHjdZq25PbJdHwA/VAW2zYVojaVREligf0Qfo6F4=";

fn test_no_crlsign_cert_der() -> Vec<u8> {
    use base64::{Engine as _, engine::general_purpose};
    general_purpose::STANDARD
        .decode(TEST_NO_CRLSIGN_CERT_DER_B64)
        .expect("decode base64 cert")
}

#[test]
fn verify_errors_are_reported() {
    let crl_der = std::fs::read(
        "tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.crl",
    )
    .expect("read CRL fixture");
    let issuer_cert_der = std::fs::read(
        "tests/fixtures/repository/rpki.apnic.net/repository/B527EF581D6611E2BB468F7C72FD1FF2/BfycW4hQb3wNP4YsiJW-1n6fjro.cer",
    )
    .expect("read issuer cert");

    let mut crl = RpkixCrl::decode_der(&crl_der).expect("decode");

    // IssuerSubjectMismatch.
    crl.issuer_dn = "CN=Not The Issuer".to_string();
    let err = crl
        .verify_signature_with_issuer_certificate_der(&issuer_cert_der)
        .unwrap_err();
    assert!(matches!(err, CrlVerifyError::IssuerSubjectMismatch { .. }));

    // AkiSkiMismatch (force issuer dn to match, then change AKI).
    let (_rem, issuer_cert) = X509Certificate::from_der(&issuer_cert_der).unwrap();
    crl.issuer_dn = issuer_cert.subject().to_string();
    crl.extensions.authority_key_identifier = vec![0u8; 20];
    let err = crl
        .verify_signature_with_issuer_certificate_der(&issuer_cert_der)
        .unwrap_err();
    assert!(matches!(err, CrlVerifyError::AkiSkiMismatch));

    // IssuerKeyUsageMissingCrlSign (use a cert with KeyUsage present but without CRLSign).
    let no_crlsign_der = test_no_crlsign_cert_der();
    let (_rem, no_crlsign_cert) = X509Certificate::from_der(&no_crlsign_der).unwrap();
    crl.issuer_dn = no_crlsign_cert.subject().to_string();
    let err = crl
        .verify_signature_with_issuer_certificate_der(&no_crlsign_der)
        .unwrap_err();
    assert!(matches!(err, CrlVerifyError::IssuerKeyUsageMissingCrlSign));

    // InvalidSignature: verify cryptographically with the wrong SPKI.
    let no_crlsign_spki_der = no_crlsign_cert.public_key().raw.to_vec();
    let err = crl
        .verify_signature_with_issuer_spki_der(&no_crlsign_spki_der)
        .unwrap_err();
    assert!(matches!(err, CrlVerifyError::InvalidSignature(_)));

    // IssuerCertificateParse / trailing bytes.
    let err = crl
        .verify_signature_with_issuer_certificate_der(b"not a cert")
        .unwrap_err();
    assert!(matches!(err, CrlVerifyError::IssuerCertificateParse(_)));

    let mut bad = issuer_cert_der.clone();
    bad.push(0);
    let err = crl
        .verify_signature_with_issuer_certificate_der(&bad)
        .unwrap_err();
    assert!(matches!(
        err,
        CrlVerifyError::IssuerCertificateTrailingBytes(1)
    ));
}

#[test]
fn verify_signature_with_spki_der_paths() {
    let crl_der = std::fs::read(
        "tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.crl",
    )
    .expect("read CRL fixture");
    let issuer_cert_der = std::fs::read(
        "tests/fixtures/repository/rpki.apnic.net/repository/B527EF581D6611E2BB468F7C72FD1FF2/BfycW4hQb3wNP4YsiJW-1n6fjro.cer",
    )
    .expect("read issuer cert");

    let crl = RpkixCrl::decode_der(&crl_der).expect("decode");
    let (_rem, issuer_cert) = X509Certificate::from_der(&issuer_cert_der).unwrap();
    let spki_der = issuer_cert.public_key().raw.to_vec();

    crl.verify_signature_with_issuer_spki_der(&spki_der)
        .expect("verify with spki der");

    let mut bad = spki_der;
    bad.push(0);
    let err = crl.verify_signature_with_issuer_spki_der(&bad).unwrap_err();
    assert!(matches!(err, CrlVerifyError::IssuerSpkiTrailingBytes(1)));
}

#[test]
fn decode_rejects_trailing_bytes() {
    let crl_der = std::fs::read(
        "tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.crl",
    )
    .expect("read CRL fixture");
    let mut bad = crl_der.clone();
    bad.push(0);
    let err = RpkixCrl::decode_der(&bad).unwrap_err();
    assert!(matches!(
        err,
        CrlDecodeError::Parse(CrlParseError::TrailingBytes(1))
    ));
}

#[test]
fn verify_rejects_crl_with_trailing_bytes_in_raw_der() {
    let crl_der = std::fs::read(
        "tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.crl",
    )
    .expect("read CRL fixture");
    let issuer_cert_der = std::fs::read(
        "tests/fixtures/repository/rpki.apnic.net/repository/B527EF581D6611E2BB468F7C72FD1FF2/BfycW4hQb3wNP4YsiJW-1n6fjro.cer",
    )
    .expect("read issuer cert");

    let mut crl = RpkixCrl::decode_der(&crl_der).expect("decode");
    crl.raw_der.push(0);
    let (_rem, issuer_cert) = X509Certificate::from_der(&issuer_cert_der).unwrap();
    let spki_der = issuer_cert.public_key().raw.to_vec();

    let err = crl
        .verify_signature_with_issuer_spki_der(&spki_der)
        .unwrap_err();
    assert!(matches!(err, CrlVerifyError::CrlTrailingBytes(1)));
}
