use crate::current_repo_index::CurrentRepoIndexHandle;
use crate::data_model::manifest::{ManifestDecodeError, ManifestObject, ManifestValidateError};
use crate::data_model::signed_object::SignedObjectVerifyError;
use crate::policy::{CaFailedFetchPolicy, Policy};
use crate::report::{RfcRef, Warning};
use crate::storage::{PackFile, PackTime, RocksStore, StorageError, VcirArtifactRole};
use crate::validation::cert_path::{CertPathError, validate_signed_object_ee_cert_path_fast};
use crate::validation::publication_point::PublicationPointSnapshot;
use sha2::Digest;
use std::cmp::Ordering;
use std::collections::HashSet;
use x509_parser::prelude::FromDer;

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PublicationPointSource {
    Fresh,
    PublicationPointCache,
    VcirCurrentInstance,
    FailedFetchNoCache,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PublicationPointResult {
    pub source: PublicationPointSource,
    pub snapshot: PublicationPointSnapshot,
    pub warnings: Vec<Warning>,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct FreshValidatedPublicationPoint {
    pub manifest_rsync_uri: String,
    pub publication_point_rsync_uri: String,
    pub manifest_number_be: Vec<u8>,
    pub this_update: PackTime,
    pub next_update: PackTime,
    pub verified_at: PackTime,
    pub manifest_bytes: Vec<u8>,
    pub files: Vec<PackFile>,
}

pub trait PublicationPointData {
    fn manifest_rsync_uri(&self) -> &str;
    fn publication_point_rsync_uri(&self) -> &str;
    fn manifest_number_be(&self) -> &[u8];
    fn this_update(&self) -> &PackTime;
    fn next_update(&self) -> &PackTime;
    fn verified_at(&self) -> &PackTime;
    fn manifest_bytes(&self) -> &[u8];
    fn files(&self) -> &[PackFile];
}

impl FreshValidatedPublicationPoint {
    pub fn to_publication_point_snapshot(&self) -> PublicationPointSnapshot {
        PublicationPointSnapshot {
            format_version: PublicationPointSnapshot::FORMAT_VERSION_V1,
            manifest_rsync_uri: self.manifest_rsync_uri.clone(),
            publication_point_rsync_uri: self.publication_point_rsync_uri.clone(),
            manifest_number_be: self.manifest_number_be.clone(),
            this_update: self.this_update.clone(),
            next_update: self.next_update.clone(),
            verified_at: self.verified_at.clone(),
            manifest_bytes: self.manifest_bytes.clone(),
            files: self.files.clone(),
        }
    }
}

impl PublicationPointData for FreshValidatedPublicationPoint {
    fn manifest_rsync_uri(&self) -> &str {
        &self.manifest_rsync_uri
    }

    fn publication_point_rsync_uri(&self) -> &str {
        &self.publication_point_rsync_uri
    }

    fn manifest_number_be(&self) -> &[u8] {
        self.manifest_number_be.as_slice()
    }

    fn this_update(&self) -> &PackTime {
        &self.this_update
    }

    fn next_update(&self) -> &PackTime {
        &self.next_update
    }

    fn verified_at(&self) -> &PackTime {
        &self.verified_at
    }

    fn manifest_bytes(&self) -> &[u8] {
        self.manifest_bytes.as_slice()
    }

    fn files(&self) -> &[PackFile] {
        self.files.as_slice()
    }
}

impl PublicationPointData for PublicationPointSnapshot {
    fn manifest_rsync_uri(&self) -> &str {
        &self.manifest_rsync_uri
    }

    fn publication_point_rsync_uri(&self) -> &str {
        &self.publication_point_rsync_uri
    }

    fn manifest_number_be(&self) -> &[u8] {
        self.manifest_number_be.as_slice()
    }

    fn this_update(&self) -> &PackTime {
        &self.this_update
    }

    fn next_update(&self) -> &PackTime {
        &self.next_update
    }

    fn verified_at(&self) -> &PackTime {
        &self.verified_at
    }

    fn manifest_bytes(&self) -> &[u8] {
        self.manifest_bytes.as_slice()
    }

    fn files(&self) -> &[PackFile] {
        self.files.as_slice()
    }
}

#[derive(Debug, thiserror::Error)]
pub enum ManifestFreshError {
    #[error(
        "verification-only fallback replay selected current-instance VCIR: {manifest_rsync_uri}"
    )]
    VerificationOnlyForcedCurrentInstanceVcir { manifest_rsync_uri: String },

    #[error("repo sync failed: {detail} (RFC 8182 §3.4.5; RFC 9286 §6.6)")]
    RepoSyncFailed { detail: String },

    #[error(
        "manifest not found in current repository view: {manifest_rsync_uri} (RFC 9286 §6.2; RFC 9286 §6.6)"
    )]
    MissingManifest { manifest_rsync_uri: String },

    #[error("manifest decode failed: {0} (RFC 9286 §4; RFC 9286 §6.2; RFC 9286 §6.6)")]
    Decode(#[from] ManifestDecodeError),

    #[error(
        "manifest embedded EE certificate resources invalid: {0} (RFC 9286 §5.1; RFC 9286 §6.2; RFC 9286 §6.6)"
    )]
    EeResources(#[from] ManifestValidateError),

    #[error(
        "manifest CMS signature verification failed: {0} (RFC 6488 §3; RFC 9589 §4; RFC 9286 §6.2; RFC 9286 §6.6)"
    )]
    Signature(#[from] SignedObjectVerifyError),

    #[error(
        "manifest embedded EE certificate path validation failed: {0} (RFC 6488 §3; RFC 9286 §6.2; RFC 9286 §6.6)"
    )]
    EeCertPath(#[from] CertPathError),

    #[error(
        "manifest embedded EE certificate CRLDistributionPoints missing (cannot validate EE certificate) (RFC 6487 §4.8.6; RFC 6488 §3; RFC 9286 §6.2; RFC 9286 §6.6)"
    )]
    EeCrlDpMissing,

    #[error(
        "publication point contains no CRL files (cannot validate manifest EE certificate) (RFC 9286 §7; RFC 6487 §4.8.6; RFC 6488 §3; RFC 9286 §6.2; RFC 9286 §6.6)"
    )]
    NoCrlFiles,

    #[error(
        "CRL referenced by manifest embedded EE certificate CRLDistributionPoints not found at publication point: {0} (RFC 6487 §4.8.6; RFC 9286 §4.2.1; RFC 9286 §6.2; RFC 9286 §6.6)"
    )]
    EeCrlNotFound(String),

    #[error(
        "manifest is not valid at validation_time: this_update={this_update_rfc3339_utc} next_update={next_update_rfc3339_utc} validation_time={validation_time_rfc3339_utc} (RFC 9286 §6.3; RFC 9286 §6.6)"
    )]
    StaleOrEarly {
        this_update_rfc3339_utc: String,
        next_update_rfc3339_utc: String,
        validation_time_rfc3339_utc: String,
    },

    #[error(
        "manifest must reside at the same publication point as id-ad-caRepository: manifest={manifest_rsync_uri} publication_point={publication_point_rsync_uri} (RFC 9286 §6.1; RFC 9286 §6.6)"
    )]
    ManifestOutsidePublicationPoint {
        manifest_rsync_uri: String,
        publication_point_rsync_uri: String,
    },

    #[error(
        "manifestNumber not higher than previously validated manifest: old={old_hex} new={new_hex} (RFC 9286 §4.2.1; RFC 9286 §6.6)"
    )]
    ManifestNumberNotIncreasing { old_hex: String, new_hex: String },

    #[error(
        "thisUpdate not more recent than previously validated manifest: old={old_rfc3339_utc} new={new_rfc3339_utc} (RFC 9286 §4.2.1; RFC 9286 §6.6)"
    )]
    ThisUpdateNotIncreasing {
        old_rfc3339_utc: String,
        new_rfc3339_utc: String,
    },

    #[error(
        "manifest referenced file missing in current repository view: {rsync_uri} (RFC 9286 §6.4; RFC 9286 §6.6)"
    )]
    MissingFile { rsync_uri: String },

    #[error("manifest file hash mismatch: {rsync_uri} (RFC 9286 §6.5; RFC 9286 §6.6)")]
    HashMismatch { rsync_uri: String },

    #[error("issuer CA certificate bytes unavailable: {detail} (RFC 6487 §4; RFC 9286 §6.2)")]
    IssuerCaLoadFailed { detail: String },
}

impl ManifestFreshError {
    pub(crate) fn should_warn_when_current_instance_reused(&self) -> bool {
        !matches!(
            self,
            ManifestFreshError::VerificationOnlyForcedCurrentInstanceVcir { .. }
                | ManifestFreshError::RepoSyncFailed { .. }
                | ManifestFreshError::MissingManifest { .. }
                | ManifestFreshError::MissingFile { .. }
        )
    }
}

#[derive(Debug, thiserror::Error)]
pub enum ManifestReuseError {
    #[error("latest current-instance VCIR missing: {0} (RFC 9286 §6.6)")]
    MissingCurrentInstanceVcir(String),

    #[error(
        "latest current-instance VCIR is not marked failed-fetch eligible: {0} (RFC 9286 §6.6)"
    )]
    IneligibleCurrentInstanceVcir(String),

    #[error(
        "latest current-instance VCIR instance_gate expired: manifest={manifest_rsync_uri} effective_until={effective_until_rfc3339_utc} validation_time={validation_time_rfc3339_utc} (RFC 9286 §6.6)"
    )]
    CurrentInstanceVcirExpired {
        manifest_rsync_uri: String,
        effective_until_rfc3339_utc: String,
        validation_time_rfc3339_utc: String,
    },

    #[error(
        "current-instance VCIR current_manifest_rsync_uri does not match requested manifest URI: expected={expected} actual={actual}"
    )]
    ManifestUriMismatch { expected: String, actual: String },

    #[error("manifest raw bytes missing for current-instance VCIR reconstruction: {0}")]
    MissingManifestRaw(String),

    #[error("artifact raw bytes missing for current-instance VCIR reconstruction: {rsync_uri}")]
    MissingArtifactRaw { rsync_uri: String },

    #[error("invalid current-instance VCIR: {0}")]
    InvalidCurrentInstanceVcir(String),

    #[error("storage error during current-instance VCIR reuse: {0}")]
    Storage(#[from] StorageError),
}

#[derive(Debug, thiserror::Error)]
pub enum ManifestProcessError {
    #[error("manifest processing failed and cache use is disabled: {0}")]
    StopAllOutput(#[from] ManifestFreshError),

    #[error(
        "manifest processing failed and no reusable current-instance validated result is available: fresh={fresh}; reused={reused}"
    )]
    NoUsableCache {
        fresh: ManifestFreshError,
        reused: ManifestReuseError,
    },

    #[error("storage error during manifest processing: {0}")]
    Storage(#[from] StorageError),
}

pub fn process_manifest_publication_point(
    store: &RocksStore,
    policy: &Policy,
    manifest_rsync_uri: &str,
    publication_point_rsync_uri: &str,
    issuer_ca_der: &[u8],
    issuer_ca_rsync_uri: Option<&str>,
    validation_time: time::OffsetDateTime,
) -> Result<PublicationPointResult, ManifestProcessError> {
    process_manifest_publication_point_after_repo_sync(
        store,
        policy,
        manifest_rsync_uri,
        publication_point_rsync_uri,
        issuer_ca_der,
        issuer_ca_rsync_uri,
        validation_time,
        true,
        None,
    )
}

pub fn process_manifest_publication_point_fresh_after_repo_sync(
    store: &RocksStore,
    manifest_rsync_uri: &str,
    publication_point_rsync_uri: &str,
    issuer_ca_der: &[u8],
    issuer_ca_rsync_uri: Option<&str>,
    validation_time: time::OffsetDateTime,
    repo_sync_ok: bool,
    repo_sync_error: Option<&str>,
) -> Result<FreshValidatedPublicationPoint, ManifestFreshError> {
    process_manifest_publication_point_fresh_after_repo_sync_with_timing(
        store,
        manifest_rsync_uri,
        publication_point_rsync_uri,
        None,
        issuer_ca_der,
        issuer_ca_rsync_uri,
        validation_time,
        repo_sync_ok,
        repo_sync_error,
    )
    .map(|(fresh, _timing)| fresh)
}

#[derive(Clone, Debug, Default)]
pub struct FreshPublicationPointTimingBreakdown {
    pub current_index_lock_ms: u64,
    pub manifest_load_ms: u64,
    pub manifest_index_lookup_ms: u64,
    pub manifest_blob_load_ms: u64,
    pub manifest_decode_ms: u64,
    pub replay_guard_ms: u64,
    pub replay_meta_hit: bool,
    pub replay_meta_miss: bool,
    pub manifest_entries_ms: u64,
    pub pack_files_ms: u64,
    pub pack_files_index_lookup_ms: u64,
    pub pack_files_blob_load_ms: u64,
    pub ee_path_validate_ms: u64,
    pub manifest_file_count: usize,
}

pub fn process_manifest_publication_point_fresh_after_repo_sync_with_timing(
    store: &RocksStore,
    manifest_rsync_uri: &str,
    publication_point_rsync_uri: &str,
    current_repo_index: Option<&CurrentRepoIndexHandle>,
    issuer_ca_der: &[u8],
    issuer_ca_rsync_uri: Option<&str>,
    validation_time: time::OffsetDateTime,
    repo_sync_ok: bool,
    repo_sync_error: Option<&str>,
) -> Result<
    (
        FreshValidatedPublicationPoint,
        FreshPublicationPointTimingBreakdown,
    ),
    ManifestFreshError,
> {
    if repo_sync_ok {
        try_build_fresh_publication_point_with_timing(
            store,
            manifest_rsync_uri,
            publication_point_rsync_uri,
            current_repo_index,
            issuer_ca_der,
            issuer_ca_rsync_uri,
            validation_time,
        )
    } else {
        Err(ManifestFreshError::RepoSyncFailed {
            detail: repo_sync_error.unwrap_or("repo sync failed").to_string(),
        })
    }
}

pub fn process_manifest_publication_point_after_repo_sync(
    store: &RocksStore,
    policy: &Policy,
    manifest_rsync_uri: &str,
    publication_point_rsync_uri: &str,
    issuer_ca_der: &[u8],
    issuer_ca_rsync_uri: Option<&str>,
    validation_time: time::OffsetDateTime,
    repo_sync_ok: bool,
    repo_sync_error: Option<&str>,
) -> Result<PublicationPointResult, ManifestProcessError> {
    let fresh = if repo_sync_ok {
        try_build_fresh_publication_point(
            store,
            manifest_rsync_uri,
            publication_point_rsync_uri,
            issuer_ca_der,
            issuer_ca_rsync_uri,
            validation_time,
        )
    } else {
        Err(ManifestFreshError::RepoSyncFailed {
            detail: repo_sync_error.unwrap_or("repo sync failed").to_string(),
        })
    };

    match fresh {
        Ok(fresh_point) => {
            let snapshot = fresh_point.to_publication_point_snapshot();
            Ok(PublicationPointResult {
                source: PublicationPointSource::Fresh,
                snapshot,
                warnings: Vec::new(),
            })
        }
        Err(fresh_err) => match policy.ca_failed_fetch_policy {
            CaFailedFetchPolicy::StopAllOutput => {
                Err(ManifestProcessError::StopAllOutput(fresh_err))
            }
            CaFailedFetchPolicy::ReuseCurrentInstanceVcir => {
                match load_current_instance_vcir_publication_point(
                    store,
                    manifest_rsync_uri,
                    publication_point_rsync_uri,
                    validation_time,
                ) {
                    Ok(snapshot) => {
                        let mut warnings = Vec::new();
                        if fresh_err.should_warn_when_current_instance_reused() {
                            warnings.push(
                                Warning::new(format!("manifest failed fetch: {fresh_err}"))
                                    .with_rfc_refs(&[RfcRef("RFC 9286 §6.6")])
                                    .with_context(manifest_rsync_uri),
                            );
                        }
                        Ok(PublicationPointResult {
                            source: PublicationPointSource::VcirCurrentInstance,
                            snapshot,
                            warnings,
                        })
                    }
                    Err(reused) => Err(ManifestProcessError::NoUsableCache {
                        fresh: fresh_err,
                        reused,
                    }),
                }
            }
        },
    }
}

pub fn load_current_instance_vcir_publication_point(
    store: &RocksStore,
    manifest_rsync_uri: &str,
    publication_point_rsync_uri: &str,
    validation_time: time::OffsetDateTime,
) -> Result<PublicationPointSnapshot, ManifestReuseError> {
    let vcir = store.get_vcir(manifest_rsync_uri)?.ok_or_else(|| {
        ManifestReuseError::MissingCurrentInstanceVcir(manifest_rsync_uri.to_string())
    })?;

    if vcir.current_manifest_rsync_uri != manifest_rsync_uri {
        return Err(ManifestReuseError::ManifestUriMismatch {
            expected: manifest_rsync_uri.to_string(),
            actual: vcir.current_manifest_rsync_uri.clone(),
        });
    }

    if !vcir.audit_summary.failed_fetch_eligible {
        return Err(ManifestReuseError::IneligibleCurrentInstanceVcir(
            manifest_rsync_uri.to_string(),
        ));
    }

    let instance_effective_until = vcir
        .instance_gate
        .instance_effective_until
        .parse()
        .map_err(|e| {
            ManifestReuseError::InvalidCurrentInstanceVcir(format!(
                "instance_gate.instance_effective_until parse failed: {e}"
            ))
        })?;
    if validation_time > instance_effective_until {
        use time::format_description::well_known::Rfc3339;
        return Err(ManifestReuseError::CurrentInstanceVcirExpired {
            manifest_rsync_uri: manifest_rsync_uri.to_string(),
            effective_until_rfc3339_utc: instance_effective_until
                .to_offset(time::UtcOffset::UTC)
                .format(&Rfc3339)
                .expect("format VCIR instance_effective_until"),
            validation_time_rfc3339_utc: validation_time
                .to_offset(time::UtcOffset::UTC)
                .format(&Rfc3339)
                .expect("format validation_time"),
        });
    }

    let manifest_artifact = vcir
        .related_artifacts
        .iter()
        .find(|artifact| {
            artifact.artifact_role == VcirArtifactRole::Manifest
                && artifact.uri.as_deref() == Some(manifest_rsync_uri)
        })
        .ok_or_else(|| {
            ManifestReuseError::InvalidCurrentInstanceVcir(
                "missing manifest artifact matching manifest_rsync_uri".to_string(),
            )
        })?;

    let manifest_bytes = store
        .get_blob_bytes(&manifest_artifact.sha256)?
        .ok_or_else(|| ManifestReuseError::MissingManifestRaw(manifest_artifact.sha256.clone()))?;

    let mut seen = HashSet::new();
    let mut files = Vec::new();
    for artifact in &vcir.related_artifacts {
        let Some(uri) = artifact.uri.as_ref() else {
            continue;
        };
        if artifact.artifact_role == VcirArtifactRole::Manifest
            || artifact.artifact_role == VcirArtifactRole::IssuerCert
            || artifact.artifact_role == VcirArtifactRole::Tal
            || artifact.artifact_role == VcirArtifactRole::TrustAnchorCert
        {
            continue;
        }
        if !seen.insert(uri.clone()) {
            continue;
        }
        let entry_bytes = store.get_blob_bytes(&artifact.sha256)?.ok_or_else(|| {
            ManifestReuseError::MissingArtifactRaw {
                rsync_uri: uri.clone(),
            }
        })?;
        files.push(PackFile::from_bytes_compute_sha256(uri, entry_bytes));
    }

    Ok(PublicationPointSnapshot {
        format_version: PublicationPointSnapshot::FORMAT_VERSION_V1,
        manifest_rsync_uri: manifest_rsync_uri.to_string(),
        publication_point_rsync_uri: publication_point_rsync_uri.to_string(),
        manifest_number_be: vcir
            .validated_manifest_meta
            .validated_manifest_number
            .clone(),
        this_update: vcir
            .validated_manifest_meta
            .validated_manifest_this_update
            .clone(),
        next_update: vcir
            .validated_manifest_meta
            .validated_manifest_next_update
            .clone(),
        verified_at: vcir.last_successful_validation_time.clone(),
        manifest_bytes,
        files,
    })
}

fn decode_and_validate_manifest_with_current_time(
    manifest_bytes: &[u8],
    validation_time: time::OffsetDateTime,
) -> Result<ManifestObject, ManifestFreshError> {
    let manifest = ManifestObject::decode_der(manifest_bytes)?;
    manifest.validate_embedded_ee_cert()?;
    manifest.signed_object.verify()?;

    let this_update = manifest
        .manifest
        .this_update
        .to_offset(time::UtcOffset::UTC);
    let next_update = manifest
        .manifest
        .next_update
        .to_offset(time::UtcOffset::UTC);
    let now = validation_time.to_offset(time::UtcOffset::UTC);
    if now < this_update || now > next_update {
        return Err(ManifestFreshError::StaleOrEarly {
            this_update_rfc3339_utc: this_update
                .format(&time::format_description::well_known::Rfc3339)
                .expect("format thisUpdate"),
            next_update_rfc3339_utc: next_update
                .format(&time::format_description::well_known::Rfc3339)
                .expect("format nextUpdate"),
            validation_time_rfc3339_utc: now
                .format(&time::format_description::well_known::Rfc3339)
                .expect("format validation_time"),
        });
    }

    Ok(manifest)
}

pub(crate) fn try_build_fresh_publication_point(
    store: &RocksStore,
    manifest_rsync_uri: &str,
    publication_point_rsync_uri: &str,
    issuer_ca_der: &[u8],
    issuer_ca_rsync_uri: Option<&str>,
    validation_time: time::OffsetDateTime,
) -> Result<FreshValidatedPublicationPoint, ManifestFreshError> {
    try_build_fresh_publication_point_with_timing(
        store,
        manifest_rsync_uri,
        publication_point_rsync_uri,
        None,
        issuer_ca_der,
        issuer_ca_rsync_uri,
        validation_time,
    )
    .map(|(fresh, _timing)| fresh)
}

pub(crate) fn try_build_fresh_publication_point_with_timing(
    store: &RocksStore,
    manifest_rsync_uri: &str,
    publication_point_rsync_uri: &str,
    current_repo_index: Option<&CurrentRepoIndexHandle>,
    issuer_ca_der: &[u8],
    issuer_ca_rsync_uri: Option<&str>,
    validation_time: time::OffsetDateTime,
) -> Result<
    (
        FreshValidatedPublicationPoint,
        FreshPublicationPointTimingBreakdown,
    ),
    ManifestFreshError,
> {
    let mut timing = FreshPublicationPointTimingBreakdown::default();
    let current_index_lock_started = std::time::Instant::now();
    let current_index_guard = current_repo_index.and_then(|handle| handle.read().ok());
    timing.current_index_lock_ms = current_index_lock_started.elapsed().as_millis() as u64;

    if !rsync_uri_is_under_publication_point(manifest_rsync_uri, publication_point_rsync_uri) {
        return Err(ManifestFreshError::ManifestOutsidePublicationPoint {
            manifest_rsync_uri: manifest_rsync_uri.to_string(),
            publication_point_rsync_uri: publication_point_rsync_uri.to_string(),
        });
    }

    let manifest_load_started = std::time::Instant::now();
    let manifest_bytes = if let Some(index) = current_index_guard.as_ref() {
        let manifest_lookup_started = std::time::Instant::now();
        let current = index.get_by_uri(manifest_rsync_uri).ok_or_else(|| {
            ManifestFreshError::MissingManifest {
                manifest_rsync_uri: manifest_rsync_uri.to_string(),
            }
        })?;
        timing.manifest_index_lookup_ms = manifest_lookup_started.elapsed().as_millis() as u64;
        let manifest_blob_load_started = std::time::Instant::now();
        store
            .get_blob_bytes(&current.current_hash_hex)
            .map_err(|e| ManifestFreshError::MissingManifest {
                manifest_rsync_uri: format!("{manifest_rsync_uri} ({e})"),
            })?
            .ok_or_else(|| ManifestFreshError::MissingManifest {
                manifest_rsync_uri: manifest_rsync_uri.to_string(),
            })
            .inspect(|_| {
                timing.manifest_blob_load_ms =
                    manifest_blob_load_started.elapsed().as_millis() as u64;
            })?
    } else {
        let manifest_blob_load_started = std::time::Instant::now();
        store
            .load_current_object_bytes_by_uri(manifest_rsync_uri)
            .map_err(|e| ManifestFreshError::MissingManifest {
                manifest_rsync_uri: format!("{manifest_rsync_uri} ({e})"),
            })?
            .ok_or_else(|| ManifestFreshError::MissingManifest {
                manifest_rsync_uri: manifest_rsync_uri.to_string(),
            })
            .inspect(|_| {
                timing.manifest_blob_load_ms =
                    manifest_blob_load_started.elapsed().as_millis() as u64;
            })?
    };
    timing.manifest_load_ms = manifest_load_started.elapsed().as_millis() as u64;

    let manifest_decode_started = std::time::Instant::now();
    let manifest =
        decode_and_validate_manifest_with_current_time(&manifest_bytes, validation_time)?;
    timing.manifest_decode_ms = manifest_decode_started.elapsed().as_millis() as u64;

    let this_update = manifest
        .manifest
        .this_update
        .to_offset(time::UtcOffset::UTC);
    let next_update = manifest
        .manifest
        .next_update
        .to_offset(time::UtcOffset::UTC);
    let now = validation_time.to_offset(time::UtcOffset::UTC);

    // RFC 9286 §4.2.1: replay/rollback detection for manifestNumber and thisUpdate.
    //
    // Important nuance for revalidation across runs:
    // - If the manifestNumber is equal to the previously validated manifestNumber *and* the
    //   manifest bytes are identical, then this is the same manifest being revalidated and MUST
    //   be accepted (otherwise, RPs would incorrectly treat stable repositories as "failed fetch"
    //   and fall back to the current-instance VCIR snapshot).
    // - If manifestNumber is equal but the manifest bytes differ, treat this as invalid (a
    //   repository is not allowed to change the manifest while keeping the manifestNumber).
    // - If manifestNumber is lower, treat as rollback and reject.
    // - If manifestNumber is higher, require thisUpdate to be more recent than the previously
    //   validated thisUpdate.
    let replay_guard_started = std::time::Instant::now();
    if let Some(old_meta) = store
        .get_manifest_replay_meta(manifest_rsync_uri)
        .ok()
        .flatten()
    {
        timing.replay_meta_hit = true;
        if old_meta.manifest_rsync_uri == manifest_rsync_uri {
            let new_num = manifest.manifest.manifest_number.bytes_be.as_slice();
            let old_num = old_meta.manifest_number_be.as_slice();
            match cmp_minimal_be_unsigned(new_num, old_num) {
                Ordering::Greater => {
                    let old_this_update = old_meta
                        .manifest_this_update
                        .parse()
                        .expect("manifest replay meta validation ensures thisUpdate parses");
                    if this_update <= old_this_update {
                        use time::format_description::well_known::Rfc3339;
                        return Err(ManifestFreshError::ThisUpdateNotIncreasing {
                            old_rfc3339_utc: old_this_update
                                .to_offset(time::UtcOffset::UTC)
                                .format(&Rfc3339)
                                .expect("format old thisUpdate"),
                            new_rfc3339_utc: this_update
                                .format(&Rfc3339)
                                .expect("format new thisUpdate"),
                        });
                    }
                }
                Ordering::Equal => {
                    let new_manifest_hash = sha2::Sha256::digest(&manifest_bytes);
                    if old_meta.manifest_sha256.as_slice() != new_manifest_hash.as_slice() {
                        return Err(ManifestFreshError::ManifestNumberNotIncreasing {
                            old_hex: hex::encode_upper(old_num),
                            new_hex: hex::encode_upper(new_num),
                        });
                    }
                }
                Ordering::Less => {
                    return Err(ManifestFreshError::ManifestNumberNotIncreasing {
                        old_hex: hex::encode_upper(old_num),
                        new_hex: hex::encode_upper(new_num),
                    });
                }
            }
        }
    } else {
        timing.replay_meta_miss = true;
    }
    timing.replay_guard_ms = replay_guard_started.elapsed().as_millis() as u64;

    let manifest_entries_started = std::time::Instant::now();
    let entries = manifest
        .manifest
        .parse_files()
        .map_err(ManifestDecodeError::Validate)?;
    timing.manifest_entries_ms = manifest_entries_started.elapsed().as_millis() as u64;
    timing.manifest_file_count = entries.len();
    let mut files = Vec::with_capacity(manifest.manifest.file_count());
    let pack_files_started = std::time::Instant::now();
    let external_raw_store = store
        .external_raw_store_ref()
        .cloned()
        .map(std::sync::Arc::new);
    let external_repo_bytes = store
        .external_repo_bytes_ref()
        .cloned()
        .map(std::sync::Arc::new);
    let mut pack_files_index_lookup_duration = std::time::Duration::ZERO;
    let mut pack_files_blob_load_duration = std::time::Duration::ZERO;
    for entry in &entries {
        let rsync_uri =
            join_rsync_dir_and_file(publication_point_rsync_uri, entry.file_name.as_str());
        let current_object = if let Some(index) = current_index_guard.as_ref() {
            let index_lookup_started = std::time::Instant::now();
            let current =
                index
                    .get_by_uri(&rsync_uri)
                    .ok_or_else(|| ManifestFreshError::MissingFile {
                        rsync_uri: rsync_uri.clone(),
                    })?;
            pack_files_index_lookup_duration += index_lookup_started.elapsed();
            crate::storage::CurrentObjectWithHash {
                current_hash_hex: current.current_hash_hex.clone(),
                current_hash: current.current_hash,
                bytes: Vec::new(),
            }
        } else {
            let blob_load_started = std::time::Instant::now();
            store
                .load_current_object_with_hash_by_uri(&rsync_uri)
                .map_err(|_e| ManifestFreshError::MissingFile {
                    rsync_uri: rsync_uri.clone(),
                })?
                .ok_or_else(|| ManifestFreshError::MissingFile {
                    rsync_uri: rsync_uri.clone(),
                })
                .inspect(|_| {
                    pack_files_blob_load_duration += blob_load_started.elapsed();
                })?
        };

        if current_object.current_hash != entry.hash_bytes {
            return Err(ManifestFreshError::HashMismatch { rsync_uri });
        }

        if let (Some(_), Some(repo_bytes)) =
            (current_index_guard.as_ref(), external_repo_bytes.as_ref())
        {
            files.push(PackFile::from_lazy_repo_bytes(
                rsync_uri,
                current_object.current_hash_hex,
                current_object.current_hash,
                repo_bytes.clone(),
            ));
        } else if let (Some(_), Some(raw_store)) =
            (current_index_guard.as_ref(), external_raw_store.as_ref())
        {
            files.push(PackFile::from_lazy_external_raw_store(
                rsync_uri,
                current_object.current_hash_hex,
                current_object.current_hash,
                raw_store.clone(),
            ));
        } else {
            let bytes = if current_object.bytes.is_empty() {
                let blob_load_started = std::time::Instant::now();
                store
                    .get_blob_bytes(&current_object.current_hash_hex)
                    .map_err(|_e| ManifestFreshError::MissingFile {
                        rsync_uri: rsync_uri.clone(),
                    })?
                    .ok_or_else(|| ManifestFreshError::MissingFile {
                        rsync_uri: rsync_uri.clone(),
                    })
                    .inspect(|_| {
                        pack_files_blob_load_duration += blob_load_started.elapsed();
                    })?
            } else {
                current_object.bytes
            };
            files.push(PackFile::from_bytes_with_sha256(
                rsync_uri,
                bytes,
                current_object.current_hash,
            ));
        }
    }
    timing.pack_files_index_lookup_ms = pack_files_index_lookup_duration.as_millis() as u64;
    timing.pack_files_blob_load_ms = pack_files_blob_load_duration.as_millis() as u64;
    timing.pack_files_ms = pack_files_started.elapsed().as_millis() as u64;

    // RFC 6488 §3: manifest (signed object) validity includes a valid EE cert path.
    // We validate this after §6.4/§6.5 so the issuer CRL can be selected from the publication point.
    let ee_path_validate_started = std::time::Instant::now();
    validate_manifest_embedded_ee_cert_path(
        &manifest,
        &files,
        issuer_ca_der,
        issuer_ca_rsync_uri,
        validation_time,
    )?;
    timing.ee_path_validate_ms = ee_path_validate_started.elapsed().as_millis() as u64;

    Ok((
        FreshValidatedPublicationPoint {
            manifest_rsync_uri: manifest_rsync_uri.to_string(),
            publication_point_rsync_uri: publication_point_rsync_uri.to_string(),
            manifest_number_be: manifest.manifest.manifest_number.bytes_be.clone(),
            this_update: PackTime::from_utc_offset_datetime(this_update),
            next_update: PackTime::from_utc_offset_datetime(next_update),
            verified_at: PackTime::from_utc_offset_datetime(now),
            manifest_bytes,
            files,
        },
        timing,
    ))
}

fn cmp_minimal_be_unsigned(a: &[u8], b: &[u8]) -> Ordering {
    // Compare two minimal big-endian byte strings as unsigned integers.
    // (Leading zeros are not expected; callers store minimal big-endian.)
    a.len().cmp(&b.len()).then_with(|| a.cmp(b))
}

fn join_rsync_dir_and_file(base: &str, file_name: &str) -> String {
    if base.ends_with('/') {
        format!("{base}{file_name}")
    } else {
        format!("{base}/{file_name}")
    }
}

fn rsync_uri_is_under_publication_point(uri: &str, publication_point_rsync_uri: &str) -> bool {
    let pp = if publication_point_rsync_uri.ends_with('/') {
        publication_point_rsync_uri.to_string()
    } else {
        format!("{publication_point_rsync_uri}/")
    };
    uri.starts_with(&pp)
}

fn validate_manifest_embedded_ee_cert_path(
    manifest: &ManifestObject,
    files: &[crate::storage::PackFile],
    issuer_ca_der: &[u8],
    issuer_ca_rsync_uri: Option<&str>,
    validation_time: time::OffsetDateTime,
) -> Result<(), ManifestFreshError> {
    let ee = &manifest.signed_object.signed_data.certificates[0];

    let crl_files = files
        .iter()
        .filter(|f| f.rsync_uri.ends_with(".crl"))
        .collect::<Vec<_>>();
    if crl_files.is_empty() {
        return Err(ManifestFreshError::NoCrlFiles);
    }

    let Some(crldp_uris) = ee
        .resource_cert
        .tbs
        .extensions
        .crl_distribution_points_uris
        .as_ref()
    else {
        return Err(ManifestFreshError::EeCrlDpMissing);
    };

    for u in crldp_uris {
        let s = u.as_str();
        if let Some(f) = crl_files.iter().find(|f| f.rsync_uri == s) {
            let crl_bytes = f.bytes().map_err(|e| ManifestFreshError::MissingFile {
                rsync_uri: format!("{s} ({e})"),
            })?;
            let issuer_ca = crate::data_model::rc::ResourceCertificate::decode_der(issuer_ca_der)
                .map_err(CertPathError::IssuerDecode)?;
            let (rem, issuer_spki) = x509_parser::x509::SubjectPublicKeyInfo::from_der(
                &issuer_ca.tbs.subject_public_key_info,
            )
            .map_err(|e| CertPathError::IssuerSpkiParse(e.to_string()))?;
            if !rem.is_empty() {
                return Err(CertPathError::IssuerSpkiTrailingBytes(rem.len()).into());
            }
            let issuer_crl = crate::data_model::crl::RpkixCrl::decode_der(crl_bytes)
                .map_err(CertPathError::from)?;
            let revoked_serials = issuer_crl
                .revoked_certs
                .iter()
                .map(|rc| rc.serial_number.bytes_be.clone())
                .collect::<std::collections::HashSet<_>>();
            validate_signed_object_ee_cert_path_fast(
                ee,
                &issuer_ca,
                &issuer_spki,
                &issuer_crl,
                &revoked_serials,
                issuer_ca_rsync_uri,
                Some(f.rsync_uri.as_str()),
                validation_time,
            )?;
            return Ok(());
        }
    }

    Err(ManifestFreshError::EeCrlNotFound(
        crldp_uris
            .iter()
            .map(|u| u.as_str())
            .collect::<Vec<_>>()
            .join(", "),
    ))
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::current_repo_index::CurrentRepoIndex;
    use crate::data_model::manifest::ManifestObject;
    use crate::storage::{
        PackFile, PackTime, RawByHashEntry, RocksStore, ValidatedCaInstanceResult,
        ValidatedManifestMeta, VcirArtifactKind, VcirArtifactRole, VcirArtifactValidationStatus,
        VcirAuditSummary, VcirCcrManifestProjection, VcirInstanceGate, VcirRelatedArtifact,
        VcirSummary,
    };
    use std::path::Path;

    fn manifest_fixture_path() -> &'static Path {
        Path::new(
            "tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft",
        )
    }

    fn issuer_ca_fixture_der() -> Vec<u8> {
        std::fs::read(
            "tests/fixtures/repository/rpki.apnic.net/repository/B527EF581D6611E2BB468F7C72FD1FF2/BfycW4hQb3wNP4YsiJW-1n6fjro.cer",
        )
        .expect("read issuer ca fixture")
    }

    fn issuer_ca_rsync_uri() -> &'static str {
        "rsync://rpki.apnic.net/repository/B527EF581D6611E2BB468F7C72FD1FF2/BfycW4hQb3wNP4YsiJW-1n6fjro.cer"
    }

    fn fixture_to_rsync_uri(path: &Path) -> String {
        let rel = path
            .strip_prefix("tests/fixtures/repository")
            .expect("path under fixture repository");
        let mut it = rel.components();
        let host = it
            .next()
            .expect("host component")
            .as_os_str()
            .to_string_lossy();
        let rest = it.as_path().to_string_lossy();
        format!("rsync://{host}/{rest}")
    }

    fn fixture_dir_to_rsync_uri(dir: &Path) -> String {
        let mut s = fixture_to_rsync_uri(dir);
        if !s.ends_with('/') {
            s.push('/');
        }
        s
    }

    fn load_manifest_fixture() -> (
        ManifestObject,
        Vec<u8>,
        String,
        String,
        time::OffsetDateTime,
    ) {
        let manifest_path = manifest_fixture_path();
        let manifest_bytes = std::fs::read(manifest_path).expect("read manifest fixture");
        let manifest = ManifestObject::decode_der(&manifest_bytes).expect("decode manifest");
        let manifest_rsync_uri = fixture_to_rsync_uri(manifest_path);
        let publication_point_rsync_uri = fixture_dir_to_rsync_uri(manifest_path.parent().unwrap());
        let validation_time = manifest.manifest.this_update + time::Duration::seconds(1);
        (
            manifest,
            manifest_bytes,
            manifest_rsync_uri,
            publication_point_rsync_uri,
            validation_time,
        )
    }

    fn raw_by_hash_entry(uri: &str, bytes: Vec<u8>, object_type: &str) -> RawByHashEntry {
        let mut entry =
            RawByHashEntry::from_bytes(hex::encode(sha2::Sha256::digest(&bytes)), bytes);
        entry.origin_uris.push(uri.to_string());
        entry.object_type = Some(object_type.to_string());
        entry.encoding = Some("der".to_string());
        entry
    }

    fn put_current_object(store: &RocksStore, rsync_uri: &str, bytes: Vec<u8>, object_type: &str) {
        let hash = hex::encode(sha2::Sha256::digest(&bytes));
        store
            .put_raw_by_hash_entry(&raw_by_hash_entry(rsync_uri, bytes, object_type))
            .expect("put raw_by_hash entry");
        store
            .put_repository_view_entry(&crate::storage::RepositoryViewEntry {
                rsync_uri: rsync_uri.to_string(),
                current_hash: Some(hash),
                repository_source: Some("https://example.test/notification.xml".to_string()),
                object_type: Some(object_type.to_string()),
                state: crate::storage::RepositoryViewState::Present,
            })
            .expect("put repository view entry");
    }

    fn put_complete_publication_point_current_objects(
        store: &RocksStore,
        manifest: &ManifestObject,
        manifest_rsync_uri: &str,
        manifest_bytes: Vec<u8>,
        publication_point_rsync_uri: &str,
    ) {
        put_current_object(store, manifest_rsync_uri, manifest_bytes, "mft");
        for entry in manifest.manifest.parse_files().expect("parse files") {
            let file_path = manifest_fixture_path()
                .parent()
                .unwrap()
                .join(entry.file_name.as_str());
            let bytes = std::fs::read(&file_path).expect("read fixture file");
            let rsync_uri = format!("{publication_point_rsync_uri}{}", entry.file_name);
            let object_type = rsync_uri.rsplit('.').next().unwrap_or("bin");
            put_current_object(store, &rsync_uri, bytes, object_type);
        }
    }

    fn put_raw_only(store: &RocksStore, rsync_uri: &str, bytes: Vec<u8>, object_type: &str) {
        store
            .put_raw_by_hash_entry(&raw_by_hash_entry(rsync_uri, bytes, object_type))
            .expect("put raw_by_hash entry");
    }

    fn sample_vcir_for_manifest_replay_meta(
        manifest: &ManifestObject,
        manifest_rsync_uri: &str,
        publication_point_rsync_uri: &str,
        manifest_bytes: &[u8],
        validation_time: time::OffsetDateTime,
    ) -> ValidatedCaInstanceResult {
        let manifest_hash = hex::encode(sha2::Sha256::digest(manifest_bytes));
        let this_update = manifest
            .manifest
            .this_update
            .to_offset(time::UtcOffset::UTC);
        let mut vcir = sample_current_instance_vcir(
            manifest_rsync_uri,
            publication_point_rsync_uri,
            &manifest_hash,
            "rsync://example.test/repo/object.roa",
            &hex::encode(sha2::Sha256::digest(b"object")),
            validation_time,
            true,
        );
        vcir.validated_manifest_meta.validated_manifest_number =
            manifest.manifest.manifest_number.bytes_be.clone();
        vcir.validated_manifest_meta.validated_manifest_this_update =
            PackTime::from_utc_offset_datetime(this_update);
        vcir.ccr_manifest_projection.manifest_number_be =
            manifest.manifest.manifest_number.bytes_be.clone();
        vcir.ccr_manifest_projection.manifest_this_update =
            PackTime::from_utc_offset_datetime(this_update);
        vcir.ccr_manifest_projection.manifest_sha256 =
            hex::decode(manifest_hash).expect("decode manifest hash");
        vcir.ccr_manifest_projection.manifest_size = manifest_bytes.len() as u64;
        vcir
    }

    fn sample_current_instance_vcir(
        manifest_rsync_uri: &str,
        publication_point_rsync_uri: &str,
        manifest_sha256: &str,
        locked_object_uri: &str,
        locked_object_sha256: &str,
        validation_time: time::OffsetDateTime,
        failed_fetch_eligible: bool,
    ) -> ValidatedCaInstanceResult {
        let gate_time =
            PackTime::from_utc_offset_datetime(validation_time + time::Duration::hours(1));
        let ccr_manifest_projection = VcirCcrManifestProjection {
            manifest_rsync_uri: manifest_rsync_uri.to_string(),
            manifest_sha256: hex::decode(manifest_sha256).expect("decode manifest sha256"),
            manifest_size: 2048,
            manifest_ee_aki: vec![0x11; 20],
            manifest_number_be: vec![1],
            manifest_this_update: PackTime::from_utc_offset_datetime(validation_time),
            manifest_sia_locations_der: vec![vec![
                0x30, 0x11, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x05, 0x86, 0x05,
                b'r', b's', b'y', b'n', b'c',
            ]],
            subordinate_skis: Vec::new(),
        };
        ValidatedCaInstanceResult {
            manifest_rsync_uri: manifest_rsync_uri.to_string(),
            parent_manifest_rsync_uri: None,
            tal_id: "test-tal".to_string(),
            ca_subject_name: "CN=test".to_string(),
            ca_ski: "00112233445566778899aabbccddeeff00112233".to_string(),
            issuer_ski: "00112233445566778899aabbccddeeff00112233".to_string(),
            last_successful_validation_time: PackTime::from_utc_offset_datetime(validation_time),
            current_manifest_rsync_uri: manifest_rsync_uri.to_string(),
            current_crl_rsync_uri: format!("{publication_point_rsync_uri}current.crl"),
            validated_manifest_meta: ValidatedManifestMeta {
                validated_manifest_number: vec![1],
                validated_manifest_this_update: PackTime::from_utc_offset_datetime(validation_time),
                validated_manifest_next_update: gate_time.clone(),
            },
            ccr_manifest_projection,
            instance_gate: VcirInstanceGate {
                manifest_next_update: gate_time.clone(),
                current_crl_next_update: gate_time.clone(),
                self_ca_not_after: gate_time.clone(),
                instance_effective_until: gate_time,
            },
            child_entries: Vec::new(),
            local_outputs: Vec::new(),
            related_artifacts: vec![
                VcirRelatedArtifact {
                    artifact_role: VcirArtifactRole::Manifest,
                    artifact_kind: VcirArtifactKind::Mft,
                    uri: Some(manifest_rsync_uri.to_string()),
                    sha256: manifest_sha256.to_string(),
                    object_type: Some("mft".to_string()),
                    validation_status: VcirArtifactValidationStatus::Accepted,
                    reject_reason: None,
                },
                VcirRelatedArtifact {
                    artifact_role: VcirArtifactRole::SignedObject,
                    artifact_kind: VcirArtifactKind::Roa,
                    uri: Some(locked_object_uri.to_string()),
                    sha256: locked_object_sha256.to_string(),
                    object_type: Some("roa".to_string()),
                    validation_status: VcirArtifactValidationStatus::Accepted,
                    reject_reason: None,
                },
            ],
            summary: VcirSummary {
                local_vrp_count: 0,
                local_aspa_count: 0,
                local_router_key_count: 0,
                child_count: 0,
                accepted_object_count: 2,
                rejected_object_count: 0,
            },
            audit_summary: VcirAuditSummary {
                failed_fetch_eligible,
                last_failed_fetch_reason: None,
                warning_count: 0,
                audit_flags: Vec::new(),
            },
        }
    }
    fn locked_files_for_manifest(
        manifest: &ManifestObject,
        publication_point_rsync_uri: &str,
    ) -> Vec<PackFile> {
        let manifest_path = manifest_fixture_path();
        manifest
            .manifest
            .parse_files()
            .expect("parse files")
            .into_iter()
            .map(|entry| {
                let file_path = manifest_path
                    .parent()
                    .unwrap()
                    .join(entry.file_name.as_str());
                let bytes = std::fs::read(&file_path).unwrap_or_else(|_| {
                    panic!("read fixture file referenced by manifest: {file_path:?}")
                });
                PackFile::from_bytes_compute_sha256(
                    format!("{publication_point_rsync_uri}{}", entry.file_name),
                    bytes,
                )
            })
            .collect()
    }

    #[test]
    fn try_build_fresh_publication_point_rejects_manifest_outside_publication_point() {
        let temp = tempfile::tempdir().expect("tempdir");
        let store = RocksStore::open(temp.path()).expect("open rocksdb");
        let (_, _, manifest_rsync_uri, _, validation_time) = load_manifest_fixture();
        let err = try_build_fresh_publication_point(
            &store,
            &manifest_rsync_uri,
            "rsync://example.test/other/",
            &issuer_ca_fixture_der(),
            Some(issuer_ca_rsync_uri()),
            validation_time,
        )
        .unwrap_err();
        assert!(
            matches!(
                err,
                ManifestFreshError::ManifestOutsidePublicationPoint { .. }
            ),
            "{err}"
        );
    }

    #[test]
    fn try_build_fresh_publication_point_reports_missing_manifest_when_raw_store_is_empty() {
        let temp = tempfile::tempdir().expect("tempdir");
        let store = RocksStore::open(temp.path()).expect("open rocksdb");
        let (_, _, manifest_rsync_uri, publication_point_rsync_uri, validation_time) =
            load_manifest_fixture();
        let err = try_build_fresh_publication_point(
            &store,
            &manifest_rsync_uri,
            &publication_point_rsync_uri,
            &issuer_ca_fixture_der(),
            Some(issuer_ca_rsync_uri()),
            validation_time,
        )
        .unwrap_err();
        assert!(
            matches!(err, ManifestFreshError::MissingManifest { .. }),
            "{err}"
        );
    }

    #[test]
    fn try_build_fresh_publication_point_reports_missing_locked_file() {
        let temp = tempfile::tempdir().expect("tempdir");
        let store = RocksStore::open(temp.path()).expect("open rocksdb");
        let (
            manifest,
            manifest_bytes,
            manifest_rsync_uri,
            publication_point_rsync_uri,
            validation_time,
        ) = load_manifest_fixture();
        put_current_object(&store, &manifest_rsync_uri, manifest_bytes, "mft");
        let first_non_crl = manifest
            .manifest
            .parse_files()
            .expect("parse files")
            .into_iter()
            .find(|entry| !entry.file_name.ends_with(".crl"))
            .expect("fixture non-crl entry");
        let file_path = manifest_fixture_path()
            .parent()
            .unwrap()
            .join(first_non_crl.file_name.as_str());
        let bytes = std::fs::read(&file_path).expect("read fixture file");
        let rsync_uri = format!("{publication_point_rsync_uri}{}", first_non_crl.file_name);
        let object_type = rsync_uri.rsplit('.').next().unwrap_or("bin");
        put_current_object(&store, &rsync_uri, bytes, object_type);

        let err = try_build_fresh_publication_point(
            &store,
            &manifest_rsync_uri,
            &publication_point_rsync_uri,
            &issuer_ca_fixture_der(),
            Some(issuer_ca_rsync_uri()),
            validation_time,
        )
        .unwrap_err();
        assert!(
            matches!(err, ManifestFreshError::MissingFile { .. }),
            "{err}"
        );
    }

    #[test]
    fn try_build_fresh_publication_point_detects_hash_mismatch_via_repository_view_hash() {
        let temp = tempfile::tempdir().expect("tempdir");
        let store = RocksStore::open(temp.path()).expect("open rocksdb");
        let (
            manifest,
            manifest_bytes,
            manifest_rsync_uri,
            publication_point_rsync_uri,
            validation_time,
        ) = load_manifest_fixture();
        put_current_object(&store, &manifest_rsync_uri, manifest_bytes, "mft");

        let non_crl_entries = manifest
            .manifest
            .parse_files()
            .expect("parse files")
            .into_iter()
            .filter(|entry| !entry.file_name.ends_with(".crl"))
            .collect::<Vec<_>>();
        let first = &non_crl_entries[0];
        let second = &non_crl_entries[1];

        let first_uri = format!("{publication_point_rsync_uri}{}", first.file_name);
        let second_path = manifest_fixture_path()
            .parent()
            .unwrap()
            .join(second.file_name.as_str());
        let wrong_bytes = std::fs::read(&second_path).expect("read wrong fixture file");
        let object_type = first_uri.rsplit('.').next().unwrap_or("bin");
        put_current_object(&store, &first_uri, wrong_bytes, object_type);

        for entry in non_crl_entries.iter().skip(1) {
            let file_path = manifest_fixture_path()
                .parent()
                .unwrap()
                .join(entry.file_name.as_str());
            let bytes = std::fs::read(&file_path).expect("read fixture file");
            let rsync_uri = format!("{publication_point_rsync_uri}{}", entry.file_name);
            let object_type = rsync_uri.rsplit('.').next().unwrap_or("bin");
            put_current_object(&store, &rsync_uri, bytes, object_type);
        }

        let err = try_build_fresh_publication_point(
            &store,
            &manifest_rsync_uri,
            &publication_point_rsync_uri,
            &issuer_ca_fixture_der(),
            Some(issuer_ca_rsync_uri()),
            validation_time,
        )
        .unwrap_err();
        assert!(
            matches!(err, ManifestFreshError::HashMismatch { .. }),
            "{err}"
        );
    }

    #[test]
    fn try_build_fresh_publication_point_uses_current_repo_index_without_repository_view() {
        let temp = tempfile::tempdir().expect("tempdir");
        let store = RocksStore::open(temp.path()).expect("open rocksdb");
        let (
            manifest,
            manifest_bytes,
            manifest_rsync_uri,
            publication_point_rsync_uri,
            validation_time,
        ) = load_manifest_fixture();

        put_raw_only(&store, &manifest_rsync_uri, manifest_bytes.clone(), "mft");
        let current_index = CurrentRepoIndex::shared();
        let mut entries = vec![crate::storage::RepositoryViewEntry {
            rsync_uri: manifest_rsync_uri.clone(),
            current_hash: Some(hex::encode(sha2::Sha256::digest(&manifest_bytes))),
            repository_source: Some("https://example.test/notification.xml".to_string()),
            object_type: Some("mft".to_string()),
            state: crate::storage::RepositoryViewState::Present,
        }];

        for entry in manifest.manifest.parse_files().expect("parse files") {
            let file_path = manifest_fixture_path()
                .parent()
                .unwrap()
                .join(entry.file_name.as_str());
            let bytes = std::fs::read(&file_path).expect("read fixture file");
            let rsync_uri = format!("{publication_point_rsync_uri}{}", entry.file_name);
            let object_type = rsync_uri.rsplit('.').next().unwrap_or("bin").to_string();
            put_raw_only(&store, &rsync_uri, bytes.clone(), &object_type);
            entries.push(crate::storage::RepositoryViewEntry {
                rsync_uri,
                current_hash: Some(hex::encode(sha2::Sha256::digest(&bytes))),
                repository_source: Some("https://example.test/notification.xml".to_string()),
                object_type: Some(object_type),
                state: crate::storage::RepositoryViewState::Present,
            });
        }

        current_index
            .write()
            .expect("index write lock")
            .apply_repository_view_entries(&entries)
            .expect("apply current index");

        assert!(
            store
                .get_repository_view_entry(&manifest_rsync_uri)
                .expect("get repository view")
                .is_none()
        );

        let (fresh, _timing) = try_build_fresh_publication_point_with_timing(
            &store,
            &manifest_rsync_uri,
            &publication_point_rsync_uri,
            Some(&current_index),
            &issuer_ca_fixture_der(),
            Some(issuer_ca_rsync_uri()),
            validation_time,
        )
        .expect("fresh publication point via current index");

        assert_eq!(fresh.manifest_rsync_uri, manifest_rsync_uri);
        assert_eq!(fresh.files.len(), manifest.manifest.file_count());
    }

    #[test]
    fn try_build_fresh_publication_point_records_replay_meta_miss() {
        let temp = tempfile::tempdir().expect("tempdir");
        let store = RocksStore::open(temp.path()).expect("open rocksdb");
        let (
            manifest,
            manifest_bytes,
            manifest_rsync_uri,
            publication_point_rsync_uri,
            validation_time,
        ) = load_manifest_fixture();
        put_complete_publication_point_current_objects(
            &store,
            &manifest,
            &manifest_rsync_uri,
            manifest_bytes,
            &publication_point_rsync_uri,
        );

        let (_fresh, timing) = try_build_fresh_publication_point_with_timing(
            &store,
            &manifest_rsync_uri,
            &publication_point_rsync_uri,
            None,
            &issuer_ca_fixture_der(),
            Some(issuer_ca_rsync_uri()),
            validation_time,
        )
        .expect("fresh publication point without replay meta");

        assert!(timing.replay_meta_miss);
        assert!(!timing.replay_meta_hit);
    }

    #[test]
    fn try_build_fresh_publication_point_uses_replay_meta_hit_for_same_manifest() {
        let temp = tempfile::tempdir().expect("tempdir");
        let store = RocksStore::open(temp.path()).expect("open rocksdb");
        let (
            manifest,
            manifest_bytes,
            manifest_rsync_uri,
            publication_point_rsync_uri,
            validation_time,
        ) = load_manifest_fixture();
        let previous_vcir = sample_vcir_for_manifest_replay_meta(
            &manifest,
            &manifest_rsync_uri,
            &publication_point_rsync_uri,
            &manifest_bytes,
            validation_time,
        );
        store.put_vcir(&previous_vcir).expect("put previous vcir");
        put_complete_publication_point_current_objects(
            &store,
            &manifest,
            &manifest_rsync_uri,
            manifest_bytes,
            &publication_point_rsync_uri,
        );

        let (_fresh, timing) = try_build_fresh_publication_point_with_timing(
            &store,
            &manifest_rsync_uri,
            &publication_point_rsync_uri,
            None,
            &issuer_ca_fixture_der(),
            Some(issuer_ca_rsync_uri()),
            validation_time,
        )
        .expect("fresh publication point with matching replay meta");

        assert!(timing.replay_meta_hit);
        assert!(!timing.replay_meta_miss);
    }

    #[test]
    fn validate_manifest_embedded_ee_cert_path_rejects_missing_crl_files() {
        let (manifest, _, _, publication_point_rsync_uri, validation_time) =
            load_manifest_fixture();
        let files = locked_files_for_manifest(&manifest, &publication_point_rsync_uri)
            .into_iter()
            .filter(|f| !f.rsync_uri.ends_with(".crl"))
            .collect::<Vec<_>>();

        let err = validate_manifest_embedded_ee_cert_path(
            &manifest,
            &files,
            &issuer_ca_fixture_der(),
            Some(issuer_ca_rsync_uri()),
            validation_time,
        )
        .unwrap_err();
        assert!(matches!(err, ManifestFreshError::NoCrlFiles), "{err}");
    }

    #[test]
    fn validate_manifest_embedded_ee_cert_path_rejects_missing_ee_crldp() {
        let (mut manifest, _, _, publication_point_rsync_uri, validation_time) =
            load_manifest_fixture();
        manifest.signed_object.signed_data.certificates[0]
            .resource_cert
            .tbs
            .extensions
            .crl_distribution_points_uris = None;
        let files = locked_files_for_manifest(&manifest, &publication_point_rsync_uri);

        let err = validate_manifest_embedded_ee_cert_path(
            &manifest,
            &files,
            &issuer_ca_fixture_der(),
            Some(issuer_ca_rsync_uri()),
            validation_time,
        )
        .unwrap_err();
        assert!(matches!(err, ManifestFreshError::EeCrlDpMissing), "{err}");
    }

    #[test]
    fn validate_manifest_embedded_ee_cert_path_rejects_unlisted_crldp_uri() {
        let (manifest, _, _, publication_point_rsync_uri, validation_time) =
            load_manifest_fixture();
        let mut files = locked_files_for_manifest(&manifest, &publication_point_rsync_uri)
            .into_iter()
            .filter(|f| !f.rsync_uri.ends_with(".crl"))
            .collect::<Vec<_>>();
        files.push(PackFile::from_bytes_compute_sha256(
            "rsync://example.test/repo/unrelated.crl",
            b"dummy".to_vec(),
        ));

        let err = validate_manifest_embedded_ee_cert_path(
            &manifest,
            &files,
            &issuer_ca_fixture_der(),
            Some(issuer_ca_rsync_uri()),
            validation_time,
        )
        .unwrap_err();
        assert!(matches!(err, ManifestFreshError::EeCrlNotFound(_)), "{err}");
    }

    #[test]
    fn validate_manifest_embedded_ee_cert_path_rejects_expired_crl() {
        let (manifest, _, _, publication_point_rsync_uri, _) = load_manifest_fixture();
        let files = locked_files_for_manifest(&manifest, &publication_point_rsync_uri);
        let ee = &manifest.signed_object.signed_data.certificates[0];
        let crldp_uri = ee
            .resource_cert
            .tbs
            .extensions
            .crl_distribution_points_uris
            .as_ref()
            .and_then(|uris| uris.first())
            .expect("fixture manifest EE CRLDP")
            .as_str()
            .to_string();
        let crl_file = files
            .iter()
            .find(|file| file.rsync_uri == crldp_uri)
            .expect("fixture CRL referenced by manifest EE");
        let crl = crate::data_model::crl::RpkixCrl::decode_der(
            crl_file.bytes().expect("read fixture crl bytes"),
        )
        .expect("decode fixture crl");
        let validation_time = crl.next_update.utc;

        let err = validate_manifest_embedded_ee_cert_path(
            &manifest,
            &files,
            &issuer_ca_fixture_der(),
            Some(issuer_ca_rsync_uri()),
            validation_time,
        )
        .unwrap_err();

        assert!(
            matches!(
                err,
                ManifestFreshError::EeCertPath(
                    crate::validation::cert_path::CertPathError::CrlNotValidAtTime
                )
            ),
            "{err}"
        );
    }

    #[test]
    fn load_current_instance_vcir_publication_point_returns_manifest_and_locked_files() {
        let temp = tempfile::tempdir().expect("tempdir");
        let store = RocksStore::open(temp.path()).expect("open rocksdb");
        let validation_time = time::OffsetDateTime::UNIX_EPOCH + time::Duration::hours(1);
        let manifest_uri = "rsync://example.test/repo/current.mft";
        let publication_point_uri = "rsync://example.test/repo/";
        let locked_uri = "rsync://example.test/repo/object.roa";
        let manifest_bytes = vec![0x30, 0x31, 0x32];
        let locked_bytes = vec![0x01, 0x02, 0x03, 0x04];
        let manifest_entry = raw_by_hash_entry(manifest_uri, manifest_bytes.clone(), "mft");
        let locked_entry = raw_by_hash_entry(locked_uri, locked_bytes.clone(), "roa");
        store
            .put_raw_by_hash_entry(&manifest_entry)
            .expect("put manifest raw_by_hash");
        store
            .put_raw_by_hash_entry(&locked_entry)
            .expect("put locked raw_by_hash");
        let vcir = sample_current_instance_vcir(
            manifest_uri,
            publication_point_uri,
            &manifest_entry.sha256_hex,
            locked_uri,
            &locked_entry.sha256_hex,
            validation_time,
            true,
        );
        store.put_vcir(&vcir).expect("put vcir");

        let point = load_current_instance_vcir_publication_point(
            &store,
            manifest_uri,
            publication_point_uri,
            validation_time,
        )
        .expect("load current-instance vcir publication point");
        assert_eq!(point.manifest_bytes, manifest_bytes);
        assert_eq!(point.files.len(), 1);
        assert_eq!(point.files[0].rsync_uri, locked_uri);
        assert_eq!(
            point.files[0].bytes_cloned().expect("locked bytes"),
            locked_bytes
        );
    }

    #[test]
    fn load_current_instance_vcir_publication_point_rejects_ineligible_vcir() {
        let temp = tempfile::tempdir().expect("tempdir");
        let store = RocksStore::open(temp.path()).expect("open rocksdb");
        let validation_time = time::OffsetDateTime::UNIX_EPOCH + time::Duration::hours(1);
        let manifest_uri = "rsync://example.test/repo/current.mft";
        let publication_point_uri = "rsync://example.test/repo/";
        let locked_uri = "rsync://example.test/repo/object.roa";
        let manifest_entry = raw_by_hash_entry(manifest_uri, vec![0x30], "mft");
        let locked_entry = raw_by_hash_entry(locked_uri, vec![0x01], "roa");
        store
            .put_raw_by_hash_entry(&manifest_entry)
            .expect("put manifest raw_by_hash");
        store
            .put_raw_by_hash_entry(&locked_entry)
            .expect("put locked raw_by_hash");
        let vcir = sample_current_instance_vcir(
            manifest_uri,
            publication_point_uri,
            &manifest_entry.sha256_hex,
            locked_uri,
            &locked_entry.sha256_hex,
            validation_time,
            false,
        );
        store.put_vcir(&vcir).expect("put vcir");

        let err = load_current_instance_vcir_publication_point(
            &store,
            manifest_uri,
            publication_point_uri,
            validation_time,
        )
        .unwrap_err();
        assert!(
            matches!(err, ManifestReuseError::IneligibleCurrentInstanceVcir(_)),
            "{err}"
        );
    }

    #[test]
    fn load_current_instance_vcir_publication_point_rejects_expired_vcir() {
        let temp = tempfile::tempdir().expect("tempdir");
        let store = RocksStore::open(temp.path()).expect("open rocksdb");
        let validation_time = time::OffsetDateTime::UNIX_EPOCH + time::Duration::hours(2);
        let manifest_uri = "rsync://example.test/repo/current.mft";
        let publication_point_uri = "rsync://example.test/repo/";
        let locked_uri = "rsync://example.test/repo/object.roa";
        let manifest_entry = raw_by_hash_entry(manifest_uri, vec![0x30], "mft");
        let locked_entry = raw_by_hash_entry(locked_uri, vec![0x01], "roa");
        store
            .put_raw_by_hash_entry(&manifest_entry)
            .expect("put manifest raw_by_hash");
        store
            .put_raw_by_hash_entry(&locked_entry)
            .expect("put locked raw_by_hash");
        let mut vcir = sample_current_instance_vcir(
            manifest_uri,
            publication_point_uri,
            &manifest_entry.sha256_hex,
            locked_uri,
            &locked_entry.sha256_hex,
            validation_time - time::Duration::hours(2),
            true,
        );
        let expired =
            PackTime::from_utc_offset_datetime(validation_time - time::Duration::minutes(1));
        vcir.instance_gate.manifest_next_update = expired.clone();
        vcir.instance_gate.current_crl_next_update = expired.clone();
        vcir.instance_gate.self_ca_not_after = expired.clone();
        vcir.instance_gate.instance_effective_until = expired;
        vcir.validated_manifest_meta.validated_manifest_next_update =
            vcir.instance_gate.instance_effective_until.clone();
        store.put_vcir(&vcir).expect("put expired vcir");

        let err = load_current_instance_vcir_publication_point(
            &store,
            manifest_uri,
            publication_point_uri,
            validation_time,
        )
        .unwrap_err();
        assert!(
            matches!(err, ManifestReuseError::CurrentInstanceVcirExpired { .. }),
            "{err}"
        );
    }

    #[test]
    fn load_current_instance_vcir_publication_point_rejects_missing_locked_artifact_raw() {
        let temp = tempfile::tempdir().expect("tempdir");
        let store = RocksStore::open(temp.path()).expect("open rocksdb");
        let validation_time = time::OffsetDateTime::UNIX_EPOCH + time::Duration::hours(1);
        let manifest_uri = "rsync://example.test/repo/current.mft";
        let publication_point_uri = "rsync://example.test/repo/";
        let locked_uri = "rsync://example.test/repo/object.roa";
        let manifest_entry = raw_by_hash_entry(manifest_uri, vec![0x30], "mft");
        store
            .put_raw_by_hash_entry(&manifest_entry)
            .expect("put manifest raw_by_hash");
        let vcir = sample_current_instance_vcir(
            manifest_uri,
            publication_point_uri,
            &manifest_entry.sha256_hex,
            locked_uri,
            &hex::encode(sha2::Sha256::digest([0x01, 0x02])),
            validation_time,
            true,
        );
        store.put_vcir(&vcir).expect("put vcir");

        let err = load_current_instance_vcir_publication_point(
            &store,
            manifest_uri,
            publication_point_uri,
            validation_time,
        )
        .unwrap_err();
        assert!(
            matches!(err, ManifestReuseError::MissingArtifactRaw { .. }),
            "{err}"
        );
    }
}
