use std::collections::{BTreeMap, BTreeSet};

use serde::Serialize;
use sha2::Digest;

use crate::ccr::encode::{
    encode_aspa_payload_state_payload_der, encode_manifest_state_payload_der,
    encode_roa_payload_state_payload_der, encode_router_key_state_payload_der,
    encode_trust_anchor_state_payload_der,
};
use crate::ccr::hash::compute_state_hash;
use crate::ccr::manifest_location::select_manifest_signed_object_location;
use crate::ccr::model::{
    AspaPayloadSet, AspaPayloadState, ManifestInstance, ManifestState, RoaPayloadSet,
    RoaPayloadState, RouterKey, RouterKeySet, RouterKeyState, TrustAnchorState,
};
use crate::data_model::manifest::ManifestObject;
use crate::data_model::rc::SubjectInfoAccess;
use crate::data_model::roa::RoaAfi;
use crate::data_model::router_cert::BgpsecRouterCertificate;
use crate::data_model::ta::TrustAnchor;
use crate::storage::{RocksStore, ValidatedCaInstanceResult, VcirArtifactRole};
use crate::validation::objects::{AspaAttestation, RouterKeyPayload, Vrp};

#[derive(Debug, thiserror::Error)]
pub enum CcrBuildError {
    #[error("trust anchor set must not be empty")]
    EmptyTrustAnchors,

    #[error("trust anchor certificate missing SubjectKeyIdentifier")]
    MissingTrustAnchorSki,

    #[error("ROA payload state encoding failed: {0}")]
    RoaEncode(String),

    #[error("ASPA payload state encoding failed: {0}")]
    AspaEncode(String),

    #[error("TrustAnchor state encoding failed: {0}")]
    TrustAnchorEncode(String),

    #[error("manifest artifact missing in VCIR: {0}")]
    MissingManifestArtifact(String),

    #[error("manifest raw bytes missing in store for {manifest_rsync_uri}: {sha256_hex}")]
    MissingManifestRawBytes {
        manifest_rsync_uri: String,
        sha256_hex: String,
    },

    #[error("manifest raw bytes load failed for {manifest_rsync_uri}: {detail}")]
    LoadManifestRawBytes {
        manifest_rsync_uri: String,
        detail: String,
    },

    #[error("manifest decode failed for {manifest_rsync_uri}: {detail}")]
    ManifestDecode {
        manifest_rsync_uri: String,
        detail: String,
    },

    #[error("manifest EE certificate missing AuthorityKeyIdentifier: {0}")]
    ManifestEeMissingAki(String),

    #[error("manifest EE certificate missing Subject Information Access extension: {0}")]
    ManifestEeMissingSia(String),

    #[error("manifest EE certificate SIA is not the EE form: {0}")]
    ManifestEeSiaWrongVariant(String),

    #[error(
        "manifest EE certificate SIA location selection failed for {manifest_rsync_uri}: {detail}"
    )]
    ManifestSiaLocationSelection {
        manifest_rsync_uri: String,
        detail: String,
    },

    #[error("manifest child SKI is not valid lowercase hex or not 20 bytes: {0}")]
    InvalidChildSki(String),

    #[error("manifest state encoding failed: {0}")]
    ManifestEncode(String),

    #[error("duplicate manifest hash with conflicting content for URI: {0}")]
    DuplicateManifestHashConflict(String),

    #[error("router key state encoding failed: {0}")]
    RouterKeyEncode(String),
}

#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
pub struct ManifestStateBuildBreakdown {
    pub vcir_count: usize,
    pub unique_manifest_count: usize,
    pub find_manifest_artifact_ms: u64,
    pub load_manifest_blob_ms: u64,
    pub decode_manifest_der_ms: u64,
    pub build_manifest_instance_ms: u64,
    pub dedup_manifest_instance_ms: u64,
    pub encode_manifest_state_ms: u64,
    pub total_ms: u64,
}

pub fn build_roa_payload_state(vrps: &[Vrp]) -> Result<RoaPayloadState, CcrBuildError> {
    let mut grouped: BTreeMap<u32, BTreeSet<RoaPayloadKey>> = BTreeMap::new();
    for vrp in vrps {
        grouped
            .entry(vrp.asn)
            .or_default()
            .insert(RoaPayloadKey::from_vrp(vrp));
    }

    let mut rps = Vec::with_capacity(grouped.len());
    for (asn, entries) in grouped {
        let mut families: BTreeMap<u16, Vec<RoaPayloadKey>> = BTreeMap::new();
        for entry in entries {
            families.entry(entry.afi).or_default().push(entry);
        }
        let mut ip_addr_blocks = Vec::with_capacity(families.len());
        for (afi, entries) in families {
            ip_addr_blocks.push(encode_roa_ip_address_family(afi, &entries));
        }
        rps.push(RoaPayloadSet {
            as_id: asn,
            ip_addr_blocks,
        });
    }

    let payload_der = encode_roa_payload_state_payload_der(&rps)
        .map_err(|e| CcrBuildError::RoaEncode(e.to_string()))?;
    Ok(RoaPayloadState {
        rps,
        hash: compute_state_hash(&payload_der),
    })
}

pub fn build_aspa_payload_state(
    attestations: &[AspaAttestation],
) -> Result<AspaPayloadState, CcrBuildError> {
    let mut grouped: BTreeMap<u32, BTreeSet<u32>> = BTreeMap::new();
    for attestation in attestations {
        grouped
            .entry(attestation.customer_as_id)
            .or_default()
            .extend(attestation.provider_as_ids.iter().copied());
    }

    let aps: Vec<AspaPayloadSet> = grouped
        .into_iter()
        .map(|(customer_as_id, providers)| AspaPayloadSet {
            customer_as_id,
            providers: providers.into_iter().collect(),
        })
        .collect();

    let payload_der = encode_aspa_payload_state_payload_der(&aps)
        .map_err(|e| CcrBuildError::AspaEncode(e.to_string()))?;
    Ok(AspaPayloadState {
        aps,
        hash: compute_state_hash(&payload_der),
    })
}

pub fn build_trust_anchor_state(
    trust_anchors: &[TrustAnchor],
) -> Result<TrustAnchorState, CcrBuildError> {
    if trust_anchors.is_empty() {
        return Err(CcrBuildError::EmptyTrustAnchors);
    }
    let mut skis: BTreeSet<Vec<u8>> = BTreeSet::new();
    for ta in trust_anchors {
        let ski = ta
            .ta_certificate
            .rc_ca
            .tbs
            .extensions
            .subject_key_identifier
            .clone()
            .ok_or(CcrBuildError::MissingTrustAnchorSki)?;
        skis.insert(ski);
    }
    let skis: Vec<Vec<u8>> = skis.into_iter().collect();
    let payload_der = encode_trust_anchor_state_payload_der(&skis)
        .map_err(|e| CcrBuildError::TrustAnchorEncode(e.to_string()))?;
    Ok(TrustAnchorState {
        skis,
        hash: compute_state_hash(&payload_der),
    })
}

pub fn build_manifest_state_from_vcirs(
    store: &RocksStore,
    vcirs: &[ValidatedCaInstanceResult],
) -> Result<ManifestState, CcrBuildError> {
    build_manifest_state_from_vcirs_with_breakdown(store, vcirs).map(|(state, _)| state)
}

pub fn build_manifest_state_from_vcirs_with_breakdown(
    store: &RocksStore,
    vcirs: &[ValidatedCaInstanceResult],
) -> Result<(ManifestState, ManifestStateBuildBreakdown), CcrBuildError> {
    let total_started = std::time::Instant::now();
    let mut breakdown = ManifestStateBuildBreakdown {
        vcir_count: vcirs.len(),
        ..ManifestStateBuildBreakdown::default()
    };
    let mut find_manifest_artifact_duration = std::time::Duration::ZERO;
    let mut load_manifest_blob_duration = std::time::Duration::ZERO;
    let mut decode_manifest_der_duration = std::time::Duration::ZERO;
    let mut build_manifest_instance_duration = std::time::Duration::ZERO;
    let mut dedup_manifest_instance_duration = std::time::Duration::ZERO;
    let mut mis_by_hash: BTreeMap<Vec<u8>, ManifestInstance> = BTreeMap::new();
    let mut most_recent_update = time::OffsetDateTime::UNIX_EPOCH;

    for vcir in vcirs {
        let started = std::time::Instant::now();
        let manifest_artifact = vcir
            .related_artifacts
            .iter()
            .find(|artifact| {
                artifact.artifact_role == VcirArtifactRole::Manifest
                    && artifact.uri.as_deref() == Some(vcir.current_manifest_rsync_uri.as_str())
            })
            .ok_or_else(|| {
                CcrBuildError::MissingManifestArtifact(vcir.current_manifest_rsync_uri.clone())
            })?;
        find_manifest_artifact_duration += started.elapsed();

        let started = std::time::Instant::now();
        let raw_bytes = store
            .get_blob_bytes(&manifest_artifact.sha256)
            .map_err(|e| CcrBuildError::LoadManifestRawBytes {
                manifest_rsync_uri: vcir.current_manifest_rsync_uri.clone(),
                detail: e.to_string(),
            })?
            .ok_or_else(|| CcrBuildError::MissingManifestRawBytes {
                manifest_rsync_uri: vcir.current_manifest_rsync_uri.clone(),
                sha256_hex: manifest_artifact.sha256.clone(),
            })?;
        load_manifest_blob_duration += started.elapsed();

        let started = std::time::Instant::now();
        let manifest =
            ManifestObject::decode_der(&raw_bytes).map_err(|e| CcrBuildError::ManifestDecode {
                manifest_rsync_uri: vcir.current_manifest_rsync_uri.clone(),
                detail: e.to_string(),
            })?;
        decode_manifest_der_duration += started.elapsed();

        let started = std::time::Instant::now();
        let ee = &manifest.signed_object.signed_data.certificates[0].resource_cert;
        let aki = ee
            .tbs
            .extensions
            .authority_key_identifier
            .clone()
            .ok_or_else(|| {
                CcrBuildError::ManifestEeMissingAki(vcir.current_manifest_rsync_uri.clone())
            })?;
        let sia = ee
            .tbs
            .extensions
            .subject_info_access
            .as_ref()
            .ok_or_else(|| {
                CcrBuildError::ManifestEeMissingSia(vcir.current_manifest_rsync_uri.clone())
            })?;
        let locations = match sia {
            SubjectInfoAccess::Ee(ee_sia) => vec![
                select_manifest_signed_object_location(
                    &vcir.current_manifest_rsync_uri,
                    &ee_sia.access_descriptions,
                )
                .map_err(|detail| CcrBuildError::ManifestSiaLocationSelection {
                    manifest_rsync_uri: vcir.current_manifest_rsync_uri.clone(),
                    detail,
                })?,
            ],
            SubjectInfoAccess::Ca(_) => {
                return Err(CcrBuildError::ManifestEeSiaWrongVariant(
                    vcir.current_manifest_rsync_uri.clone(),
                ));
            }
        };

        let subordinates = collect_subordinate_ski_bytes(vcir)?;
        let this_update = vcir
            .validated_manifest_meta
            .validated_manifest_this_update
            .parse()
            .map_err(|e| CcrBuildError::ManifestDecode {
                manifest_rsync_uri: vcir.current_manifest_rsync_uri.clone(),
                detail: format!("invalid validated_manifest_this_update: {e}"),
            })?;
        if this_update > most_recent_update {
            most_recent_update = this_update;
        }

        let instance = ManifestInstance {
            hash: sha2::Sha256::digest(&raw_bytes).to_vec(),
            size: raw_bytes.len() as u64,
            aki,
            manifest_number: crate::data_model::common::BigUnsigned {
                bytes_be: vcir
                    .validated_manifest_meta
                    .validated_manifest_number
                    .clone(),
            },
            this_update,
            locations,
            subordinates,
        };
        build_manifest_instance_duration += started.elapsed();

        let started = std::time::Instant::now();
        match mis_by_hash.get(instance.hash.as_slice()) {
            Some(existing) if existing != &instance => {
                return Err(CcrBuildError::DuplicateManifestHashConflict(
                    vcir.current_manifest_rsync_uri.clone(),
                ));
            }
            Some(_) => {}
            None => {
                mis_by_hash.insert(instance.hash.clone(), instance);
            }
        }
        dedup_manifest_instance_duration += started.elapsed();
    }

    let started = std::time::Instant::now();
    let mis: Vec<ManifestInstance> = mis_by_hash.into_values().collect();
    let payload_der = encode_manifest_state_payload_der(&mis)
        .map_err(|e| CcrBuildError::ManifestEncode(e.to_string()))?;
    let state = ManifestState {
        mis,
        most_recent_update,
        hash: compute_state_hash(&payload_der),
    };
    breakdown.encode_manifest_state_ms = started.elapsed().as_millis() as u64;
    breakdown.find_manifest_artifact_ms = find_manifest_artifact_duration.as_millis() as u64;
    breakdown.load_manifest_blob_ms = load_manifest_blob_duration.as_millis() as u64;
    breakdown.decode_manifest_der_ms = decode_manifest_der_duration.as_millis() as u64;
    breakdown.build_manifest_instance_ms = build_manifest_instance_duration.as_millis() as u64;
    breakdown.dedup_manifest_instance_ms = dedup_manifest_instance_duration.as_millis() as u64;
    breakdown.unique_manifest_count = state.mis.len();
    breakdown.total_ms = total_started.elapsed().as_millis() as u64;
    Ok((state, breakdown))
}

fn collect_subordinate_ski_bytes(
    vcir: &ValidatedCaInstanceResult,
) -> Result<Vec<Vec<u8>>, CcrBuildError> {
    let mut skis = BTreeSet::new();
    for child in &vcir.child_entries {
        let bytes = hex::decode(&child.child_ski)
            .map_err(|_| CcrBuildError::InvalidChildSki(child.child_ski.clone()))?;
        if bytes.len() != 20 {
            return Err(CcrBuildError::InvalidChildSki(child.child_ski.clone()));
        }
        skis.insert(bytes);
    }
    Ok(skis.into_iter().collect())
}

pub fn build_router_key_state(
    router_certs: &[BgpsecRouterCertificate],
) -> Result<RouterKeyState, CcrBuildError> {
    let mut grouped: BTreeMap<u32, BTreeSet<RouterKey>> = BTreeMap::new();
    for cert in router_certs {
        let key = RouterKey {
            ski: cert.subject_key_identifier.clone(),
            spki_der: cert.spki_der.clone(),
        };
        for asn in &cert.asns {
            grouped.entry(*asn).or_default().insert(key.clone());
        }
    }

    let rksets: Vec<RouterKeySet> = grouped
        .into_iter()
        .map(|(as_id, router_keys)| RouterKeySet {
            as_id,
            router_keys: router_keys.into_iter().collect(),
        })
        .collect();

    let payload_der = encode_router_key_state_payload_der(&rksets)
        .map_err(|e| CcrBuildError::RouterKeyEncode(e.to_string()))?;
    Ok(RouterKeyState {
        rksets,
        hash: compute_state_hash(&payload_der),
    })
}

pub fn build_router_key_state_from_runtime(
    router_keys: &[RouterKeyPayload],
) -> Result<RouterKeyState, CcrBuildError> {
    let mut grouped: BTreeMap<u32, BTreeSet<RouterKey>> = BTreeMap::new();
    for router_key in router_keys {
        grouped
            .entry(router_key.as_id)
            .or_default()
            .insert(RouterKey {
                ski: router_key.ski.clone(),
                spki_der: router_key.spki_der.clone(),
            });
    }

    let rksets: Vec<RouterKeySet> = grouped
        .into_iter()
        .map(|(as_id, router_keys)| RouterKeySet {
            as_id,
            router_keys: router_keys.into_iter().collect(),
        })
        .collect();

    let payload_der = encode_router_key_state_payload_der(&rksets)
        .map_err(|e| CcrBuildError::RouterKeyEncode(e.to_string()))?;
    Ok(RouterKeyState {
        rksets,
        hash: compute_state_hash(&payload_der),
    })
}

#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
struct RoaPayloadKey {
    afi: u16,
    addr: Vec<u8>,
    prefix_len: u8,
    max_length: u8,
}

impl RoaPayloadKey {
    fn from_vrp(vrp: &Vrp) -> Self {
        let afi = match vrp.prefix.afi {
            RoaAfi::Ipv4 => 1,
            RoaAfi::Ipv6 => 2,
        };
        Self {
            afi,
            addr: vrp.prefix.addr.to_vec(),
            prefix_len: vrp.prefix.prefix_len as u8,
            max_length: vrp.max_length as u8,
        }
    }
}

fn encode_roa_ip_address_family(afi: u16, entries: &[RoaPayloadKey]) -> Vec<u8> {
    let address_family = afi.to_be_bytes();
    let addresses = entries
        .iter()
        .map(encode_roa_ip_address)
        .collect::<Vec<_>>();
    encode_sequence(&[
        encode_octet_string(&address_family),
        encode_sequence(&addresses),
    ])
}

fn encode_roa_ip_address(entry: &RoaPayloadKey) -> Vec<u8> {
    let (unused_bits, content) = encode_prefix_bit_string(&entry.addr, entry.prefix_len);
    let mut fields = vec![encode_bit_string(unused_bits, &content)];
    if entry.max_length != entry.prefix_len {
        fields.push(encode_integer_u8(entry.max_length));
    }
    encode_sequence(&fields)
}

fn encode_prefix_bit_string(addr: &[u8], prefix_len: u8) -> (u8, Vec<u8>) {
    if prefix_len == 0 {
        return (0, Vec::new());
    }
    let octets = ((prefix_len as usize) + 7) / 8;
    let mut content = addr[..octets].to_vec();
    let rem = prefix_len % 8;
    let unused = if rem == 0 { 0 } else { 8 - rem };
    if unused > 0 {
        let mask = 0xFFu8 << unused;
        let last = content.last_mut().expect("octets > 0");
        *last &= mask;
    }
    (unused, content)
}

fn encode_integer_u8(v: u8) -> Vec<u8> {
    encode_integer_bytes(vec![v])
}

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 encode_bit_string(unused_bits: u8, content: &[u8]) -> Vec<u8> {
    let mut value = Vec::with_capacity(content.len() + 1);
    value.push(unused_bits);
    value.extend_from_slice(content);
    encode_tlv(0x03, value)
}

fn encode_octet_string(bytes: &[u8]) -> Vec<u8> {
    encode_tlv(0x04, bytes.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);
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::ccr::decode::decode_content_info;
    use crate::ccr::encode::{
        encode_aspa_payload_state, encode_content_info, encode_trust_anchor_state,
    };
    use crate::ccr::model::{CcrContentInfo, CcrDigestAlgorithm, RpkiCanonicalCacheRepresentation};
    use crate::data_model::roa::{IpPrefix, RoaAfi};
    use crate::data_model::ta::TrustAnchor;
    use crate::data_model::tal::Tal;

    fn sample_vrp_v4(asn: u32, a: [u8; 4], prefix_len: u16, max_length: u16) -> Vrp {
        Vrp {
            asn,
            prefix: IpPrefix {
                afi: RoaAfi::Ipv4,
                prefix_len,
                addr: [a[0], a[1], a[2], a[3], 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
            },
            max_length,
        }
    }

    fn sample_vrp_v6(asn: u32, a: [u8; 16], prefix_len: u16, max_length: u16) -> Vrp {
        Vrp {
            asn,
            prefix: IpPrefix {
                afi: RoaAfi::Ipv6,
                prefix_len,
                addr: a,
            },
            max_length,
        }
    }

    fn decode_family_block(block: &[u8]) -> (u16, Vec<(u8, Vec<u8>, Option<u8>)>) {
        let mut top = crate::data_model::common::DerReader::new(block);
        let mut seq = top.take_sequence().expect("family seq");
        assert!(top.is_empty());
        let afi_bytes = seq.take_octet_string().expect("afi octet string");
        let afi = u16::from_be_bytes([afi_bytes[0], afi_bytes[1]]);
        let mut addrs = seq.take_sequence().expect("addresses seq");
        let mut entries = Vec::new();
        while !addrs.is_empty() {
            let mut addr_seq = addrs.take_sequence().expect("roa ip addr seq");
            let (unused_bits, content) = addr_seq.take_bit_string().expect("bit string");
            let prefix_len = (content.len() * 8) as u8 - unused_bits;
            let max_len = if addr_seq.is_empty() {
                None
            } else {
                Some(addr_seq.take_uint_u64().expect("maxlen") as u8)
            };
            entries.push((prefix_len, content.to_vec(), max_len));
        }
        (afi, entries)
    }

    fn sample_trust_anchor(path_tal: &str, path_ta: &str) -> TrustAnchor {
        let base = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
        let tal_bytes = std::fs::read(base.join(path_tal)).expect("read tal");
        let ta_der = std::fs::read(base.join(path_ta)).expect("read ta");
        let tal = Tal::decode_bytes(&tal_bytes).expect("decode tal");
        TrustAnchor::bind_der(tal, &ta_der, None).expect("bind ta")
    }

    fn sample_router_cert(asns: Vec<u32>, ski_fill: u8, spki_fill: u8) -> BgpsecRouterCertificate {
        let ta = sample_trust_anchor(
            "tests/fixtures/tal/apnic-rfc7730-https.tal",
            "tests/fixtures/ta/apnic-ta.cer",
        );
        BgpsecRouterCertificate {
            raw_der: vec![0x01, 0x02],
            resource_cert: ta.ta_certificate.rc_ca,
            subject_key_identifier: vec![ski_fill; 20],
            spki_der: vec![0x30, 0x03, 0x03, 0x01, spki_fill],
            asns,
        }
    }

    fn sample_manifest_vcir(manifest_der: &[u8], child_ski_hex: &str) -> ValidatedCaInstanceResult {
        let manifest = ManifestObject::decode_der(manifest_der).expect("decode manifest fixture");
        let manifest_uri = match manifest.signed_object.signed_data.certificates[0]
            .resource_cert
            .tbs
            .extensions
            .subject_info_access
            .as_ref()
            .expect("manifest sia")
        {
            SubjectInfoAccess::Ee(ee_sia) => ee_sia
                .access_descriptions
                .iter()
                .find(|ad| {
                    ad.access_method_oid == crate::data_model::oid::OID_AD_SIGNED_OBJECT
                        && ad.access_location.starts_with("rsync://")
                })
                .expect("manifest rsync signedObject")
                .access_location
                .clone(),
            SubjectInfoAccess::Ca(_) => panic!("manifest ee sia should not be CA variant"),
        };
        let hash = hex::encode(sha2::Sha256::digest(manifest_der));
        let now = manifest.manifest.this_update;
        let next = manifest.manifest.next_update;
        let projection = crate::storage::VcirCcrManifestProjection {
            manifest_rsync_uri: manifest_uri.clone(),
            manifest_sha256: sha2::Sha256::digest(manifest_der).to_vec(),
            manifest_size: manifest_der.len() as u64,
            manifest_ee_aki: manifest.signed_object.signed_data.certificates[0]
                .resource_cert
                .tbs
                .extensions
                .authority_key_identifier
                .clone()
                .expect("manifest aki"),
            manifest_number_be: manifest.manifest.manifest_number.bytes_be.clone(),
            manifest_this_update: crate::storage::PackTime::from_utc_offset_datetime(now),
            manifest_sia_locations_der: match manifest.signed_object.signed_data.certificates[0]
                .resource_cert
                .tbs
                .extensions
                .subject_info_access
                .as_ref()
                .expect("manifest sia")
            {
                SubjectInfoAccess::Ee(ee_sia) => vec![
                    select_manifest_signed_object_location(
                        &manifest_uri,
                        &ee_sia.access_descriptions,
                    )
                    .expect("select manifest signedObject"),
                ],
                SubjectInfoAccess::Ca(_) => panic!("manifest ee sia should not be CA variant"),
            },
            subordinate_skis: vec![hex::decode(child_ski_hex).expect("decode child ski")],
        };
        crate::storage::ValidatedCaInstanceResult {
            manifest_rsync_uri: manifest_uri.clone(),
            parent_manifest_rsync_uri: None,
            tal_id: "test-tal".to_string(),
            ca_subject_name: "CN=test".to_string(),
            ca_ski: "11".repeat(20),
            issuer_ski: "22".repeat(20),
            last_successful_validation_time: crate::storage::PackTime::from_utc_offset_datetime(
                now,
            ),
            current_manifest_rsync_uri: manifest_uri.clone(),
            current_crl_rsync_uri: format!("{manifest_uri}.crl"),
            validated_manifest_meta: crate::storage::ValidatedManifestMeta {
                validated_manifest_number: manifest.manifest.manifest_number.bytes_be.clone(),
                validated_manifest_this_update: crate::storage::PackTime::from_utc_offset_datetime(
                    now,
                ),
                validated_manifest_next_update: crate::storage::PackTime::from_utc_offset_datetime(
                    next,
                ),
            },
            ccr_manifest_projection: projection,
            instance_gate: crate::storage::VcirInstanceGate {
                manifest_next_update: crate::storage::PackTime::from_utc_offset_datetime(next),
                current_crl_next_update: crate::storage::PackTime::from_utc_offset_datetime(next),
                self_ca_not_after: crate::storage::PackTime::from_utc_offset_datetime(next),
                instance_effective_until: crate::storage::PackTime::from_utc_offset_datetime(next),
            },
            child_entries: vec![crate::storage::VcirChildEntry {
                child_manifest_rsync_uri: format!("{manifest_uri}/child.mft"),
                child_cert_rsync_uri: format!("{manifest_uri}/child.cer"),
                child_cert_hash: "aa".repeat(32),
                child_ski: child_ski_hex.to_string(),
                child_rsync_base_uri: format!("{manifest_uri}/"),
                child_publication_point_rsync_uri: format!("{manifest_uri}/"),
                child_rrdp_notification_uri: None,
                child_effective_ip_resources: None,
                child_effective_as_resources: None,
                accepted_at_validation_time: crate::storage::PackTime::from_utc_offset_datetime(
                    now,
                ),
            }],
            local_outputs: Vec::new(),
            related_artifacts: vec![crate::storage::VcirRelatedArtifact {
                artifact_role: crate::storage::VcirArtifactRole::Manifest,
                artifact_kind: crate::storage::VcirArtifactKind::Mft,
                uri: Some(manifest_uri.clone()),
                sha256: hash,
                object_type: Some("mft".to_string()),
                validation_status: crate::storage::VcirArtifactValidationStatus::Accepted,
                reject_reason: None,
            }],
            summary: crate::storage::VcirSummary {
                local_vrp_count: 0,
                local_aspa_count: 0,
                local_router_key_count: 0,
                child_count: 1,
                accepted_object_count: 1,
                rejected_object_count: 0,
            },
            audit_summary: crate::storage::VcirAuditSummary {
                failed_fetch_eligible: true,
                last_failed_fetch_reason: None,
                warning_count: 0,
                audit_flags: Vec::new(),
            },
        }
    }

    #[test]
    fn build_manifest_state_from_vcirs_collects_current_manifests_and_hashes_payload() {
        let base = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
        let manifest_a = std::fs::read(base.join("tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft")).expect("read manifest a");
        let manifest_b = std::fs::read(base.join(
            "tests/fixtures/repository/ca.rg.net/rpki/RGnet-OU/bW-_qXU9uNhGQz21NR2ansB8lr0.mft",
        ))
        .expect("read manifest b");
        let vcir_a = sample_manifest_vcir(&manifest_a, &"33".repeat(20));
        let vcir_b = sample_manifest_vcir(&manifest_b, &"44".repeat(20));
        let store_dir = tempfile::tempdir().expect("tempdir");
        let store = RocksStore::open(store_dir.path()).expect("open rocksdb");
        for (vcir, bytes) in [(&vcir_a, &manifest_a), (&vcir_b, &manifest_b)] {
            let artifact = &vcir.related_artifacts[0];
            let mut raw =
                crate::storage::RawByHashEntry::from_bytes(artifact.sha256.clone(), bytes.to_vec());
            raw.origin_uris
                .push(vcir.current_manifest_rsync_uri.clone());
            raw.object_type = Some("mft".to_string());
            raw.encoding = Some("der".to_string());
            store.put_raw_by_hash_entry(&raw).expect("put raw manifest");
        }

        let state = build_manifest_state_from_vcirs(&store, &[vcir_a.clone(), vcir_b.clone()])
            .expect("build manifest state");
        assert_eq!(state.mis.len(), 2);
        assert!(state.mis[0].hash < state.mis[1].hash);
        let expected_subordinates = [
            hex::decode(vcir_a.child_entries[0].child_ski.clone()).unwrap(),
            hex::decode(vcir_b.child_entries[0].child_ski.clone()).unwrap(),
        ];
        let actual_subordinates = state
            .mis
            .iter()
            .map(|mi| {
                assert_eq!(mi.subordinates.len(), 1);
                assert!(!mi.locations.is_empty());
                mi.subordinates[0].clone()
            })
            .collect::<std::collections::BTreeSet<_>>();
        let expected_subordinates = expected_subordinates
            .into_iter()
            .collect::<std::collections::BTreeSet<_>>();
        assert_eq!(actual_subordinates, expected_subordinates);
        let payload_der =
            encode_manifest_state_payload_der(&state.mis).expect("encode mis payload");
        assert!(crate::ccr::verify_state_hash(&state.hash, &payload_der));
        let max_time = [
            vcir_a
                .validated_manifest_meta
                .validated_manifest_this_update
                .parse()
                .unwrap(),
            vcir_b
                .validated_manifest_meta
                .validated_manifest_this_update
                .parse()
                .unwrap(),
        ]
        .into_iter()
        .max()
        .unwrap();
        assert_eq!(state.most_recent_update, max_time);
    }

    #[test]
    fn build_manifest_state_from_vcirs_empty_uses_epoch() {
        let store_dir = tempfile::tempdir().expect("tempdir");
        let store = RocksStore::open(store_dir.path()).expect("open rocksdb");
        let state = build_manifest_state_from_vcirs(&store, &[]).expect("empty manifest state");
        assert!(state.mis.is_empty());
        assert_eq!(state.most_recent_update, time::OffsetDateTime::UNIX_EPOCH);
        let payload_der =
            encode_manifest_state_payload_der(&state.mis).expect("encode mis payload");
        assert!(crate::ccr::verify_state_hash(&state.hash, &payload_der));
    }

    #[test]
    fn build_router_key_state_groups_dedupes_and_sorts() {
        let certs = vec![
            sample_router_cert(vec![64497], 0x21, 0xA1),
            sample_router_cert(vec![64496, 64497], 0x11, 0xA0),
            sample_router_cert(vec![64496], 0x11, 0xA0),
            sample_router_cert(vec![64496], 0x11, 0xA2),
        ];
        let state = build_router_key_state(&certs).expect("build router key state");
        assert_eq!(state.rksets.len(), 2);
        assert_eq!(state.rksets[0].as_id, 64496);
        assert_eq!(state.rksets[1].as_id, 64497);
        assert_eq!(state.rksets[0].router_keys.len(), 2);
        assert_eq!(state.rksets[1].router_keys.len(), 2);
        assert!(state.rksets[0].router_keys[0].ski <= state.rksets[0].router_keys[1].ski);
        let payload_der =
            encode_router_key_state_payload_der(&state.rksets).expect("encode rk payload");
        assert!(crate::ccr::verify_state_hash(&state.hash, &payload_der));
    }

    #[test]
    fn build_router_key_state_empty_is_valid_and_hashes_empty_sequence() {
        let state = build_router_key_state(&[]).expect("empty router key state");
        assert!(state.rksets.is_empty());
        let payload_der =
            encode_router_key_state_payload_der(&state.rksets).expect("encode rk payload");
        assert!(crate::ccr::verify_state_hash(&state.hash, &payload_der));
    }

    #[test]
    fn build_roa_payload_state_groups_dedupes_and_sorts() {
        let vrps = vec![
            sample_vrp_v4(64497, [10, 1, 0, 0], 16, 16),
            sample_vrp_v4(64496, [10, 0, 0, 0], 8, 8),
            sample_vrp_v4(64496, [10, 0, 0, 0], 8, 8),
            sample_vrp_v4(64496, [10, 1, 0, 0], 16, 24),
            sample_vrp_v6(
                64496,
                [0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                32,
                48,
            ),
        ];
        let state = build_roa_payload_state(&vrps).expect("build roa state");
        assert_eq!(state.rps.len(), 2);
        assert_eq!(state.rps[0].as_id, 64496);
        assert_eq!(state.rps[1].as_id, 64497);
        assert_eq!(state.rps[0].ip_addr_blocks.len(), 2);
        let (afi4, entries4) = decode_family_block(&state.rps[0].ip_addr_blocks[0]);
        let (afi6, entries6) = decode_family_block(&state.rps[0].ip_addr_blocks[1]);
        assert_eq!(afi4, 1);
        assert_eq!(afi6, 2);
        assert_eq!(entries4.len(), 2);
        assert_eq!(entries4[0], (8, vec![10], None));
        assert_eq!(entries4[1], (16, vec![10, 1], Some(24)));
        assert_eq!(entries6.len(), 1);
        assert_eq!(entries6[0], (32, vec![0x20, 0x01, 0x0d, 0xb8], Some(48)));
        let payload_der = encode_roa_payload_state_payload_der(&state.rps).expect("encode payload");
        assert!(crate::ccr::verify_state_hash(&state.hash, &payload_der));
    }

    #[test]
    fn build_roa_payload_state_empty_is_valid_and_hashes_empty_sequence() {
        let state = build_roa_payload_state(&[]).expect("empty roa state");
        assert!(state.rps.is_empty());
        let payload_der = encode_roa_payload_state_payload_der(&state.rps).expect("encode payload");
        assert!(crate::ccr::verify_state_hash(&state.hash, &payload_der));
    }

    #[test]
    fn build_aspa_payload_state_merges_and_sorts() {
        let aspas = vec![
            AspaAttestation {
                customer_as_id: 64497,
                provider_as_ids: vec![65002],
            },
            AspaAttestation {
                customer_as_id: 64496,
                provider_as_ids: vec![65003, 65001],
            },
            AspaAttestation {
                customer_as_id: 64496,
                provider_as_ids: vec![65002, 65001],
            },
        ];
        let state = build_aspa_payload_state(&aspas).expect("build aspa state");
        assert_eq!(state.aps.len(), 2);
        assert_eq!(state.aps[0].customer_as_id, 64496);
        assert_eq!(state.aps[0].providers, vec![65001, 65002, 65003]);
        assert_eq!(state.aps[1].customer_as_id, 64497);
        let encoded = encode_aspa_payload_state(&state).expect("encode aspa state");
        let decoded = decode_content_info(
            &encode_content_info(&CcrContentInfo::new(RpkiCanonicalCacheRepresentation {
                version: 0,
                hash_alg: CcrDigestAlgorithm::Sha256,
                produced_at: time::OffsetDateTime::now_utc(),
                mfts: None,
                vrps: None,
                vaps: Some(state.clone()),
                tas: None,
                rks: None,
            }))
            .expect("encode ccr"),
        )
        .expect("decode ccr");
        assert_eq!(decoded.content.vaps, Some(state));
        assert!(!encoded.is_empty());
    }

    #[test]
    fn build_trust_anchor_state_collects_sorted_unique_skis() {
        let apnic = sample_trust_anchor(
            "tests/fixtures/tal/apnic-rfc7730-https.tal",
            "tests/fixtures/ta/apnic-ta.cer",
        );
        let arin = sample_trust_anchor(
            "tests/fixtures/tal/arin.tal",
            "tests/fixtures/ta/arin-ta.cer",
        );
        let state = build_trust_anchor_state(&[apnic.clone(), arin.clone(), apnic])
            .expect("build ta state");
        assert_eq!(state.skis.len(), 2);
        assert!(state.skis[0] < state.skis[1]);
        let payload_der =
            encode_trust_anchor_state_payload_der(&state.skis).expect("encode ta payload");
        assert!(crate::ccr::verify_state_hash(&state.hash, &payload_der));
        let encoded = encode_trust_anchor_state(&state).expect("encode ta state");
        assert!(!encoded.is_empty());
    }

    #[test]
    fn build_trust_anchor_state_rejects_empty_and_missing_ski() {
        let err = build_trust_anchor_state(&[]).expect_err("empty TAs must fail");
        assert!(err.to_string().contains("trust anchor set"), "{err}");

        let mut ta = sample_trust_anchor(
            "tests/fixtures/tal/apnic-rfc7730-https.tal",
            "tests/fixtures/ta/apnic-ta.cer",
        );
        ta.ta_certificate
            .rc_ca
            .tbs
            .extensions
            .subject_key_identifier = None;
        let err = build_trust_anchor_state(&[ta]).expect_err("missing ski must fail");
        assert!(err.to_string().contains("SubjectKeyIdentifier"), "{err}");
    }
}
