use serde::Serialize;
use sha2::Digest;
use std::collections::{BTreeMap, HashSet};
use std::path::{Path, PathBuf};

use rpki::data_model::aspa::AspaObject;
use rpki::data_model::crl::RpkixCrl;
use rpki::data_model::manifest::ManifestObject;
use rpki::data_model::rc::{
    AsIdentifierChoice, AsResourceSet, IpAddressChoice, IpResourceSet, ResourceCertificate,
};
use rpki::data_model::roa::RoaObject;

#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
#[serde(rename_all = "lowercase")]
enum ObjType {
    Cer,
    Crl,
    Manifest,
    Roa,
    Aspa,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
enum Part {
    PackManifest,
    PackCrl,
    StoredObject { index: u32 },
}

#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
struct CandidateRef {
    pack_index: u32,
    part: Part,
    size_bytes: u32,
}

#[derive(Clone, Debug, Serialize)]
struct SampleMeta {
    obj_type: ObjType,
    label: String,
    file_rel_path: String,
    size_bytes: u32,
    sha256_hex: String,

    // Provenance (relative to BENCH_STORE_DIR)
    pack_rel_path: String,
    pack_update_time_rfc3339_utc: String,
    manifest_uri: String,
    object_uri: String,

    // From StoredManifest (if available)
    manifest_this_update_rfc3339_utc: String,
    manifest_not_after_rfc3339_utc: String,

    metrics: Metrics,
}

#[derive(Clone, Debug, Serialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
enum Metrics {
    Cer {
        spki_len: u32,
        ext_count: u32,
        ip_resource_count: u32,
        as_resource_count: u32,
    },
    Crl {
        revoked_count: u32,
    },
    Manifest {
        file_count: u32,
    },
    Roa {
        addr_family_count: u32,
        prefix_count: u32,
        max_length_present: u32,
    },
    Aspa {
        provider_count: u32,
    },
}

#[derive(Clone, Debug, Serialize)]
struct SamplesManifest {
    created_at_rfc3339_utc: String,
    store_dir_hint: String,
    per_type: BTreeMap<String, u32>,
    samples: Vec<SampleMeta>,
}

fn env_string(name: &str) -> Option<String> {
    std::env::var(name).ok().filter(|s| !s.trim().is_empty())
}

fn env_u64_opt(name: &str) -> Option<u64> {
    std::env::var(name).ok().and_then(|s| s.parse::<u64>().ok())
}

fn env_bool(name: &str) -> bool {
    matches!(
        std::env::var(name).as_deref(),
        Ok("1") | Ok("true") | Ok("TRUE") | Ok("yes") | Ok("YES")
    )
}

fn create_parent_dirs(path: &Path) {
    if let Some(parent) = path.parent() {
        std::fs::create_dir_all(parent).unwrap_or_else(|e| {
            panic!("create_dir_all {}: {e}", parent.display());
        });
    }
}

fn walk_collect_files(root: &Path, out: &mut Vec<PathBuf>) -> std::io::Result<()> {
    for entry in std::fs::read_dir(root)? {
        let entry = entry?;
        let path = entry.path();
        let meta = entry.metadata()?;
        if meta.is_dir() {
            walk_collect_files(&path, out)?;
            continue;
        }
        if meta.is_file() {
            out.push(path);
        }
    }
    Ok(())
}

fn ext_from_uri_bytes(uri: &[u8]) -> Option<&[u8]> {
    let mut last_dot = None;
    for (i, &b) in uri.iter().enumerate() {
        if b == b'.' {
            last_dot = Some(i);
        }
    }
    let dot = last_dot?;
    if dot + 1 >= uri.len() {
        return None;
    }
    Some(&uri[dot + 1..])
}

fn ext_matches_ascii_case_insensitive(ext: &[u8], needle_lower: &[u8]) -> bool {
    if ext.len() != needle_lower.len() {
        return false;
    }
    ext.iter()
        .zip(needle_lower.iter())
        .all(|(&a, &b)| a.to_ascii_lowercase() == b)
}

fn percentile_index(len: usize, q: f64) -> usize {
    if len == 0 {
        return 0;
    }
    if len == 1 {
        return 0;
    }
    let q = q.clamp(0.0, 1.0);
    (((len - 1) as f64) * q).round() as usize
}

fn quantile_labels() -> Vec<(&'static str, f64)> {
    vec![
        ("min", 0.0),
        ("p01", 0.01),
        ("p10", 0.10),
        ("p25", 0.25),
        ("p50", 0.50),
        ("p75", 0.75),
        ("p90", 0.90),
        ("p95", 0.95),
        ("p99", 0.99),
        ("max", 1.0),
    ]
}

fn fmt_rfc3339_utc(t: time::OffsetDateTime) -> String {
    use time::format_description::well_known::Rfc3339;
    t.to_offset(time::UtcOffset::UTC)
        .format(&Rfc3339)
        .expect("format RFC3339")
}

fn scan_pack_candidates(
    pack_index: u32,
    bytes: &[u8],
    out_cer: &mut Vec<CandidateRef>,
    out_crl: &mut Vec<CandidateRef>,
    out_manifest: &mut Vec<CandidateRef>,
    out_roa: &mut Vec<CandidateRef>,
    out_aspa: &mut Vec<CandidateRef>,
) -> Result<(), String> {
    let mut cur = ByteCursor::new(bytes);

    let _version = cur.read_u8()?;
    cur.skip_string_u32()?; // manifest_uri
    cur.skip_option_string_u32()?; // rpki_notify

    let update_kind = cur.read_u8()?;
    cur.skip_i64()?; // update time
    if update_kind != 0 {
        // LastAttempt: no StoredManifest + objects
        return Ok(());
    }

    cur.skip_i64()?; // not_after
    cur.skip(20)?; // manifest_number
    cur.skip_i64()?; // this_update
    cur.skip_string_u32()?; // ca_repository

    let mft_len = cur.peek_u64_as_usize()?;
    cur.skip_bytes_u64()?; // manifest DER
    out_manifest.push(CandidateRef {
        pack_index,
        part: Part::PackManifest,
        size_bytes: mft_len as u32,
    });

    cur.skip_string_u32()?; // crl_uri
    let crl_len = cur.peek_u64_as_usize()?;
    cur.skip_bytes_u64()?;
    out_crl.push(CandidateRef {
        pack_index,
        part: Part::PackCrl,
        size_bytes: crl_len as u32,
    });

    let mut object_index = 0u32;
    while cur.remaining() > 0 {
        let uri_bytes = cur.read_bytes_u32()?;
        let ext = ext_from_uri_bytes(uri_bytes);

        let hash_type = cur.read_u8()?;
        if hash_type == 1 {
            cur.skip(32)?;
        } else if hash_type != 0 {
            return Err(format!("unsupported stored object hash_type {hash_type}"));
        }

        let content_len = cur.peek_u64_as_usize()?;
        cur.skip_bytes_u64()?;

        let cand = CandidateRef {
            pack_index,
            part: Part::StoredObject {
                index: object_index,
            },
            size_bytes: content_len as u32,
        };
        if let Some(ext) = ext {
            if ext_matches_ascii_case_insensitive(ext, b"cer") {
                out_cer.push(cand);
            } else if ext_matches_ascii_case_insensitive(ext, b"roa") {
                out_roa.push(cand);
            } else if ext_matches_ascii_case_insensitive(ext, b"asa") {
                out_aspa.push(cand);
            }
        }

        object_index += 1;
    }

    Ok(())
}

struct Extracted {
    pack_update_time_utc: time::OffsetDateTime,
    manifest_uri: String,
    manifest_this_update_utc: time::OffsetDateTime,
    manifest_not_after_utc: time::OffsetDateTime,
    object_uri: String,
    der: Vec<u8>,
}

fn extract_candidate(pack_path: &Path, cand: CandidateRef) -> Result<Extracted, String> {
    let bytes =
        std::fs::read(pack_path).map_err(|e| format!("read {}: {e}", pack_path.display()))?;
    let mut cur = ByteCursor::new(bytes.as_slice());

    let _version = cur.read_u8()?;
    let manifest_uri = cur.read_string_u32()?;
    cur.skip_option_string_u32()?; // rpki_notify

    let update_kind = cur.read_u8()?;
    let pack_update_time_utc = cur.read_time_utc_i64_be()?;
    if update_kind != 0 {
        return Err("pack is LastAttempt".to_string());
    }

    let manifest_not_after_utc = cur.read_time_utc_i64_be()?;
    cur.skip(20)?; // manifest_number
    let manifest_this_update_utc = cur.read_time_utc_i64_be()?;
    cur.skip_string_u32()?; // ca_repository

    let manifest_der = cur.read_bytes_u64_vec()?; // manifest DER

    if cand.part == Part::PackManifest {
        return Ok(Extracted {
            pack_update_time_utc,
            manifest_uri: manifest_uri.clone(),
            manifest_this_update_utc,
            manifest_not_after_utc,
            object_uri: manifest_uri,
            der: manifest_der,
        });
    }

    let crl_uri = cur.read_string_u32()?;
    let crl_der = cur.read_bytes_u64_vec()?;

    match cand.part {
        Part::PackManifest => unreachable!("handled above"),
        Part::PackCrl => Ok(Extracted {
            pack_update_time_utc,
            manifest_uri,
            manifest_this_update_utc,
            manifest_not_after_utc,
            object_uri: crl_uri,
            der: crl_der,
        }),
        Part::StoredObject { index } => {
            let mut object_index = 0u32;
            while cur.remaining() > 0 {
                let uri = cur.read_string_u32()?;
                let hash_type = cur.read_u8()?;
                if hash_type == 1 {
                    cur.skip(32)?;
                } else if hash_type != 0 {
                    return Err(format!("unsupported stored object hash_type {hash_type}"));
                }
                let content = cur.read_bytes_u64_vec()?;
                if object_index == index {
                    return Ok(Extracted {
                        pack_update_time_utc,
                        manifest_uri,
                        manifest_this_update_utc,
                        manifest_not_after_utc,
                        object_uri: uri,
                        der: content,
                    });
                }
                object_index += 1;
            }
            Err(format!("stored object index {index} out of range"))
        }
    }
}

fn ip_resource_count(set: &IpResourceSet) -> u32 {
    let mut n = 0u32;
    for fam in &set.families {
        match &fam.choice {
            IpAddressChoice::Inherit => n = n.saturating_add(1),
            IpAddressChoice::AddressesOrRanges(items) => n = n.saturating_add(items.len() as u32),
        }
    }
    n
}

fn as_choice_count(choice: &AsIdentifierChoice) -> u32 {
    match choice {
        AsIdentifierChoice::Inherit => 1,
        AsIdentifierChoice::AsIdsOrRanges(items) => items.len() as u32,
    }
}

fn as_resource_count(set: &AsResourceSet) -> u32 {
    let mut n = 0u32;
    if let Some(c) = set.asnum.as_ref() {
        n = n.saturating_add(as_choice_count(c));
    }
    if let Some(c) = set.rdi.as_ref() {
        n = n.saturating_add(as_choice_count(c));
    }
    n
}

fn metrics_for(obj_type: ObjType, der: &[u8]) -> Result<Metrics, String> {
    match obj_type {
        ObjType::Cer => {
            let parsed = ResourceCertificate::parse_der(der).map_err(|e| e.to_string())?;
            let spki_len = parsed.subject_public_key_info.len() as u32;
            let ext_count = (parsed.extensions.basic_constraints.len()
                + parsed.extensions.subject_key_identifier.len()
                + parsed.extensions.authority_key_identifier.len()
                + parsed.extensions.crl_distribution_points.len()
                + parsed.extensions.authority_info_access.len()
                + parsed.extensions.subject_info_access.len()
                + parsed.extensions.certificate_policies.len()
                + parsed.extensions.ip_resources.len()
                + parsed.extensions.as_resources.len()) as u32;
            let cert = parsed.validate_profile().map_err(|e| e.to_string())?;
            let ip_resource_count = cert
                .tbs
                .extensions
                .ip_resources
                .as_ref()
                .map(ip_resource_count)
                .unwrap_or(0);
            let as_resource_count = cert
                .tbs
                .extensions
                .as_resources
                .as_ref()
                .map(as_resource_count)
                .unwrap_or(0);
            Ok(Metrics::Cer {
                spki_len,
                ext_count,
                ip_resource_count,
                as_resource_count,
            })
        }
        ObjType::Crl => {
            let parsed = RpkixCrl::parse_der(der).map_err(|e| e.to_string())?;
            let crl = parsed.validate_profile().map_err(|e| e.to_string())?;
            Ok(Metrics::Crl {
                revoked_count: crl.revoked_certs.len() as u32,
            })
        }
        ObjType::Manifest => {
            let mft = ManifestObject::decode_der(der).map_err(|e| e.to_string())?;
            Ok(Metrics::Manifest {
                file_count: mft.manifest.file_count() as u32,
            })
        }
        ObjType::Roa => {
            let roa = RoaObject::decode_der(der).map_err(|e| e.to_string())?;
            let addr_family_count = roa.roa.ip_addr_blocks.len() as u32;
            let mut prefix_count = 0u32;
            let mut max_length_present = 0u32;
            for fam in &roa.roa.ip_addr_blocks {
                for a in &fam.addresses {
                    prefix_count += 1;
                    if a.max_length.is_some() {
                        max_length_present += 1;
                    }
                }
            }
            Ok(Metrics::Roa {
                addr_family_count,
                prefix_count,
                max_length_present,
            })
        }
        ObjType::Aspa => {
            let asa = AspaObject::decode_der(der).map_err(|e| e.to_string())?;
            Ok(Metrics::Aspa {
                provider_count: asa.aspa.provider_as_ids.len() as u32,
            })
        }
    }
}

fn write_sample(out_dir: &Path, obj_type: ObjType, label: &str, der: &[u8]) -> PathBuf {
    let (subdir, ext) = match obj_type {
        ObjType::Cer => ("cer", "cer"),
        ObjType::Crl => ("crl", "crl"),
        ObjType::Manifest => ("manifest", "mft"),
        ObjType::Roa => ("roa", "roa"),
        ObjType::Aspa => ("aspa", "asa"),
    };
    let path = out_dir.join(subdir).join(format!("{label}.{ext}"));
    create_parent_dirs(&path);
    std::fs::write(&path, der).unwrap_or_else(|e| panic!("write {}: {e}", path.display()));
    path
}

#[test]
#[ignore = "manual: collect representative CER/CRL/MFT/ROA/ASA DER fixtures from routinator stored/ SAP packs"]
fn stage2_collect_selected_der_v2_from_routinator_store() {
    let store_dir = env_string("BENCH_STORE_DIR")
        .map(PathBuf::from)
        .unwrap_or_else(|| panic!("set BENCH_STORE_DIR to routinator stored/ dir"));
    let out_dir = env_string("BENCH_OUT_DIR")
        .map(PathBuf::from)
        .unwrap_or_else(|| {
            PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/benchmark/selected_der_v2")
        });
    let max_packs = env_u64_opt("BENCH_MAX_PACKS");
    let clean = env_bool("BENCH_CLEAN");
    let dry_run = env_bool("BENCH_DRY_RUN");
    let verbose = env_bool("BENCH_VERBOSE");

    if clean && !dry_run && out_dir.exists() {
        std::fs::remove_dir_all(&out_dir)
            .unwrap_or_else(|e| panic!("remove_dir_all {}: {e}", out_dir.display()));
    }

    let mut pack_paths = Vec::new();
    walk_collect_files(&store_dir, &mut pack_paths)
        .unwrap_or_else(|e| panic!("walk {}: {e}", store_dir.display()));
    pack_paths.retain(|p| p.extension().and_then(|s| s.to_str()) == Some("mft"));
    pack_paths.sort();
    if let Some(max) = max_packs {
        pack_paths.truncate(max as usize);
    }

    println!("# Stage2 collect: selected_der_v2 (from routinator stored/ SAP packs)");
    println!();
    println!("- store_dir: {}", store_dir.display());
    println!("- out_dir: {}", out_dir.display());
    println!("- pack_files_found: {}", pack_paths.len());
    if let Some(max) = max_packs {
        println!("- max_packs: {} (truncating input list)", max);
    }
    println!("- clean: {}", clean);
    println!("- dry_run: {}", dry_run);
    println!();

    let mut cer = Vec::<CandidateRef>::new();
    let mut crl = Vec::<CandidateRef>::new();
    let mut mft = Vec::<CandidateRef>::new();
    let mut roa = Vec::<CandidateRef>::new();
    let mut asa = Vec::<CandidateRef>::new();

    for (i, p) in pack_paths.iter().enumerate() {
        let bytes = match std::fs::read(p) {
            Ok(v) => v,
            Err(_) => continue,
        };
        if let Err(e) = scan_pack_candidates(
            i as u32,
            bytes.as_slice(),
            &mut cer,
            &mut crl,
            &mut mft,
            &mut roa,
            &mut asa,
        ) {
            if verbose {
                eprintln!("# scan error {}: {}", p.display(), e);
            }
        }
    }

    let mut by_type: BTreeMap<ObjType, Vec<CandidateRef>> = BTreeMap::new();
    by_type.insert(ObjType::Cer, cer);
    by_type.insert(ObjType::Crl, crl);
    by_type.insert(ObjType::Manifest, mft);
    by_type.insert(ObjType::Roa, roa);
    by_type.insert(ObjType::Aspa, asa);

    let mut samples = Vec::<SampleMeta>::new();
    let mut per_type_out = BTreeMap::<String, u32>::new();

    for (obj_type, mut cands) in by_type {
        cands.sort_by(|a, b| {
            a.size_bytes
                .cmp(&b.size_bytes)
                .then_with(|| a.pack_index.cmp(&b.pack_index))
                .then_with(|| match (a.part, b.part) {
                    (Part::PackManifest, Part::PackManifest) => std::cmp::Ordering::Equal,
                    (Part::PackManifest, _) => std::cmp::Ordering::Less,
                    (_, Part::PackManifest) => std::cmp::Ordering::Greater,
                    (Part::PackCrl, Part::PackCrl) => std::cmp::Ordering::Equal,
                    (Part::PackCrl, Part::StoredObject { .. }) => std::cmp::Ordering::Less,
                    (Part::StoredObject { .. }, Part::PackCrl) => std::cmp::Ordering::Greater,
                    (Part::StoredObject { index: ai }, Part::StoredObject { index: bi }) => {
                        ai.cmp(&bi)
                    }
                })
        });

        println!("## {:?}", obj_type);
        println!();
        println!("- candidates: {}", cands.len());
        if cands.is_empty() {
            continue;
        }

        let mut selected_sha = HashSet::<String>::new();
        let mut selected_ptrs = HashSet::<(u32, Part)>::new();

        for (label, q) in quantile_labels() {
            let target = percentile_index(cands.len(), q);

            let mut found = None;
            for off in 0.. {
                let mut tried_any = false;
                if off == 0 {
                    let j = target as i32;
                    if j < 0 || (j as usize) >= cands.len() {
                        tried_any = false;
                    } else {
                        tried_any = true;
                        let cand = cands[j as usize];
                        if selected_ptrs.contains(&(cand.pack_index, cand.part)) {
                            continue;
                        }

                        let pack_path = &pack_paths[cand.pack_index as usize];
                        let extracted = match extract_candidate(pack_path.as_path(), cand) {
                            Ok(v) => v,
                            Err(_) => continue,
                        };

                        if extracted.der.len() != cand.size_bytes as usize {
                            continue;
                        }

                        let sha256 = sha2::Sha256::digest(extracted.der.as_slice());
                        let sha256_hex = hex::encode(sha256);
                        if selected_sha.contains(&sha256_hex) {
                            continue;
                        }

                        let metrics = match metrics_for(obj_type, extracted.der.as_slice()) {
                            Ok(m) => m,
                            Err(_) => continue,
                        };

                        if !dry_run {
                            let p = write_sample(
                                out_dir.as_path(),
                                obj_type,
                                label,
                                extracted.der.as_slice(),
                            );
                            if verbose {
                                println!(
                                    "- wrote {} -> {}",
                                    label,
                                    p.strip_prefix(PathBuf::from(env!("CARGO_MANIFEST_DIR")))
                                        .unwrap_or(&p)
                                        .display()
                                );
                            }
                        }

                        let pack_rel_path = pack_path
                            .strip_prefix(&store_dir)
                            .unwrap_or(pack_path.as_path())
                            .display()
                            .to_string();

                        let file_rel_path = format!(
                            "tests/benchmark/selected_der_v2/{}/{}.{}",
                            match obj_type {
                                ObjType::Cer => "cer",
                                ObjType::Crl => "crl",
                                ObjType::Manifest => "manifest",
                                ObjType::Roa => "roa",
                                ObjType::Aspa => "aspa",
                            },
                            label,
                            match obj_type {
                                ObjType::Cer => "cer",
                                ObjType::Crl => "crl",
                                ObjType::Manifest => "mft",
                                ObjType::Roa => "roa",
                                ObjType::Aspa => "asa",
                            }
                        );

                        let sha256_hex_for_set = sha256_hex.clone();
                        samples.push(SampleMeta {
                            obj_type,
                            label: label.to_string(),
                            file_rel_path,
                            size_bytes: cand.size_bytes,
                            sha256_hex,
                            pack_rel_path,
                            pack_update_time_rfc3339_utc: fmt_rfc3339_utc(
                                extracted.pack_update_time_utc,
                            ),
                            manifest_uri: extracted.manifest_uri,
                            object_uri: extracted.object_uri,
                            manifest_this_update_rfc3339_utc: fmt_rfc3339_utc(
                                extracted.manifest_this_update_utc,
                            ),
                            manifest_not_after_rfc3339_utc: fmt_rfc3339_utc(
                                extracted.manifest_not_after_utc,
                            ),
                            metrics,
                        });

                        selected_ptrs.insert((cand.pack_index, cand.part));
                        selected_sha.insert(sha256_hex_for_set);
                        found = Some(());
                        break;
                    }
                } else {
                    for delta in [off as i32, -(off as i32)] {
                        let j = target as i32 + delta;
                        if j < 0 || (j as usize) >= cands.len() {
                            continue;
                        }
                        tried_any = true;
                        let cand = cands[j as usize];
                        if selected_ptrs.contains(&(cand.pack_index, cand.part)) {
                            continue;
                        }

                        let pack_path = &pack_paths[cand.pack_index as usize];
                        let extracted = match extract_candidate(pack_path.as_path(), cand) {
                            Ok(v) => v,
                            Err(_) => continue,
                        };

                        if extracted.der.len() != cand.size_bytes as usize {
                            continue;
                        }

                        let sha256 = sha2::Sha256::digest(extracted.der.as_slice());
                        let sha256_hex = hex::encode(sha256);
                        if selected_sha.contains(&sha256_hex) {
                            continue;
                        }

                        let metrics = match metrics_for(obj_type, extracted.der.as_slice()) {
                            Ok(m) => m,
                            Err(_) => continue,
                        };

                        if !dry_run {
                            let p = write_sample(
                                out_dir.as_path(),
                                obj_type,
                                label,
                                extracted.der.as_slice(),
                            );
                            if verbose {
                                println!(
                                    "- wrote {} -> {}",
                                    label,
                                    p.strip_prefix(PathBuf::from(env!("CARGO_MANIFEST_DIR")))
                                        .unwrap_or(&p)
                                        .display()
                                );
                            }
                        }

                        let pack_rel_path = pack_path
                            .strip_prefix(&store_dir)
                            .unwrap_or(pack_path.as_path())
                            .display()
                            .to_string();

                        let file_rel_path = format!(
                            "tests/benchmark/selected_der_v2/{}/{}.{}",
                            match obj_type {
                                ObjType::Cer => "cer",
                                ObjType::Crl => "crl",
                                ObjType::Manifest => "manifest",
                                ObjType::Roa => "roa",
                                ObjType::Aspa => "aspa",
                            },
                            label,
                            match obj_type {
                                ObjType::Cer => "cer",
                                ObjType::Crl => "crl",
                                ObjType::Manifest => "mft",
                                ObjType::Roa => "roa",
                                ObjType::Aspa => "asa",
                            }
                        );

                        let sha256_hex_for_set = sha256_hex.clone();
                        samples.push(SampleMeta {
                            obj_type,
                            label: label.to_string(),
                            file_rel_path,
                            size_bytes: cand.size_bytes,
                            sha256_hex,
                            pack_rel_path,
                            pack_update_time_rfc3339_utc: fmt_rfc3339_utc(
                                extracted.pack_update_time_utc,
                            ),
                            manifest_uri: extracted.manifest_uri,
                            object_uri: extracted.object_uri,
                            manifest_this_update_rfc3339_utc: fmt_rfc3339_utc(
                                extracted.manifest_this_update_utc,
                            ),
                            manifest_not_after_rfc3339_utc: fmt_rfc3339_utc(
                                extracted.manifest_not_after_utc,
                            ),
                            metrics,
                        });

                        selected_ptrs.insert((cand.pack_index, cand.part));
                        selected_sha.insert(sha256_hex_for_set);
                        found = Some(());
                        break;
                    }
                }
                if found.is_some() {
                    break;
                }
                if !tried_any {
                    break;
                }
            }
        }

        per_type_out.insert(
            format!("{:?}", obj_type).to_lowercase(),
            selected_sha.len() as u32,
        );
        println!();
    }

    samples.sort_by(|a, b| {
        a.obj_type
            .cmp(&b.obj_type)
            .then_with(|| a.label.cmp(&b.label))
    });

    let created_at_rfc3339_utc = fmt_rfc3339_utc(time::OffsetDateTime::now_utc());
    let manifest = SamplesManifest {
        created_at_rfc3339_utc,
        store_dir_hint: store_dir.display().to_string(),
        per_type: per_type_out,
        samples,
    };

    if !dry_run {
        let meta_path = out_dir.join("meta").join("samples.json");
        create_parent_dirs(&meta_path);
        let bytes = serde_json::to_vec_pretty(&manifest).expect("encode samples manifest");
        std::fs::write(&meta_path, bytes)
            .unwrap_or_else(|e| panic!("write {}: {e}", meta_path.display()));
        println!("- wrote meta: {}", meta_path.display());
    }
}

struct ByteCursor<'a> {
    bytes: &'a [u8],
    pos: usize,
}

impl<'a> ByteCursor<'a> {
    fn new(bytes: &'a [u8]) -> Self {
        Self { bytes, pos: 0 }
    }

    fn remaining(&self) -> usize {
        self.bytes.len().saturating_sub(self.pos)
    }

    fn skip(&mut self, len: usize) -> Result<(), String> {
        if len > self.remaining() {
            return Err("truncated input".to_string());
        }
        self.pos += len;
        Ok(())
    }

    fn read_exact(&mut self, len: usize) -> Result<&'a [u8], String> {
        if len > self.remaining() {
            return Err(format!(
                "truncated input: need {len} bytes, have {}",
                self.remaining()
            ));
        }
        let start = self.pos;
        self.pos += len;
        Ok(&self.bytes[start..start + len])
    }

    fn read_u8(&mut self) -> Result<u8, String> {
        Ok(self.read_exact(1)?[0])
    }

    fn read_u32_be(&mut self) -> Result<u32, String> {
        let b = self.read_exact(4)?;
        Ok(u32::from_be_bytes([b[0], b[1], b[2], b[3]]))
    }

    fn read_u64_be(&mut self) -> Result<u64, String> {
        let b = self.read_exact(8)?;
        Ok(u64::from_be_bytes([
            b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7],
        ]))
    }

    fn peek_u64_as_usize(&mut self) -> Result<usize, String> {
        let start = self.pos;
        let v = self.read_u64_be()?;
        self.pos = start;
        usize::try_from(v).map_err(|_| format!("length too large: {v}"))
    }

    fn skip_i64(&mut self) -> Result<(), String> {
        self.skip(8)
    }

    fn read_i64_be(&mut self) -> Result<i64, String> {
        let b = self.read_exact(8)?;
        Ok(i64::from_be_bytes([
            b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7],
        ]))
    }

    fn read_time_utc_i64_be(&mut self) -> Result<time::OffsetDateTime, String> {
        let ts = self.read_i64_be()?;
        time::OffsetDateTime::from_unix_timestamp(ts)
            .map_err(|e| format!("invalid unix timestamp {ts}: {e}"))
    }

    fn read_bytes_u32(&mut self) -> Result<&'a [u8], String> {
        let len = self.read_u32_be()? as usize;
        self.read_exact(len)
    }

    fn skip_string_u32(&mut self) -> Result<(), String> {
        let len = self.read_u32_be()? as usize;
        self.skip(len)
    }

    fn read_string_u32(&mut self) -> Result<String, String> {
        let b = self.read_bytes_u32()?;
        std::str::from_utf8(b)
            .map(|s| s.to_string())
            .map_err(|e| format!("invalid UTF-8 string: {e}"))
    }

    fn skip_option_string_u32(&mut self) -> Result<(), String> {
        let len = self.read_u32_be()? as usize;
        if len == 0 {
            return Ok(());
        }
        self.skip(len)
    }

    fn skip_bytes_u64(&mut self) -> Result<(), String> {
        let len_u64 = self.read_u64_be()?;
        let len = usize::try_from(len_u64)
            .map_err(|_| format!("data block too large for this system: {len_u64}"))?;
        self.skip(len)
    }

    fn read_bytes_u64_vec(&mut self) -> Result<Vec<u8>, String> {
        let len_u64 = self.read_u64_be()?;
        let len = usize::try_from(len_u64)
            .map_err(|_| format!("data block too large for this system: {len_u64}"))?;
        Ok(self.read_exact(len)?.to_vec())
    }
}
