use crate::data_model::rc::AccessDescription;

pub(super) fn encode_access_description_der_for_vcir_ccr_projection(
    access_description: &AccessDescription,
) -> Result<Vec<u8>, String> {
    let oid = encode_oid_der_for_vcir_ccr_projection(&access_description.access_method_oid)?;
    let uri = encode_tlv_for_vcir_ccr_projection(
        0x86,
        access_description.access_location.as_bytes().to_vec(),
    );
    Ok(encode_sequence_for_vcir_ccr_projection(&[oid, uri]))
}

fn encode_oid_der_for_vcir_ccr_projection(oid: &str) -> Result<Vec<u8>, String> {
    let arcs = oid
        .split('.')
        .map(|part| {
            part.parse::<u64>()
                .map_err(|_| format!("unsupported accessMethod OID: {oid}"))
        })
        .collect::<Result<Vec<_>, _>>()?;
    if arcs.len() < 2 {
        return Err(format!("unsupported accessMethod OID: {oid}"));
    }
    if arcs[0] > 2 || (arcs[0] < 2 && arcs[1] >= 40) {
        return Err(format!("unsupported accessMethod OID: {oid}"));
    }
    let mut body = Vec::new();
    body.push((arcs[0] * 40 + arcs[1]) as u8);
    for arc in &arcs[2..] {
        encode_base128_for_vcir_ccr_projection(*arc, &mut body);
    }
    Ok(encode_tlv_for_vcir_ccr_projection(0x06, body))
}

fn encode_base128_for_vcir_ccr_projection(mut value: u64, out: &mut Vec<u8>) {
    let mut tmp = vec![(value & 0x7F) as u8];
    value >>= 7;
    while value > 0 {
        tmp.push(((value & 0x7F) as u8) | 0x80);
        value >>= 7;
    }
    tmp.reverse();
    out.extend_from_slice(&tmp);
}

fn encode_sequence_for_vcir_ccr_projection(elements: &[Vec<u8>]) -> Vec<u8> {
    let total_len: usize = elements.iter().map(Vec::len).sum();
    let mut buf = Vec::with_capacity(total_len);
    for element in elements {
        buf.extend_from_slice(element);
    }
    encode_tlv_for_vcir_ccr_projection(0x30, buf)
}

fn encode_tlv_for_vcir_ccr_projection(tag: u8, value: Vec<u8>) -> Vec<u8> {
    let mut out = Vec::with_capacity(1 + 9 + value.len());
    out.push(tag);
    encode_length_for_vcir_ccr_projection(value.len(), &mut out);
    out.extend_from_slice(&value);
    out
}

fn encode_length_for_vcir_ccr_projection(len: usize, out: &mut Vec<u8>) {
    if len < 0x80 {
        out.push(len as u8);
        return;
    }
    let mut bytes = Vec::new();
    let mut value = len;
    while value > 0 {
        bytes.push((value & 0xFF) as u8);
        value >>= 8;
    }
    bytes.reverse();
    out.push(0x80 | (bytes.len() as u8));
    out.extend_from_slice(&bytes);
}
