use rpki::data_model::roa::{RoaDecodeError, RoaEContent, RoaParseError, RoaProfileError};

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_octet_string(bytes: &[u8]) -> Vec<u8> {
    tlv(0x04, bytes)
}

fn der_bit_string(unused: u8, bytes: &[u8]) -> Vec<u8> {
    let mut content = vec![unused];
    content.extend_from_slice(bytes);
    tlv(0x03, &content)
}

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 roa_ip_address(prefix_bs: Vec<u8>, max_len: Option<u64>) -> Vec<u8> {
    let mut fields = vec![prefix_bs];
    if let Some(m) = max_len {
        fields.push(der_integer_u64(m));
    }
    der_sequence(fields)
}

fn roa_ip_family(afi: [u8; 2], addresses: Vec<Vec<u8>>) -> Vec<u8> {
    der_sequence(vec![der_octet_string(&afi), der_sequence(addresses)])
}

fn roa_attestation(version: Option<u64>, as_id: u64, families: Vec<Vec<u8>>) -> Vec<u8> {
    let mut fields = Vec::new();
    if let Some(v) = version {
        fields.push(cs_explicit(0, der_integer_u64(v)));
    }
    fields.push(der_integer_u64(as_id));
    fields.push(der_sequence(families));
    der_sequence(fields)
}

#[test]
fn trailing_bytes_are_rejected_in_parse_step() {
    let der = roa_attestation(
        None,
        64496,
        vec![roa_ip_family(
            [0, 1],
            vec![roa_ip_address(der_bit_string(0, &[]), None)],
        )],
    );
    let mut bad = der.clone();
    bad.push(0);
    let err = RoaEContent::decode_der(&bad).unwrap_err();
    assert!(matches!(
        err,
        RoaDecodeError::Parse(RoaParseError::TrailingBytes(1))
    ));
}

#[test]
fn attestation_sequence_len_is_validated() {
    let der = der_sequence(vec![der_integer_u64(64496)]);
    let err = RoaEContent::decode_der(&der).unwrap_err();
    assert!(matches!(
        err,
        RoaDecodeError::Validate(RoaProfileError::InvalidAttestationSequenceLen(1))
    ));
}

#[test]
fn version_tag_must_be_context_specific_0() {
    let der = {
        let mut fields = Vec::new();
        fields.push(cs_explicit(1, der_integer_u64(0))); // wrong tag number [1]
        fields.push(der_integer_u64(64496));
        fields.push(der_sequence(vec![roa_ip_family(
            [0, 1],
            vec![roa_ip_address(der_bit_string(0, &[]), None)],
        )]));
        der_sequence(fields)
    };
    let err = RoaEContent::decode_der(&der).unwrap_err();
    assert!(matches!(
        err,
        RoaDecodeError::Validate(RoaProfileError::ProfileDecode(_))
    ));
}

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

    let der = {
        let mut fields = Vec::new();
        fields.push(cs_explicit(0, version_inner));
        fields.push(der_integer_u64(64496));
        fields.push(der_sequence(vec![roa_ip_family(
            [0, 1],
            vec![roa_ip_address(der_bit_string(0, &[]), None)],
        )]));
        der_sequence(fields)
    };
    let err = RoaEContent::decode_der(&der).unwrap_err();
    assert!(matches!(
        err,
        RoaDecodeError::Validate(RoaProfileError::ProfileDecode(_))
    ));
}

#[test]
fn version_zero_is_accepted_when_explicitly_encoded() {
    let der = roa_attestation(
        Some(0),
        64496,
        vec![roa_ip_family(
            [0, 1],
            vec![roa_ip_address(der_bit_string(7, &[0x80]), None)], // prefix_len=1
        )],
    );
    let roa = RoaEContent::decode_der(&der).expect("ROA must decode");
    assert_eq!(roa.version, 0);
    assert_eq!(roa.as_id, 64496);
}

#[test]
fn ip_address_family_shape_and_address_family_length_are_validated() {
    // family SEQUENCE has wrong length (1)
    let bad_family = der_sequence(vec![der_octet_string(&[0, 1])]);
    let der = roa_attestation(None, 64496, vec![bad_family]);
    let err = RoaEContent::decode_der(&der).unwrap_err();
    assert!(matches!(
        err,
        RoaDecodeError::Validate(RoaProfileError::InvalidIpAddressFamily)
    ));

    // addressFamily OCTET STRING has invalid length (3)
    let family = der_sequence(vec![
        der_octet_string(&[0, 1, 2]),
        der_sequence(vec![roa_ip_address(der_bit_string(0, &[]), None)]),
    ]);
    let der = roa_attestation(None, 64496, vec![family]);
    let err = RoaEContent::decode_der(&der).unwrap_err();
    assert!(matches!(
        err,
        RoaDecodeError::Validate(RoaProfileError::InvalidAddressFamily)
    ));
}

#[test]
fn roa_ip_address_shape_and_prefix_encoding_are_validated() {
    // ROAIPAddress must have 1..2 elements.
    let family = roa_ip_family([0, 1], vec![der_sequence(vec![])]);
    let der = roa_attestation(None, 64496, vec![family]);
    let err = RoaEContent::decode_der(&der).unwrap_err();
    assert!(matches!(
        err,
        RoaDecodeError::Validate(RoaProfileError::InvalidRoaIpAddress)
    ));

    // ROAIPAddress.address must be BIT STRING, not OCTET STRING.
    let family = roa_ip_family([0, 1], vec![der_sequence(vec![der_octet_string(&[0])])]);
    let der = roa_attestation(None, 64496, vec![family]);
    let err = RoaEContent::decode_der(&der).unwrap_err();
    assert!(matches!(
        err,
        RoaDecodeError::Validate(RoaProfileError::InvalidPrefixBitString)
    ));

    // unusedBits > 7 is rejected.
    let family = roa_ip_family([0, 1], vec![roa_ip_address(der_bit_string(8, &[0]), None)]);
    let der = roa_attestation(None, 64496, vec![family]);
    let err = RoaEContent::decode_der(&der).unwrap_err();
    assert!(matches!(
        err,
        RoaDecodeError::Validate(RoaProfileError::InvalidPrefixUnusedBits)
    ));

    // empty BIT STRING with unusedBits != 0 is rejected.
    let family = roa_ip_family([0, 1], vec![roa_ip_address(der_bit_string(1, &[]), None)]);
    let der = roa_attestation(None, 64496, vec![family]);
    let err = RoaEContent::decode_der(&der).unwrap_err();
    assert!(matches!(
        err,
        RoaDecodeError::Validate(RoaProfileError::InvalidPrefixUnusedBits)
    ));
}

#[test]
fn max_length_integer_range_is_validated() {
    // maxLength too large to fit u16 triggers the try_into() error path.
    let family = roa_ip_family(
        [0, 1],
        vec![roa_ip_address(der_bit_string(0, &[0x0A]), Some(70000))],
    );
    let der = roa_attestation(None, 64496, vec![family]);
    let err = RoaEContent::decode_der(&der).unwrap_err();
    assert!(matches!(
        err,
        RoaDecodeError::Validate(RoaProfileError::InvalidMaxLength { .. })
    ));
}

#[test]
fn version_must_be_zero_when_present() {
    let der = roa_attestation(
        Some(1),
        64496,
        vec![roa_ip_family(
            [0, 1],
            vec![roa_ip_address(der_bit_string(0, &[]), None)],
        )],
    );
    let err = RoaEContent::decode_der(&der).unwrap_err();
    assert!(matches!(
        err,
        RoaDecodeError::Validate(RoaProfileError::InvalidVersion(1))
    ));
}

#[test]
fn as_id_out_of_range_is_rejected() {
    let der = roa_attestation(
        None,
        (u32::MAX as u64) + 1,
        vec![roa_ip_family(
            [0, 1],
            vec![roa_ip_address(der_bit_string(0, &[]), None)],
        )],
    );
    let err = RoaEContent::decode_der(&der).unwrap_err();
    assert!(matches!(
        err,
        RoaDecodeError::Validate(RoaProfileError::AsIdOutOfRange(_))
    ));
}

#[test]
fn ip_addr_blocks_len_is_validated() {
    let der = roa_attestation(None, 64496, vec![]);
    let err = RoaEContent::decode_der(&der).unwrap_err();
    assert!(matches!(
        err,
        RoaDecodeError::Validate(RoaProfileError::InvalidIpAddrBlocksLen(0))
    ));

    let families = vec![
        roa_ip_family([0, 1], vec![roa_ip_address(der_bit_string(0, &[]), None)]),
        roa_ip_family([0, 2], vec![roa_ip_address(der_bit_string(0, &[]), None)]),
        roa_ip_family([0, 1], vec![roa_ip_address(der_bit_string(0, &[]), None)]),
    ];
    let der = roa_attestation(None, 64496, families);
    let err = RoaEContent::decode_der(&der).unwrap_err();
    assert!(matches!(
        err,
        RoaDecodeError::Validate(RoaProfileError::InvalidIpAddrBlocksLen(3))
    ));
}

#[test]
fn duplicate_afi_is_rejected() {
    let families = vec![
        roa_ip_family([0, 1], vec![roa_ip_address(der_bit_string(0, &[]), None)]),
        roa_ip_family([0, 1], vec![roa_ip_address(der_bit_string(0, &[]), None)]),
    ];
    let der = roa_attestation(None, 64496, families);
    let err = RoaEContent::decode_der(&der).unwrap_err();
    assert!(matches!(
        err,
        RoaDecodeError::Validate(RoaProfileError::DuplicateAfi(_))
    ));
}

#[test]
fn unsupported_afi_is_rejected() {
    let der = roa_attestation(
        None,
        64496,
        vec![roa_ip_family(
            [0x12, 0x34],
            vec![roa_ip_address(der_bit_string(0, &[]), None)],
        )],
    );
    let err = RoaEContent::decode_der(&der).unwrap_err();
    assert!(matches!(
        err,
        RoaDecodeError::Validate(RoaProfileError::UnsupportedAfi(_))
    ));
}

#[test]
fn empty_address_list_is_rejected() {
    let der = roa_attestation(None, 64496, vec![roa_ip_family([0, 1], vec![])]);
    let err = RoaEContent::decode_der(&der).unwrap_err();
    assert!(matches!(
        err,
        RoaDecodeError::Validate(RoaProfileError::EmptyAddressList)
    ));
}

#[test]
fn prefix_unused_bits_must_be_zeroed() {
    // prefix_len=1 => unused_bits=7, last byte must have lower 7 bits zero.
    // Use 0b1000_0001 which has a non-zero unused bit.
    let bs = der_bit_string(7, &[0b1000_0001]);
    let der = roa_attestation(
        None,
        64496,
        vec![roa_ip_family([0, 1], vec![roa_ip_address(bs, None)])],
    );
    let err = RoaEContent::decode_der(&der).unwrap_err();
    assert!(matches!(
        err,
        RoaDecodeError::Validate(RoaProfileError::InvalidPrefixUnusedBits)
    ));
}

#[test]
fn prefix_len_out_of_range_is_rejected() {
    // IPv4 ub=32, encode 33 bits: 5 bytes with unused_bits=7 => 40-7=33.
    let bs = der_bit_string(7, &[0u8; 5]);
    let der = roa_attestation(
        None,
        64496,
        vec![roa_ip_family([0, 1], vec![roa_ip_address(bs, None)])],
    );
    let err = RoaEContent::decode_der(&der).unwrap_err();
    assert!(matches!(
        err,
        RoaDecodeError::Validate(RoaProfileError::PrefixLenOutOfRange { .. })
    ));
}

#[test]
fn max_length_range_and_relation_are_validated() {
    // IPv4, prefix_len=8
    let bs = der_bit_string(0, &[0x0A]);
    // maxLength < prefix_len
    let der = roa_attestation(
        None,
        64496,
        vec![roa_ip_family(
            [0, 1],
            vec![roa_ip_address(bs.clone(), Some(7))],
        )],
    );
    let err = RoaEContent::decode_der(&der).unwrap_err();
    assert!(matches!(
        err,
        RoaDecodeError::Validate(RoaProfileError::InvalidMaxLength { .. })
    ));

    // maxLength > ub
    let der = roa_attestation(
        None,
        64496,
        vec![roa_ip_family([0, 1], vec![roa_ip_address(bs, Some(33))])],
    );
    let err = RoaEContent::decode_der(&der).unwrap_err();
    assert!(matches!(
        err,
        RoaDecodeError::Validate(RoaProfileError::InvalidMaxLength { .. })
    ));
}
