use rpki::data_model::rc::{
    ResourceCertificate, ResourceCertificateError, ResourceCertificateParseError,
    ResourceCertificateProfileError,
};

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

fn decode_b64(b64: &str) -> Vec<u8> {
    use base64::Engine as _;
    use base64::engine::general_purpose::STANDARD;
    STANDARD.decode(b64).unwrap()
}

fn replace_first(haystack: &mut [u8], needle: &[u8], replacement: &[u8]) -> bool {
    if needle.len() != replacement.len() {
        return false;
    }
    for i in 0..=haystack.len().saturating_sub(needle.len()) {
        if &haystack[i..i + needle.len()] == needle {
            haystack[i..i + needle.len()].copy_from_slice(replacement);
            return true;
        }
    }
    false
}

fn replace_all(haystack: &mut [u8], needle: &[u8], replacement: &[u8]) -> usize {
    if needle.len() != replacement.len() {
        return 0;
    }
    let mut n = 0;
    let mut i = 0;
    while i + needle.len() <= haystack.len() {
        if &haystack[i..i + needle.len()] == needle {
            haystack[i..i + needle.len()].copy_from_slice(replacement);
            n += 1;
            i += needle.len();
        } else {
            i += 1;
        }
    }
    n
}

#[test]
fn trailing_bytes_after_cert_are_rejected() {
    let mut der = decode_b64(TEST_NO_SIA_CERT_DER_B64);
    der.push(0);
    let err = ResourceCertificate::from_der(&der).unwrap_err();
    assert!(matches!(
        err,
        ResourceCertificateError::Parse(ResourceCertificateParseError::TrailingBytes(1))
    ));
}

#[test]
fn signature_algorithm_mismatch_is_detected() {
    let mut der = decode_b64(TEST_NO_SIA_CERT_DER_B64);
    // DER encoding of sha256WithRSAEncryption OID:
    // 06 09 2A 86 48 86 F7 0D 01 01 0B
    let oid = [
        0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0B,
    ];
    let mut patched = oid;
    patched[10] = 0x01; // rsaEncryption, same length encoding
    assert!(replace_first(&mut der, &oid, &patched));
    let err = ResourceCertificate::from_der(&der).unwrap_err();
    assert!(matches!(
        err,
        ResourceCertificateError::Validate(
            ResourceCertificateProfileError::SignatureAlgorithmMismatch
        )
    ));
}

#[test]
fn unsupported_signature_algorithm_is_detected() {
    let mut der = decode_b64(TEST_NO_SIA_CERT_DER_B64);
    let oid = [
        0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0B,
    ];
    let mut patched = oid;
    patched[10] = 0x01;
    let n = replace_all(&mut der, &oid, &patched);
    assert!(
        n >= 2,
        "expected to patch at least inner+outer AlgorithmIdentifier"
    );
    let err = ResourceCertificate::from_der(&der).unwrap_err();
    assert!(matches!(
        err,
        ResourceCertificateError::Validate(
            ResourceCertificateProfileError::UnsupportedSignatureAlgorithm
        )
    ));
}

#[test]
fn invalid_signature_algorithm_parameters_are_detected() {
    let mut der = decode_b64(TEST_NO_SIA_CERT_DER_B64);
    // Replace NULL parameters (05 00) right after the sha256WithRSAEncryption OID with
    // an empty OCTET STRING (04 00) to keep the encoding length unchanged.
    let alg = [
        0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0B, 0x05, 0x00,
    ];
    let mut patched = alg;
    patched[11] = 0x04;
    let n = replace_all(&mut der, &alg, &patched);
    assert!(
        n >= 2,
        "expected to patch at least inner+outer AlgorithmIdentifier parameters"
    );
    let err = ResourceCertificate::from_der(&der).unwrap_err();
    assert!(matches!(
        err,
        ResourceCertificateError::Validate(
            ResourceCertificateProfileError::InvalidSignatureAlgorithmParameters
        )
    ));
}
