use webpki::{BorrowedCertRevocationList, CertRevocationList, DerTypeId, Error};

const REVOKED_SERIAL: &[u8] = &[0x03, 0xAE, 0x51, 0xDB, 0x51, 0x15, 0x5A, 0x3C];

const REVOKED_SERIAL_NEGATIVE: &[u8] = &[0xfd, 0x78, 0xa8, 0x4e];

// because cert::Cert::serial is the raw DER integer encoding, this includes a leading
// zero if one is required to make the twos complement representation positive.  in this case
// one is required.
const REVOKED_SERIAL_WITH_TOP_BIT_SET: &[u8] = &[0x00, 0x80, 0xfe, 0xed, 0xf0, 0x0d];
#[test]
fn parse_valid_crl() {
    // We should be able to parse a valid CRL without error, and find the revoked serial.
    let crl = include_bytes!("crls/crl.valid.der");
    let borrowed_crl =
        BorrowedCertRevocationList::from_der(&crl[..]).expect("failed to parse valid crl");

    #[cfg(feature = "alloc")]
    {
        let crl: CertRevocationList = borrowed_crl.to_owned().unwrap().into();
        assert!(crl.find_serial(REVOKED_SERIAL).unwrap().is_some());
    }

    let crl: CertRevocationList = borrowed_crl.into();
    assert!(crl.find_serial(REVOKED_SERIAL).unwrap().is_some());
}

#[test]
fn parse_empty_crl() {
    // We should be able to parse an empty CRL without error, and find no revoked certs.
    let crl = include_bytes!("crls/crl.empty.der");
    let borrowed_crl =
        BorrowedCertRevocationList::from_der(&crl[..]).expect("failed to parse empty crl");

    #[cfg(feature = "alloc")]
    {
        // We should also be able to create an owned empty CRL without error.
        let res = borrowed_crl.to_owned();
        assert!(res.is_ok());
    }

    assert!(borrowed_crl.into_iter().next().is_none());
}

#[test]
fn parse_mismatched_sigalg_crl() {
    // Parsing a CRL with a mismatched outer/inner signature algorithm should fail.
    let crl = include_bytes!("crls/crl.mismatched.sigalg.der");
    let res = BorrowedCertRevocationList::from_der(&crl[..]);
    assert!(matches!(res, Err(Error::SignatureAlgorithmMismatch)));
}

#[test]
fn parse_bad_this_update_crl() {
    // Parsing a CRL with an invalid this update time should error.
    let crl = include_bytes!("crls/crl.invalid.this.update.time.der");
    let res = BorrowedCertRevocationList::from_der(&crl[..]);
    assert!(matches!(res, Err(Error::BadDerTime)));
}

#[test]
fn parse_missing_next_update_crl() {
    // Parsing a CRL with a missing next update time should error.
    let crl = include_bytes!("crls/crl.missing.next.update.der");
    let res = BorrowedCertRevocationList::from_der(&crl[..]);
    assert!(matches!(res, Err(Error::TrailingData(DerTypeId::Time))));
}

#[test]
fn parse_wrong_version_crl() {
    // Parsing a CRL with an unsupported version should error.
    let crl = include_bytes!("crls/crl.wrong.version.der");
    let res = BorrowedCertRevocationList::from_der(&crl[..]);
    assert!(matches!(res, Err(Error::UnsupportedCrlVersion)));
}

#[test]
fn parse_missing_exts_crl() {
    // Parsing a CRL with no list extensions should error.
    let crl = include_bytes!("crls/crl.missing.exts.der");
    let res = BorrowedCertRevocationList::from_der(&crl[..]);
    assert!(matches!(res, Err(Error::MalformedExtensions)));
}

#[test]
fn parse_delta_crl() {
    // Parsing a CRL with an extension indicating its a delta CRL should error.
    let crl = include_bytes!("crls/crl.delta.der");
    let res = BorrowedCertRevocationList::from_der(&crl[..]);
    assert!(matches!(res, Err(Error::UnsupportedDeltaCrl)));
}

#[test]
fn parse_unknown_crit_ext_crl() {
    // Parsing a CRL with an unknown critical list extension should error.
    let crl = include_bytes!("crls/crl.unknown.crit.ext.der");
    let res = BorrowedCertRevocationList::from_der(&crl[..]);
    assert!(matches!(res, Err(Error::UnsupportedCriticalExtension)));
}

#[test]
fn parse_negative_crl_number_crl() {
    // Parsing a CRL with a negative CRL number should error.
    let crl = include_bytes!("crls/crl.negative.crl.number.der");
    let res = BorrowedCertRevocationList::from_der(&crl[..]);
    assert!(matches!(res, Err(Error::InvalidCrlNumber)));
}

#[test]
fn parse_too_long_crl_number_crl() {
    // Parsing a CRL with a CRL number > 20 octets should error.
    let crl = include_bytes!("crls/crl.too.long.crl.number.der");
    let res = BorrowedCertRevocationList::from_der(&crl[..]);
    assert!(matches!(res, Err(Error::InvalidCrlNumber)));
}

#[test]
fn parse_entry_negative_serial_crl() {
    let crl = include_bytes!("crls/crl.negative.serial.der");
    let borrowed_crl = BorrowedCertRevocationList::from_der(&crl[..]).unwrap();

    #[cfg(feature = "alloc")]
    {
        let crl: CertRevocationList = borrowed_crl.to_owned().unwrap().into();
        assert!(
            crl.find_serial(REVOKED_SERIAL)
                .expect("looking for REVOKED_SERIAL failed")
                .is_none()
        );
        assert!(
            crl.find_serial(REVOKED_SERIAL_NEGATIVE)
                .expect("looking for REVOKED_SERIAL_NEGATIVE failed")
                .is_some()
        );
    }

    let crl: CertRevocationList = borrowed_crl.into();
    assert!(
        crl.find_serial(REVOKED_SERIAL)
            .expect("looking for REVOKED_SERIAL failed")
            .is_none()
    );
    assert!(
        crl.find_serial(REVOKED_SERIAL_NEGATIVE)
            .expect("looking for REVOKED_SERIAL_NEGATIVE failed")
            .is_some()
    );
}

#[test]
fn parse_entry_topbit_serial_crl() {
    let crl = include_bytes!("crls/crl.topbit.serial.der");
    let borrowed_crl = BorrowedCertRevocationList::from_der(&crl[..]).unwrap();

    #[cfg(feature = "alloc")]
    {
        let crl: CertRevocationList = borrowed_crl.to_owned().unwrap().into();
        assert!(
            crl.find_serial(REVOKED_SERIAL_WITH_TOP_BIT_SET)
                .expect("failed to look for REVOKED_SERIAL_WITH_TOP_BIT_SET")
                .is_some()
        );
    }

    let crl: CertRevocationList = borrowed_crl.into();
    assert!(
        crl.find_serial(REVOKED_SERIAL_WITH_TOP_BIT_SET)
            .expect("failed to look for REVOKED_SERIAL_WITH_TOP_BIT_SET")
            .is_some()
    );
}

#[test]
fn parse_entry_without_exts_crl() {
    // Parsing a CRL that includes a revoked entry that has no extensions shouldn't error, and we
    // should find the expected revoked certificate.
    let crl = include_bytes!("crls/crl.no.entry.exts.der");
    let borrowed_crl =
        BorrowedCertRevocationList::from_der(&crl[..]).expect("unexpected error parsing crl");

    #[cfg(feature = "alloc")]
    {
        let crl: CertRevocationList = borrowed_crl.to_owned().unwrap().into();
        assert!(crl.find_serial(REVOKED_SERIAL).unwrap().is_some());
    }

    let crl: CertRevocationList = borrowed_crl.into();
    assert!(crl.find_serial(REVOKED_SERIAL).unwrap().is_some());
}

#[test]
fn parse_entry_with_empty_exts_seq() {
    // Parsing a CRL that has a revoked cert entry with an empty extensions sequence shouldn't error.
    let crl = include_bytes!("crls/crl.entry.empty.ext.seq.der");
    let res = BorrowedCertRevocationList::from_der(&crl[..]);
    assert!(res.is_ok());

    #[cfg(feature = "alloc")]
    {
        let res = res.unwrap().to_owned();
        assert!(res.is_ok());
    }
}

#[test]
fn parse_entry_unknown_crit_ext_crl() {
    // Parsing a CRL that includes a revoked entry that has an unknown critical extension shouldn't
    // error up-front because the problem is with a revoked cert entry.
    let crl = include_bytes!("crls/crl.entry.unknown.crit.ext.der");
    let borrowed_crl = BorrowedCertRevocationList::from_der(&crl[..]).unwrap();

    #[cfg(feature = "alloc")]
    {
        // Parsing the CRL as an owned CRL should error since it will process the revoked certs.
        let res = borrowed_crl.to_owned();
        assert!(matches!(res, Err(Error::UnsupportedCriticalExtension)));
    }

    // but should error when we try to find a revoked serial due to the entry with the unsupported
    // critical ext.
    let crl: CertRevocationList = borrowed_crl.into();
    let res = crl.find_serial(REVOKED_SERIAL);
    assert!(matches!(res, Err(Error::UnsupportedCriticalExtension)));
}

#[test]
fn parse_entry_invalid_reason_crl() {
    // Parsing a CRL that includes a revoked entry that has an unknown revocation reason shouldn't
    // error up-front since the problem is with a revoked entry.
    let crl = include_bytes!("crls/crl.entry.invalid.reason.der");
    let borrowed_crl = BorrowedCertRevocationList::from_der(&crl[..]).unwrap();

    #[cfg(feature = "alloc")]
    {
        // Parsing the CRL as an owned CRL should error since it will process the revoked certs.
        let res = borrowed_crl.to_owned();
        assert!(matches!(res, Err(Error::UnsupportedRevocationReason)));
    }

    // But searching for a serial should error due to the revoked cert with the unknown reason.
    let crl: CertRevocationList = borrowed_crl.into();
    let res = crl.find_serial(REVOKED_SERIAL);
    assert!(matches!(res, Err(Error::UnsupportedRevocationReason)));
}

#[test]
fn parse_entry_invalidity_date_crl() {
    // Parsing a CRL that includes a revoked entry that has an invalidity date ext shouldn't error
    // and we should find the expected revoked cert with an invalidity date.
    let crl = include_bytes!("crls/crl.entry.invalidity.date.der");
    let borrowed_crl =
        BorrowedCertRevocationList::from_der(&crl[..]).expect("unexpected err parsing CRL");

    #[cfg(feature = "alloc")]
    {
        let crl: CertRevocationList = borrowed_crl.to_owned().unwrap().into();
        assert!(
            crl.find_serial(REVOKED_SERIAL)
                .unwrap()
                .unwrap()
                .invalidity_date
                .is_some()
        );
    }

    let crl: CertRevocationList = borrowed_crl.into();
    assert!(
        crl.find_serial(REVOKED_SERIAL)
            .unwrap()
            .unwrap()
            .invalidity_date
            .is_some()
    );
}

#[test]
fn parse_entry_indirect_issuer_crl() {
    // Parsing a CRL that includes a revoked entry that has a issuer certificate extension
    // shouldn't error up-front - we expect the error to be surfaced when we iterate the revoked
    // certs.
    let crl = include_bytes!("crls/crl.entry.issuer.ext.der");
    let borrowed_crl = BorrowedCertRevocationList::from_der(&crl[..]).unwrap();

    #[cfg(feature = "alloc")]
    {
        // Building an owned CRL should error up front since it will process the revoked certs.
        let res = borrowed_crl.to_owned();
        assert!(matches!(res, Err(Error::UnsupportedIndirectCrl)));
    }

    let crl: CertRevocationList = borrowed_crl.into();
    let res = crl.find_serial(REVOKED_SERIAL);
    assert!(matches!(res, Err(Error::UnsupportedIndirectCrl)));
}
