use der_parser::num_bigint::BigUint;
use rpki::data_model::common::X509NameDer;
use rpki::data_model::oid::OID_CP_IPADDR_ASNUMBER;
use rpki::data_model::rc::{
    Afi, AsIdOrRange, AsIdentifierChoice, AsResourceSet, IpAddressChoice, IpAddressFamily,
    IpAddressOrRange, IpPrefix, RcExtensions, ResourceCertKind, ResourceCertificate,
    RpkixTbsCertificate,
};
use rpki::data_model::ta::{TaCertificate, TaCertificateDecodeError, TaCertificateProfileError};
use time::OffsetDateTime;

fn dummy_rc_ca(ext: RcExtensions) -> ResourceCertificate {
    let t = OffsetDateTime::from_unix_timestamp(0).unwrap();
    ResourceCertificate {
        raw_der: Vec::new(),
        kind: ResourceCertKind::Ca,
        tbs: RpkixTbsCertificate {
            version: 2,
            serial_number: BigUint::from(1u32),
            signature_algorithm: "1.2.840.113549.1.1.11".into(),
            issuer_name: X509NameDer(b"CN=TA".to_vec()),
            subject_name: X509NameDer(b"CN=TA".to_vec()),
            validity_not_before: t,
            validity_not_after: t,
            subject_public_key_info: Vec::new(),
            extensions: ext,
        },
    }
}

#[test]
fn ta_certificate_from_der_parses_downloaded_fixtures() {
    let fixtures = [
        "tests/fixtures/ta/afrinic-ta.cer",
        "tests/fixtures/ta/apnic-ta.cer",
        "tests/fixtures/ta/arin-ta.cer",
        "tests/fixtures/ta/lacnic-ta.cer",
        "tests/fixtures/ta/ripe-ncc-ta.cer",
    ];
    for path in fixtures {
        let der = std::fs::read(path).expect("read TA fixture");
        let ta = TaCertificate::from_der(&der).expect("parse TA fixture");
        assert_eq!(ta.rc_ca.kind, ResourceCertKind::Ca);
        assert_eq!(
            ta.rc_ca.tbs.extensions.certificate_policies_oid.as_deref(),
            Some(OID_CP_IPADDR_ASNUMBER)
        );
        assert!(ta.rc_ca.tbs.extensions.subject_key_identifier.is_some());
    }
}

#[test]
fn ta_certificate_rejects_non_self_signed_ca() {
    // This is a CA cert fixture, but not self-signed (issuer != subject).
    let der = std::fs::read(
        "tests/fixtures/repository/rpki.apnic.net/repository/B527EF581D6611E2BB468F7C72FD1FF2/BfycW4hQb3wNP4YsiJW-1n6fjro.cer",
    )
    .expect("read CA cert fixture");
    assert!(matches!(
        TaCertificate::from_der(&der),
        Err(TaCertificateDecodeError::Validate(
            TaCertificateProfileError::NotSelfSignedIssuerSubject
        ))
    ));
}

#[test]
fn ta_constraints_require_policies_and_ski() {
    let rc = dummy_rc_ca(RcExtensions {
        basic_constraints_ca: true,
        basic_constraints: None,
        subject_key_identifier: None,
        authority_key_identifier: None,
        crl_distribution_points_uris: None,
        ca_issuers_uris: None,
        subject_info_access: None,
        certificate_policies_oid: None,
        certificate_policies: None,
        extension_oids: Vec::new(),
        ip_resources: Some(rpki::data_model::rc::IpResourceSet { families: vec![] }),
        as_resources: None,
    });
    assert!(matches!(
        TaCertificate::validate_rc_constraints(&rc),
        Err(TaCertificateProfileError::MissingOrInvalidCertificatePolicies)
    ));

    let rc = dummy_rc_ca(RcExtensions {
        certificate_policies_oid: Some(OID_CP_IPADDR_ASNUMBER.to_string()),
        ..rc.tbs.extensions.clone()
    });
    assert!(matches!(
        TaCertificate::validate_rc_constraints(&rc),
        Err(TaCertificateProfileError::MissingSubjectKeyIdentifier)
    ));
}

#[test]
fn ta_constraints_require_non_empty_resources_and_no_inherit() {
    // Missing both IP and AS resources.
    let rc = dummy_rc_ca(RcExtensions {
        basic_constraints_ca: true,
        basic_constraints: None,
        subject_key_identifier: Some(vec![1]),
        authority_key_identifier: None,
        crl_distribution_points_uris: None,
        ca_issuers_uris: None,
        subject_info_access: None,
        certificate_policies_oid: Some(OID_CP_IPADDR_ASNUMBER.to_string()),
        certificate_policies: None,
        extension_oids: Vec::new(),
        ip_resources: None,
        as_resources: None,
    });
    assert!(matches!(
        TaCertificate::validate_rc_constraints(&rc),
        Err(TaCertificateProfileError::ResourcesMissing)
    ));

    // IP resources present but empty => resources empty.
    let rc = dummy_rc_ca(RcExtensions {
        ip_resources: Some(rpki::data_model::rc::IpResourceSet { families: vec![] }),
        ..rc.tbs.extensions.clone()
    });
    assert!(matches!(
        TaCertificate::validate_rc_constraints(&rc),
        Err(TaCertificateProfileError::ResourcesEmpty)
    ));

    // IP resources inherit is rejected.
    let rc = dummy_rc_ca(RcExtensions {
        ip_resources: Some(rpki::data_model::rc::IpResourceSet {
            families: vec![IpAddressFamily {
                afi: Afi::Ipv4,
                choice: IpAddressChoice::Inherit,
            }],
        }),
        ..rc.tbs.extensions.clone()
    });
    assert!(matches!(
        TaCertificate::validate_rc_constraints(&rc),
        Err(TaCertificateProfileError::IpResourcesInherit)
    ));

    // AS resources inherit is rejected.
    let rc = dummy_rc_ca(RcExtensions {
        ip_resources: None,
        as_resources: Some(AsResourceSet {
            asnum: Some(AsIdentifierChoice::Inherit),
            rdi: None,
        }),
        ..rc.tbs.extensions.clone()
    });
    assert!(matches!(
        TaCertificate::validate_rc_constraints(&rc),
        Err(TaCertificateProfileError::AsResourcesInherit)
    ));

    // Valid non-empty explicit IP resources => OK.
    let rc = dummy_rc_ca(RcExtensions {
        ip_resources: Some(rpki::data_model::rc::IpResourceSet {
            families: vec![IpAddressFamily {
                afi: Afi::Ipv6,
                choice: IpAddressChoice::AddressesOrRanges(vec![IpAddressOrRange::Prefix(
                    IpPrefix {
                        afi: Afi::Ipv6,
                        prefix_len: 32,
                        addr: vec![0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                    },
                )]),
            }],
        }),
        as_resources: None,
        ..rc.tbs.extensions.clone()
    });
    TaCertificate::validate_rc_constraints(&rc).expect("valid explicit resources");

    // Valid non-empty explicit AS resources => OK.
    let rc = dummy_rc_ca(RcExtensions {
        ip_resources: None,
        as_resources: Some(AsResourceSet {
            asnum: Some(AsIdentifierChoice::AsIdsOrRanges(vec![AsIdOrRange::Id(
                64512,
            )])),
            rdi: None,
        }),
        ..rc.tbs.extensions.clone()
    });
    TaCertificate::validate_rc_constraints(&rc).expect("valid explicit AS resources");
}
