use rpki::data_model::oid::{OID_AD_CA_REPOSITORY, OID_AD_RPKI_MANIFEST, OID_AD_RPKI_NOTIFY};
use rpki::data_model::rc::{
    AccessDescription, ResourceCertKind, ResourceCertificate, SubjectInfoAccess,
    SubjectInfoAccessCa,
};
use rpki::validation::ca_instance::{CaInstanceUrisError, ca_instance_uris_from_ca_certificate};

fn apnic_child_ca_fixture_der() -> Vec<u8> {
    std::fs::read(
        "tests/fixtures/repository/rpki.apnic.net/repository/B527EF581D6611E2BB468F7C72FD1FF2/BfycW4hQb3wNP4YsiJW-1n6fjro.cer",
    )
    .expect("read apnic fixture ca")
}

fn set_sia_ca(cert: &mut ResourceCertificate, ads: Vec<AccessDescription>) {
    cert.tbs.extensions.subject_info_access = Some(SubjectInfoAccess::Ca(SubjectInfoAccessCa {
        access_descriptions: ads,
    }));
}

#[test]
fn ca_instance_uris_success_and_error_branches() {
    let mut cert = ResourceCertificate::decode_der(&apnic_child_ca_fixture_der())
        .expect("decode apnic fixture ca");

    // Success path.
    let uris = ca_instance_uris_from_ca_certificate(&cert).expect("uris");
    assert!(uris.rsync_base_uri.starts_with("rsync://"));
    assert!(uris.rsync_base_uri.ends_with('/'));
    assert!(uris.manifest_rsync_uri.starts_with("rsync://"));
    assert!(uris.manifest_rsync_uri.ends_with(".mft"));

    // NotCa.
    cert.kind = ResourceCertKind::Ee;
    let err = ca_instance_uris_from_ca_certificate(&cert).unwrap_err();
    assert!(matches!(err, CaInstanceUrisError::NotCa));
    cert.kind = ResourceCertKind::Ca;

    // MissingSia.
    cert.tbs.extensions.subject_info_access = None;
    let err = ca_instance_uris_from_ca_certificate(&cert).unwrap_err();
    assert!(matches!(err, CaInstanceUrisError::MissingSia));

    // MissingCaRepository / MissingRpkiManifest.
    set_sia_ca(
        &mut cert,
        vec![AccessDescription {
            access_method_oid: OID_AD_RPKI_MANIFEST.to_string(),
            access_location: "rsync://example.test/repo/x.mft".to_string(),
        }],
    );
    let err = ca_instance_uris_from_ca_certificate(&cert).unwrap_err();
    assert!(
        matches!(err, CaInstanceUrisError::MissingCaRepository),
        "{err}"
    );

    set_sia_ca(
        &mut cert,
        vec![AccessDescription {
            access_method_oid: OID_AD_CA_REPOSITORY.to_string(),
            access_location: "rsync://example.test/repo/".to_string(),
        }],
    );
    let err = ca_instance_uris_from_ca_certificate(&cert).unwrap_err();
    assert!(
        matches!(err, CaInstanceUrisError::MissingRpkiManifest),
        "{err}"
    );

    // Scheme validation branches.
    set_sia_ca(
        &mut cert,
        vec![AccessDescription {
            access_method_oid: OID_AD_CA_REPOSITORY.to_string(),
            access_location: "http://example.test/repo/".to_string(),
        }],
    );
    let err = ca_instance_uris_from_ca_certificate(&cert).unwrap_err();
    assert!(
        matches!(err, CaInstanceUrisError::CaRepositoryNotRsync(_)),
        "{err}"
    );

    set_sia_ca(
        &mut cert,
        vec![
            AccessDescription {
                access_method_oid: OID_AD_CA_REPOSITORY.to_string(),
                access_location: "rsync://example.test/repo/".to_string(),
            },
            AccessDescription {
                access_method_oid: OID_AD_RPKI_MANIFEST.to_string(),
                access_location: "http://example.test/repo/x.mft".to_string(),
            },
        ],
    );
    let err = ca_instance_uris_from_ca_certificate(&cert).unwrap_err();
    assert!(
        matches!(err, CaInstanceUrisError::RpkiManifestNotRsync(_)),
        "{err}"
    );

    set_sia_ca(
        &mut cert,
        vec![
            AccessDescription {
                access_method_oid: OID_AD_CA_REPOSITORY.to_string(),
                access_location: "rsync://example.test/repo/".to_string(),
            },
            AccessDescription {
                access_method_oid: OID_AD_RPKI_MANIFEST.to_string(),
                access_location: "rsync://example.test/repo/x.mft".to_string(),
            },
            AccessDescription {
                access_method_oid: OID_AD_RPKI_NOTIFY.to_string(),
                access_location: "rsync://example.test/repo/notification.xml".to_string(),
            },
        ],
    );
    let err = ca_instance_uris_from_ca_certificate(&cert).unwrap_err();
    assert!(
        matches!(err, CaInstanceUrisError::RpkiNotifyNotHttps(_)),
        "{err}"
    );

    // ManifestNotUnderPublicationPoint.
    set_sia_ca(
        &mut cert,
        vec![
            AccessDescription {
                access_method_oid: OID_AD_CA_REPOSITORY.to_string(),
                access_location: "rsync://example.test/repo/".to_string(),
            },
            AccessDescription {
                access_method_oid: OID_AD_RPKI_MANIFEST.to_string(),
                access_location: "rsync://other.test/repo/x.mft".to_string(),
            },
        ],
    );
    let err = ca_instance_uris_from_ca_certificate(&cert).unwrap_err();
    assert!(
        matches!(
            err,
            CaInstanceUrisError::ManifestNotUnderPublicationPoint { .. }
        ),
        "{err}"
    );
}
