use rpki::data_model::aspa::{AspaDecodeError, AspaEContent, AspaParseError, AspaProfileError};

fn len_bytes(len: usize) -> Vec<u8> {
    if len < 128 {
        vec![len as u8]
    } else {
        let mut tmp = Vec::new();
        let mut n = len;
        while n > 0 {
            tmp.push((n & 0xFF) as u8);
            n >>= 8;
        }
        tmp.reverse();
        let mut out = vec![0x80 | (tmp.len() as u8)];
        out.extend(tmp);
        out
    }
}

fn tlv(tag: u8, content: &[u8]) -> Vec<u8> {
    let mut out = vec![tag];
    out.extend(len_bytes(content.len()));
    out.extend_from_slice(content);
    out
}

fn der_integer_u64(v: u64) -> Vec<u8> {
    let mut bytes = Vec::new();
    let mut n = v;
    if n == 0 {
        bytes.push(0);
    } else {
        while n > 0 {
            bytes.push((n & 0xFF) as u8);
            n >>= 8;
        }
        bytes.reverse();
        if bytes[0] & 0x80 != 0 {
            bytes.insert(0, 0);
        }
    }
    tlv(0x02, &bytes)
}

fn der_sequence(children: Vec<Vec<u8>>) -> Vec<u8> {
    let mut content = Vec::new();
    for c in children {
        content.extend(c);
    }
    tlv(0x30, &content)
}

fn cs_explicit(tag_no: u8, inner_der: Vec<u8>) -> Vec<u8> {
    tlv(0xA0 | (tag_no & 0x1F), &inner_der)
}

fn aspa_attestation_explicit_version(version: u64, customer: u64, providers: Vec<u64>) -> Vec<u8> {
    let providers_der = der_sequence(providers.into_iter().map(der_integer_u64).collect());
    der_sequence(vec![
        cs_explicit(0, der_integer_u64(version)),
        der_integer_u64(customer),
        providers_der,
    ])
}

fn aspa_attestation_missing_version(customer: u64, providers: Vec<u64>) -> Vec<u8> {
    let providers_der = der_sequence(providers.into_iter().map(der_integer_u64).collect());
    der_sequence(vec![der_integer_u64(customer), providers_der])
}

#[test]
fn trailing_bytes_are_rejected_in_parse_step() {
    let der = aspa_attestation_explicit_version(1, 64496, vec![64497]);
    let mut bad = der.clone();
    bad.push(0);
    let err = AspaEContent::decode_der(&bad).unwrap_err();
    assert!(matches!(
        err,
        AspaDecodeError::Parse(AspaParseError::TrailingBytes(1))
    ));
}

#[test]
fn version_tag_must_be_context_specific_0() {
    // Build a 3-element SEQUENCE but make the first element an INTEGER, not [0] EXPLICIT.
    let providers_der = der_sequence(vec![der_integer_u64(64497)]);
    let der = der_sequence(vec![
        der_integer_u64(1),     // wrong tag/class
        der_integer_u64(64496), // customerASID
        providers_der,          // providers
    ]);
    let err = AspaEContent::decode_der(&der).unwrap_err();
    assert!(matches!(
        err,
        AspaDecodeError::Validate(AspaProfileError::VersionMustBeExplicitOne)
    ));
}

#[test]
fn version_explicit_tag_rejects_trailing_bytes_inside_inner_der() {
    let mut version_inner = der_integer_u64(1);
    version_inner.extend(tlv(0x05, &[])); // NULL after INTEGER

    let providers_der = der_sequence(vec![der_integer_u64(64497)]);
    let der = der_sequence(vec![
        cs_explicit(0, version_inner),
        der_integer_u64(64496),
        providers_der,
    ]);
    let err = AspaEContent::decode_der(&der).unwrap_err();
    assert!(matches!(
        err,
        AspaDecodeError::Validate(AspaProfileError::ProfileDecode(_))
    ));
}

#[test]
fn provider_asid_out_of_range_is_rejected() {
    let der = aspa_attestation_explicit_version(1, 64496, vec![(u32::MAX as u64) + 1]);
    let err = AspaEContent::decode_der(&der).unwrap_err();
    assert!(matches!(
        err,
        AspaDecodeError::Validate(AspaProfileError::ProviderAsIdOutOfRange(_))
    ));
}

#[test]
fn version_must_be_explicit_and_equal_to_one() {
    let der = aspa_attestation_missing_version(64496, vec![64497]);
    let err = AspaEContent::decode_der(&der).unwrap_err();
    assert!(matches!(
        err,
        AspaDecodeError::Validate(AspaProfileError::InvalidAttestationSequence)
    ));

    let der = aspa_attestation_explicit_version(0, 64496, vec![64497]);
    let err = AspaEContent::decode_der(&der).unwrap_err();
    assert!(matches!(
        err,
        AspaDecodeError::Validate(AspaProfileError::VersionMustBeExplicitOne)
    ));
}

#[test]
fn customer_asid_out_of_range_is_rejected() {
    let der = aspa_attestation_explicit_version(1, (u32::MAX as u64) + 1, vec![64497]);
    let err = AspaEContent::decode_der(&der).unwrap_err();
    assert!(matches!(
        err,
        AspaDecodeError::Validate(AspaProfileError::CustomerAsIdOutOfRange(_))
    ));
}

#[test]
fn providers_constraints_are_enforced() {
    // empty providers
    let der = aspa_attestation_explicit_version(1, 64496, vec![]);
    let err = AspaEContent::decode_der(&der).unwrap_err();
    assert!(matches!(
        err,
        AspaDecodeError::Validate(AspaProfileError::EmptyProviders)
    ));

    // not strictly increasing (duplicate)
    let der = aspa_attestation_explicit_version(1, 64496, vec![64497, 64497]);
    let err = AspaEContent::decode_der(&der).unwrap_err();
    assert!(matches!(
        err,
        AspaDecodeError::Validate(AspaProfileError::ProvidersNotStrictlyIncreasing)
    ));

    // not strictly increasing (descending)
    let der = aspa_attestation_explicit_version(1, 64496, vec![64500, 64497]);
    let err = AspaEContent::decode_der(&der).unwrap_err();
    assert!(matches!(
        err,
        AspaDecodeError::Validate(AspaProfileError::ProvidersNotStrictlyIncreasing)
    ));

    // contains customer
    let der = aspa_attestation_explicit_version(1, 64496, vec![64496, 64497]);
    let err = AspaEContent::decode_der(&der).unwrap_err();
    assert!(matches!(
        err,
        AspaDecodeError::Validate(AspaProfileError::ProvidersContainCustomer(64496))
    ));
}
