use crate::ccr::model::{
    AspaPayloadSet, AspaPayloadState, CCR_VERSION_V0, CcrContentInfo, CcrDigestAlgorithm,
    ManifestInstance, ManifestState, RoaPayloadSet, RoaPayloadState, RouterKey, RouterKeySet,
    RouterKeyState, RpkiCanonicalCacheRepresentation, TrustAnchorState,
};
use crate::data_model::common::BigUnsigned;
use crate::data_model::oid::{OID_CT_RPKI_CCR_RAW, OID_SHA256_RAW};

#[derive(Debug, thiserror::Error)]
pub enum CcrEncodeError {
    #[error("CCR model validation failed: {0}")]
    Validate(String),

    #[error("GeneralizedTime formatting failed: {0}")]
    ProducedAtFormat(String),
}

pub fn encode_content_info(content_info: &CcrContentInfo) -> Result<Vec<u8>, CcrEncodeError> {
    content_info.validate().map_err(CcrEncodeError::Validate)?;
    let content_der = encode_ccr(&content_info.content)?;
    Ok(encode_sequence(&[
        encode_oid(OID_CT_RPKI_CCR_RAW),
        encode_explicit(0, &content_der),
    ]))
}

pub fn encode_ccr(ccr: &RpkiCanonicalCacheRepresentation) -> Result<Vec<u8>, CcrEncodeError> {
    ccr.validate().map_err(CcrEncodeError::Validate)?;
    let mut fields = Vec::new();
    if ccr.version != CCR_VERSION_V0 {
        fields.push(encode_explicit(0, &encode_integer_u32(ccr.version)));
    }
    fields.push(encode_digest_algorithm(&ccr.hash_alg));
    fields.push(encode_generalized_time(ccr.produced_at)?);
    if let Some(mfts) = &ccr.mfts {
        fields.push(encode_explicit(1, &encode_manifest_state(mfts)?));
    }
    if let Some(vrps) = &ccr.vrps {
        fields.push(encode_explicit(2, &encode_roa_payload_state(vrps)?));
    }
    if let Some(vaps) = &ccr.vaps {
        fields.push(encode_explicit(3, &encode_aspa_payload_state(vaps)?));
    }
    if let Some(tas) = &ccr.tas {
        fields.push(encode_explicit(4, &encode_trust_anchor_state(tas)?));
    }
    if let Some(rks) = &ccr.rks {
        fields.push(encode_explicit(5, &encode_router_key_state(rks)?));
    }
    Ok(encode_sequence(&fields))
}

pub fn encode_manifest_state(state: &ManifestState) -> Result<Vec<u8>, CcrEncodeError> {
    state.validate().map_err(CcrEncodeError::Validate)?;
    let mis = encode_manifest_state_payload_der(&state.mis)?;
    Ok(encode_sequence(&[
        mis,
        encode_generalized_time(state.most_recent_update)?,
        encode_octet_string(&state.hash),
    ]))
}

pub fn encode_manifest_state_payload_der(
    instances: &[ManifestInstance],
) -> Result<Vec<u8>, CcrEncodeError> {
    Ok(encode_sequence(
        &instances
            .iter()
            .map(encode_manifest_instance)
            .collect::<Result<Vec<_>, _>>()?,
    ))
}

fn encode_manifest_instance(instance: &ManifestInstance) -> Result<Vec<u8>, CcrEncodeError> {
    instance.validate().map_err(CcrEncodeError::Validate)?;
    let mut fields = vec![
        encode_octet_string(&instance.hash),
        encode_integer_u64(instance.size),
        encode_octet_string(&instance.aki),
        encode_integer_bigunsigned(&instance.manifest_number),
        encode_generalized_time(instance.this_update)?,
        encode_sequence(&instance.locations),
    ];
    if !instance.subordinates.is_empty() {
        fields.push(encode_sequence(
            &instance
                .subordinates
                .iter()
                .map(|ski| encode_octet_string(ski))
                .collect::<Vec<_>>(),
        ));
    }
    Ok(encode_sequence(&fields))
}

pub fn encode_roa_payload_state(state: &RoaPayloadState) -> Result<Vec<u8>, CcrEncodeError> {
    state.validate().map_err(CcrEncodeError::Validate)?;
    let rps = encode_roa_payload_state_payload_der(&state.rps)?;
    Ok(encode_sequence(&[rps, encode_octet_string(&state.hash)]))
}

pub fn encode_roa_payload_state_payload_der(
    sets: &[RoaPayloadSet],
) -> Result<Vec<u8>, CcrEncodeError> {
    Ok(encode_sequence(
        &sets
            .iter()
            .map(encode_roa_payload_set)
            .collect::<Result<Vec<_>, _>>()?,
    ))
}

fn encode_roa_payload_set(set: &RoaPayloadSet) -> Result<Vec<u8>, CcrEncodeError> {
    set.validate().map_err(CcrEncodeError::Validate)?;
    Ok(encode_sequence(&[
        encode_integer_u32(set.as_id),
        encode_sequence(&set.ip_addr_blocks),
    ]))
}

pub fn encode_aspa_payload_state(state: &AspaPayloadState) -> Result<Vec<u8>, CcrEncodeError> {
    state.validate().map_err(CcrEncodeError::Validate)?;
    let aps = encode_aspa_payload_state_payload_der(&state.aps)?;
    Ok(encode_sequence(&[aps, encode_octet_string(&state.hash)]))
}

pub fn encode_aspa_payload_state_payload_der(
    sets: &[AspaPayloadSet],
) -> Result<Vec<u8>, CcrEncodeError> {
    Ok(encode_sequence(
        &sets
            .iter()
            .map(encode_aspa_payload_set)
            .collect::<Result<Vec<_>, _>>()?,
    ))
}

fn encode_aspa_payload_set(set: &AspaPayloadSet) -> Result<Vec<u8>, CcrEncodeError> {
    set.validate().map_err(CcrEncodeError::Validate)?;
    Ok(encode_sequence(&[
        encode_integer_u32(set.customer_as_id),
        encode_sequence(
            &set.providers
                .iter()
                .map(|provider| encode_integer_u32(*provider))
                .collect::<Vec<_>>(),
        ),
    ]))
}

pub fn encode_trust_anchor_state(state: &TrustAnchorState) -> Result<Vec<u8>, CcrEncodeError> {
    state.validate().map_err(CcrEncodeError::Validate)?;
    let skis = encode_trust_anchor_state_payload_der(&state.skis)?;
    Ok(encode_sequence(&[skis, encode_octet_string(&state.hash)]))
}

pub fn encode_trust_anchor_state_payload_der(skis: &[Vec<u8>]) -> Result<Vec<u8>, CcrEncodeError> {
    Ok(encode_sequence(
        &skis
            .iter()
            .map(|ski| encode_octet_string(ski))
            .collect::<Vec<_>>(),
    ))
}

pub fn encode_router_key_state(state: &RouterKeyState) -> Result<Vec<u8>, CcrEncodeError> {
    state.validate().map_err(CcrEncodeError::Validate)?;
    let rksets = encode_router_key_state_payload_der(&state.rksets)?;
    Ok(encode_sequence(&[rksets, encode_octet_string(&state.hash)]))
}

pub fn encode_router_key_state_payload_der(
    sets: &[RouterKeySet],
) -> Result<Vec<u8>, CcrEncodeError> {
    Ok(encode_sequence(
        &sets
            .iter()
            .map(encode_router_key_set)
            .collect::<Result<Vec<_>, _>>()?,
    ))
}

fn encode_router_key_set(set: &RouterKeySet) -> Result<Vec<u8>, CcrEncodeError> {
    set.validate().map_err(CcrEncodeError::Validate)?;
    Ok(encode_sequence(&[
        encode_integer_u32(set.as_id),
        encode_sequence(
            &set.router_keys
                .iter()
                .map(encode_router_key)
                .collect::<Result<Vec<_>, _>>()?,
        ),
    ]))
}

fn encode_router_key(key: &RouterKey) -> Result<Vec<u8>, CcrEncodeError> {
    key.validate().map_err(CcrEncodeError::Validate)?;
    Ok(encode_sequence(&[
        encode_octet_string(&key.ski),
        key.spki_der.clone(),
    ]))
}

fn encode_digest_algorithm(alg: &CcrDigestAlgorithm) -> Vec<u8> {
    match alg {
        CcrDigestAlgorithm::Sha256 => encode_sequence(&[encode_oid(OID_SHA256_RAW)]),
    }
}

fn encode_generalized_time(t: time::OffsetDateTime) -> Result<Vec<u8>, CcrEncodeError> {
    let t = t.to_offset(time::UtcOffset::UTC);
    let s = format!(
        "{:04}{:02}{:02}{:02}{:02}{:02}Z",
        t.year(),
        u8::from(t.month()),
        t.day(),
        t.hour(),
        t.minute(),
        t.second()
    );
    Ok(encode_tlv(0x18, s.into_bytes()))
}

fn encode_integer_u32(v: u32) -> Vec<u8> {
    encode_integer_bytes(unsigned_integer_bytes(v as u64))
}

fn encode_integer_u64(v: u64) -> Vec<u8> {
    encode_integer_bytes(unsigned_integer_bytes(v))
}

fn encode_integer_bigunsigned(v: &BigUnsigned) -> Vec<u8> {
    encode_integer_bytes(v.bytes_be.clone())
}

fn encode_integer_bytes(mut bytes: Vec<u8>) -> Vec<u8> {
    if bytes.is_empty() {
        bytes.push(0);
    }
    if bytes[0] & 0x80 != 0 {
        bytes.insert(0, 0);
    }
    encode_tlv(0x02, bytes)
}

fn unsigned_integer_bytes(v: u64) -> Vec<u8> {
    if v == 0 {
        return vec![0];
    }
    let mut out = Vec::new();
    let mut n = v;
    while n > 0 {
        out.push((n & 0xFF) as u8);
        n >>= 8;
    }
    out.reverse();
    out
}

fn encode_oid(raw_body: &[u8]) -> Vec<u8> {
    encode_tlv(0x06, raw_body.to_vec())
}

fn encode_octet_string(bytes: &[u8]) -> Vec<u8> {
    encode_tlv(0x04, bytes.to_vec())
}

fn encode_explicit(tag_number: u8, inner_der: &[u8]) -> Vec<u8> {
    encode_tlv(0xA0 + tag_number, inner_der.to_vec())
}

fn encode_sequence(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(0x30, buf)
}

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

fn encode_length(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);
}
