use der_parser::num_bigint::BigUint;
use time::OffsetDateTime;

use rpki::data_model::common::X509NameDer;
use rpki::data_model::rc::{
    Afi, AsResourceSet, IpAddressChoice, IpAddressFamily, IpAddressOrRange, IpPrefix,
    IpResourceSet, RcExtensions, ResourceCertKind, ResourceCertificate, RpkixTbsCertificate,
    SubjectInfoAccess,
};
use rpki::data_model::roa::{
    RoaAfi, RoaEContent, RoaIpAddress, RoaIpAddressFamily, RoaValidateError,
};

fn dummy_ee(
    ip_resources: Option<IpResourceSet>,
    as_resources: Option<AsResourceSet>,
) -> ResourceCertificate {
    ResourceCertificate {
        raw_der: vec![],
        tbs: RpkixTbsCertificate {
            version: 2,
            serial_number: BigUint::from(1u8),
            signature_algorithm: "1.2.840.113549.1.1.11".to_string(),
            issuer_name: X509NameDer(b"CN=issuer".to_vec()),
            subject_name: X509NameDer(b"CN=subject".to_vec()),
            validity_not_before: OffsetDateTime::UNIX_EPOCH,
            validity_not_after: OffsetDateTime::UNIX_EPOCH,
            subject_public_key_info: vec![],
            extensions: RcExtensions {
                basic_constraints_ca: false,
                basic_constraints: None,
                subject_key_identifier: Some(vec![0x01]),
                authority_key_identifier: None,
                crl_distribution_points_uris: None,
                ca_issuers_uris: None,
                subject_info_access: Some(SubjectInfoAccess::Ca(
                    rpki::data_model::rc::SubjectInfoAccessCa {
                        access_descriptions: vec![],
                    },
                )),
                certificate_policies_oid: None,
                certificate_policies: None,
                extension_oids: Vec::new(),
                ip_resources,
                as_resources,
            },
        },
        kind: ResourceCertKind::Ee,
    }
}

fn test_roa_single_v4_prefix() -> RoaEContent {
    RoaEContent {
        version: 0,
        as_id: 64496,
        ip_addr_blocks: vec![RoaIpAddressFamily {
            afi: RoaAfi::Ipv4,
            addresses: vec![RoaIpAddress {
                prefix: rpki::data_model::roa::IpPrefix {
                    afi: RoaAfi::Ipv4,
                    prefix_len: 8,
                    addr: [10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                },
                max_length: Some(24),
            }],
        }],
    }
}

#[test]
fn validate_accepts_when_prefix_is_covered() {
    let roa = test_roa_single_v4_prefix();
    let ee = dummy_ee(
        Some(IpResourceSet {
            families: vec![IpAddressFamily {
                afi: Afi::Ipv4,
                choice: IpAddressChoice::AddressesOrRanges(vec![IpAddressOrRange::Prefix(
                    IpPrefix {
                        afi: Afi::Ipv4,
                        prefix_len: 0,
                        addr: vec![0, 0, 0, 0],
                    },
                )]),
            }],
        }),
        None,
    );
    roa.validate_against_ee_cert(&ee)
        .expect("prefix should be covered by 0/0");
}

#[test]
fn validate_rejects_when_as_resources_present() {
    let roa = test_roa_single_v4_prefix();
    let ee = dummy_ee(
        Some(IpResourceSet { families: vec![] }),
        Some(AsResourceSet {
            asnum: None,
            rdi: None,
        }),
    );
    let err = roa.validate_against_ee_cert(&ee).unwrap_err();
    assert!(matches!(err, RoaValidateError::EeAsResourcesPresent));
}

#[test]
fn validate_rejects_when_ip_resources_missing() {
    let roa = test_roa_single_v4_prefix();
    let ee = dummy_ee(None, None);
    let err = roa.validate_against_ee_cert(&ee).unwrap_err();
    assert!(matches!(err, RoaValidateError::EeIpResourcesMissing));
}

#[test]
fn validate_rejects_when_ip_resources_inherit() {
    let roa = test_roa_single_v4_prefix();
    let ee = dummy_ee(
        Some(IpResourceSet {
            families: vec![IpAddressFamily {
                afi: Afi::Ipv4,
                choice: IpAddressChoice::Inherit,
            }],
        }),
        None,
    );
    let err = roa.validate_against_ee_cert(&ee).unwrap_err();
    assert!(matches!(err, RoaValidateError::EeIpResourcesInherit));
}

#[test]
fn validate_rejects_when_prefix_not_covered() {
    let roa = test_roa_single_v4_prefix();
    let ee = dummy_ee(
        Some(IpResourceSet {
            families: vec![IpAddressFamily {
                afi: Afi::Ipv4,
                choice: IpAddressChoice::AddressesOrRanges(vec![IpAddressOrRange::Prefix(
                    IpPrefix {
                        afi: Afi::Ipv4,
                        prefix_len: 24,
                        addr: vec![192, 0, 2, 0],
                    },
                )]),
            }],
        }),
        None,
    );
    let err = roa.validate_against_ee_cert(&ee).unwrap_err();
    assert!(matches!(
        err,
        RoaValidateError::PrefixNotInEeResources { .. }
    ));
}

#[test]
fn contains_prefix_handles_non_octet_boundary_prefix_len() {
    let roa = RoaEContent {
        version: 0,
        as_id: 64496,
        ip_addr_blocks: vec![RoaIpAddressFamily {
            afi: RoaAfi::Ipv4,
            addresses: vec![RoaIpAddress {
                prefix: rpki::data_model::roa::IpPrefix {
                    afi: RoaAfi::Ipv4,
                    prefix_len: 16,
                    addr: [
                        0b1010_0000,
                        0x12,
                        0,
                        0, // 160.18.0.0/16
                        0,
                        0,
                        0,
                        0,
                        0,
                        0,
                        0,
                        0,
                        0,
                        0,
                        0,
                        0,
                    ],
                },
                max_length: None,
            }],
        }],
    };

    let ee = dummy_ee(
        Some(IpResourceSet {
            families: vec![IpAddressFamily {
                afi: Afi::Ipv4,
                choice: IpAddressChoice::AddressesOrRanges(vec![IpAddressOrRange::Prefix(
                    IpPrefix {
                        afi: Afi::Ipv4,
                        prefix_len: 9,
                        addr: vec![0b1010_0000, 0, 0, 0], // 160.0.0.0/9
                    },
                )]),
            }],
        }),
        None,
    );

    roa.validate_against_ee_cert(&ee)
        .expect("160.18.0.0/16 should be covered by 160.0.0.0/9");
}
