use crate::bootstrap::{
    compute_source_root_hash, validate_source_manifest, SourceFileEntry, SourceManifest,
};
use crate::checkpoint::{
    build_checkpoint_manifest, canonical_checkpoint_manifest_payload_bytes,
    compute_checkpoint_binding_hash, CheckpointArtifact, CheckpointManifest,
};
use crate::content::RangeProofPackage;
use crate::delivery_proofs::{build_delivery_proof_package, DeliveryProofPackage};
use crate::envelope::{
    make_attestation, ArtifactRecord, Attestation, PublishEnvelope, TransformEnvelope,
};
use crate::error::{TrazaeoError, TrazaeoResult};
use crate::publish_pipeline::{build_publish_envelope, PublishInput};
use crate::transform_pipeline::{build_transform_envelope, TransformStageInput};
use crate::trust::TrustPolicy;
use crate::verification::{verify_publish_envelope, VerificationMode, VerificationReport};
use serde::{Deserialize, Serialize};

const SOURCE_ARTIFACT_ID: &str = "source-manifest";

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct DatasetPublishedArtifactInput {
    pub artifact_id: String,
    pub artifact_ref: String,
    pub content_root_hash: String,
    pub media_type: String,
    pub content_descriptor_ref: Option<String>,
    pub content_descriptor_hash: Option<String>,
    pub snapshot_id: Option<String>,
    pub commit_id: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct DatasetWorkflowCommonInput {
    pub schema_version: String,
    pub issued_at: String,
    pub signed_at: String,
    pub signer_id: String,
    pub signing_key_hex: String,
    pub source_manifest_id: String,
    pub source_dataset_id: String,
    pub source_files: Vec<SourceFileEntry>,
    pub transform_subject_id: String,
    pub transform_job_id: String,
    pub transform_stage: String,
    pub publish_subject_id: String,
    pub dataset_id: String,
    pub dataset_version: String,
    pub published_artifact: DatasetPublishedArtifactInput,
    pub toolchain: String,
    pub parameters_ref: String,
    pub parameters_hash: String,
    pub determinism_profile: String,
    pub runtime_env_ref: Option<String>,
    pub runtime_env_hash: Option<String>,
    pub transform_spec_ref: String,
    pub transform_spec_hash: String,
    pub chunking_profile_ref: Option<String>,
    pub chunking_profile_hash: Option<String>,
    pub execution_manifest_ref: Option<String>,
    pub execution_manifest_hash: Option<String>,
    pub runtime_manifest_ref: Option<String>,
    pub runtime_manifest_hash: Option<String>,
    pub checkpoint_id: String,
    pub checkpoint_time_window: String,
    pub verification_policy_id: String,
    pub stac_refs: Vec<String>,
    pub reward_context_ref: Option<String>,
    pub reward_context_hash: Option<String>,
    pub reward_eligible: bool,
    pub trust_policy: TrustPolicy,
    pub verification_mode: VerificationMode,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct DatasetBootstrapWorkflowInput {
    #[serde(flatten)]
    pub common: DatasetWorkflowCommonInput,
    pub bootstrap_origin_label: String,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct DatasetIncrementalWorkflowInput {
    #[serde(flatten)]
    pub common: DatasetWorkflowCommonInput,
    pub prior_bundle: DatasetProvenanceBundle,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct DatasetProvenanceBundle {
    pub source_manifest: SourceManifest,
    pub source_manifest_ref: String,
    pub source_manifest_hash: String,
    pub transform_envelope: TransformEnvelope,
    pub checkpoint_manifest: CheckpointManifest,
    pub checkpoint_manifest_ref: String,
    pub checkpoint_manifest_hash: String,
    pub publish_envelope: PublishEnvelope,
    pub verification_report: VerificationReport,
    pub lineage_envelopes: Vec<String>,
    pub signature_bundle: Vec<String>,
    pub store_ref: String,
    pub snapshot_id: Option<String>,
    pub commit_id: Option<String>,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum DatasetWorkflowMode {
    Bootstrap,
    Incremental,
}

impl DatasetWorkflowMode {
    fn start_mode(self) -> &'static str {
        match self {
            Self::Bootstrap => "dataset_bootstrap",
            Self::Incremental => "dataset_incremental",
        }
    }
}

fn validate_envelope<E>(
    result: Result<(), Vec<String>>,
    context: &'static str,
    _envelope: &E,
) -> TrazaeoResult<()> {
    result.map_err(|errors| TrazaeoError::validation(context, errors))
}

fn manifest_hash_hex(manifest: &SourceManifest) -> String {
    let payload =
        serde_json::to_vec(manifest).expect("source manifest serialization should succeed");
    hex::encode(blake3::hash(&payload).as_bytes())
}

fn source_manifest_ref(manifest: &SourceManifest) -> String {
    format!("manifest://{}", manifest.manifest_id)
}

fn checkpoint_manifest_ref(dataset_id: &str, dataset_version: &str, checkpoint_id: &str) -> String {
    format!("checkpoint://{dataset_id}/{dataset_version}/{checkpoint_id}")
}

fn serialized_attestation(attestation: &Attestation) -> String {
    serde_json::to_string(attestation).expect("attestation serialization should succeed")
}

fn signed_seed_attestation(
    signer_id: &str,
    signing_key_hex: &str,
    signed_at: &str,
) -> TrazaeoResult<Attestation> {
    let seed = make_attestation(signer_id, signing_key_hex, signed_at, b"")?;
    Ok(Attestation {
        signer_id: signer_id.to_string(),
        key_id: seed.key_id,
        signature: String::new(),
        signed_at: signed_at.to_string(),
    })
}

fn sign_transform_envelope(
    mut input: TransformStageInput,
    signer_id: &str,
    signing_key_hex: &str,
    signed_at: &str,
) -> TrazaeoResult<TransformEnvelope> {
    let seed = signed_seed_attestation(signer_id, signing_key_hex, signed_at)?;
    input.key_id = seed.key_id.clone();
    let mut envelope = build_transform_envelope(&input, seed);
    let attestation = make_attestation(
        signer_id,
        signing_key_hex,
        signed_at,
        &envelope.canonical_attestation_payload_bytes(),
    )?;
    envelope.key_id = attestation.key_id.clone();
    envelope.attestations = vec![attestation];
    validate_envelope(
        envelope.validate(),
        "validate dataset transform envelope",
        &envelope,
    )?;
    Ok(envelope)
}

fn sign_publish_envelope(
    mut input: PublishInput,
    signer_id: &str,
    signing_key_hex: &str,
    signed_at: &str,
) -> TrazaeoResult<PublishEnvelope> {
    let seed = signed_seed_attestation(signer_id, signing_key_hex, signed_at)?;
    input.key_id = seed.key_id.clone();
    let mut envelope = build_publish_envelope(&input, seed);
    let attestation = make_attestation(
        signer_id,
        signing_key_hex,
        signed_at,
        &envelope.canonical_attestation_payload_bytes(),
    )?;
    envelope.key_id = attestation.key_id.clone();
    envelope.attestations = vec![attestation];
    validate_envelope(
        envelope.validate(),
        "validate dataset publish envelope",
        &envelope,
    )?;
    Ok(envelope)
}

fn signed_checkpoint_manifest(
    checkpoint_id: &str,
    checkpoint_time_window: &str,
    prior_checkpoint_ref: Option<&str>,
    published_artifacts: Vec<CheckpointArtifact>,
    lineage_refs: Vec<String>,
    signer_id: &str,
    signing_key_hex: &str,
    signed_at: &str,
) -> TrazaeoResult<CheckpointManifest> {
    let mut manifest = build_checkpoint_manifest(
        checkpoint_id,
        checkpoint_time_window,
        prior_checkpoint_ref,
        Vec::new(),
        published_artifacts,
        lineage_refs,
    );
    let attestation = make_attestation(
        signer_id,
        signing_key_hex,
        signed_at,
        &canonical_checkpoint_manifest_payload_bytes(&manifest),
    )?;
    manifest.checkpoint_signature_bundle = vec![serialized_attestation(&attestation)];
    crate::checkpoint::validate_checkpoint_manifest(&manifest)?;
    Ok(manifest)
}

fn build_source_manifest(
    manifest_id: &str,
    issued_at: &str,
    source_dataset_id: &str,
    source_files: Vec<SourceFileEntry>,
) -> TrazaeoResult<SourceManifest> {
    let manifest = SourceManifest {
        manifest_id: manifest_id.to_string(),
        manifest_created_at: issued_at.to_string(),
        source_dataset_id: source_dataset_id.to_string(),
        source_file_count: source_files.len(),
        source_root_hash: hex::encode(compute_source_root_hash(&source_files).0),
        source_files,
    };
    validate_source_manifest(&manifest)?;
    Ok(manifest)
}

fn primary_artifact(envelope: &PublishEnvelope) -> TrazaeoResult<&CheckpointArtifact> {
    envelope
        .published_artifacts
        .iter()
        .find(|artifact| artifact.artifact_id == envelope.primary_artifact_id)
        .ok_or_else(|| {
            TrazaeoError::invalid_input(
                "dataset workflow primary artifact",
                "publish_envelope primary_artifact_id not found in published_artifacts",
            )
        })
}

fn lineage_signature_bundle(lineage_envelopes: &[String]) -> TrazaeoResult<Vec<String>> {
    let mut signatures = Vec::new();
    for envelope_json in lineage_envelopes {
        let value: serde_json::Value = serde_json::from_str(envelope_json).map_err(|err| {
            TrazaeoError::serialization(
                "dataset workflow signature bundle",
                format!("invalid lineage envelope json: {err}"),
            )
        })?;
        let attestations = value
            .get("attestations")
            .and_then(serde_json::Value::as_array)
            .ok_or_else(|| {
                TrazaeoError::serialization(
                    "dataset workflow signature bundle",
                    "lineage envelope missing attestations",
                )
            })?;
        for attestation in attestations {
            signatures.push(serde_json::to_string(attestation).map_err(|err| {
                TrazaeoError::serialization(
                    "dataset workflow signature bundle",
                    format!("failed to serialize attestation: {err}"),
                )
            })?);
        }
    }
    Ok(signatures)
}

fn checkpoint_artifact(input: &DatasetPublishedArtifactInput) -> CheckpointArtifact {
    CheckpointArtifact {
        artifact_id: input.artifact_id.clone(),
        content_root_hash: input.content_root_hash.clone(),
        content_descriptor_ref: input.content_descriptor_ref.clone(),
        content_descriptor_hash: input.content_descriptor_hash.clone(),
        media_type: input.media_type.clone(),
    }
}

fn build_bundle(
    common: DatasetWorkflowCommonInput,
    mode: DatasetWorkflowMode,
    bootstrap_origin_label: Option<String>,
    prior_bundle: Option<DatasetProvenanceBundle>,
) -> TrazaeoResult<DatasetProvenanceBundle> {
    let source_manifest = build_source_manifest(
        &common.source_manifest_id,
        &common.issued_at,
        &common.source_dataset_id,
        common.source_files,
    )?;
    let source_manifest_ref = source_manifest_ref(&source_manifest);
    let source_manifest_hash = manifest_hash_hex(&source_manifest);

    let mut input_refs = vec![format!("source://{}", common.source_dataset_id)];
    let mut input_artifacts = vec![ArtifactRecord {
        artifact_id: SOURCE_ARTIFACT_ID.to_string(),
        artifact_ref: source_manifest_ref.clone(),
        content_root_hash: source_manifest.source_root_hash.clone(),
    }];
    let mut lineage_refs = Vec::new();
    let mut lineage_envelopes = Vec::new();
    let mut prior_checkpoint_ref = None;

    if let Some(prior) = prior_bundle.as_ref() {
        let prior_artifact = primary_artifact(&prior.publish_envelope)?;
        input_refs.push(prior.store_ref.clone());
        input_artifacts.push(ArtifactRecord {
            artifact_id: prior_artifact.artifact_id.clone(),
            artifact_ref: prior.store_ref.clone(),
            content_root_hash: prior_artifact.content_root_hash.clone(),
        });
        lineage_refs.push(format!("publish://{}", prior.publish_envelope.subject_id));
        lineage_envelopes.push(
            serde_json::to_string(&prior.publish_envelope).expect("publish envelope serialization"),
        );
        prior_checkpoint_ref = Some(prior.checkpoint_manifest_ref.as_str());
    }

    let output_artifacts = vec![ArtifactRecord {
        artifact_id: common.published_artifact.artifact_id.clone(),
        artifact_ref: common.published_artifact.artifact_ref.clone(),
        content_root_hash: common.published_artifact.content_root_hash.clone(),
    }];

    let transform_envelope = sign_transform_envelope(
        TransformStageInput {
            schema_version: common.schema_version.clone(),
            issued_at: common.issued_at.clone(),
            subject_id: common.transform_subject_id.clone(),
            transform_job_id: common.transform_job_id.clone(),
            transform_stage: common.transform_stage.clone(),
            input_refs,
            output_refs: vec![common.published_artifact.artifact_ref.clone()],
            input_artifacts,
            output_artifacts,
            toolchain: common.toolchain.clone(),
            parameters_ref: common.parameters_ref.clone(),
            parameters_hash: common.parameters_hash.clone(),
            determinism_profile: common.determinism_profile.clone(),
            runtime_env_ref: common.runtime_env_ref.clone(),
            runtime_env_hash: common.runtime_env_hash.clone(),
            provenance_start_mode: mode.start_mode().to_string(),
            source_manifest_ref: Some(source_manifest_ref.clone()),
            source_manifest_hash: Some(source_manifest_hash.clone()),
            source_root_hash: Some(source_manifest.source_root_hash.clone()),
            transform_spec_ref: Some(common.transform_spec_ref.clone()),
            transform_spec_hash: Some(common.transform_spec_hash.clone()),
            chunking_profile_ref: common.chunking_profile_ref.clone(),
            chunking_profile_hash: common.chunking_profile_hash.clone(),
            execution_manifest_ref: common.execution_manifest_ref.clone(),
            execution_manifest_hash: common.execution_manifest_hash.clone(),
            runtime_manifest_ref: common.runtime_manifest_ref.clone(),
            runtime_manifest_hash: common.runtime_manifest_hash.clone(),
            key_id: String::new(),
        },
        &common.signer_id,
        &common.signing_key_hex,
        &common.signed_at,
    )?;

    lineage_refs.push(format!("transform://{}", transform_envelope.subject_id));
    lineage_envelopes.push(
        serde_json::to_string(&transform_envelope).expect("transform envelope serialization"),
    );

    let checkpoint_manifest = signed_checkpoint_manifest(
        &common.checkpoint_id,
        &common.checkpoint_time_window,
        prior_checkpoint_ref,
        vec![checkpoint_artifact(&common.published_artifact)],
        lineage_refs.clone(),
        &common.signer_id,
        &common.signing_key_hex,
        &common.signed_at,
    )?;
    let checkpoint_manifest_ref = checkpoint_manifest_ref(
        &common.dataset_id,
        &common.dataset_version,
        &common.checkpoint_id,
    );
    let checkpoint_manifest_hash =
        hex::encode(compute_checkpoint_binding_hash(&checkpoint_manifest).0);

    let publish_envelope = sign_publish_envelope(
        PublishInput {
            schema_version: common.schema_version.clone(),
            issued_at: common.issued_at.clone(),
            subject_id: common.publish_subject_id.clone(),
            dataset_id: common.dataset_id.clone(),
            dataset_version: common.dataset_version.clone(),
            input_refs: vec![common.published_artifact.artifact_ref.clone()],
            output_refs: vec![common.published_artifact.artifact_ref.clone()],
            published_artifacts: vec![checkpoint_artifact(&common.published_artifact)],
            primary_artifact_id: common.published_artifact.artifact_id.clone(),
            checkpoint_manifest_ref: checkpoint_manifest_ref.clone(),
            checkpoint_manifest_hash: checkpoint_manifest_hash.clone(),
            checkpoint_id: common.checkpoint_id.clone(),
            checkpoint_log_root_hash: checkpoint_manifest.checkpoint_log_root_hash.clone(),
            lineage_refs,
            verification_policy_id: common.verification_policy_id.clone(),
            key_id: String::new(),
            stac_refs: common.stac_refs.clone(),
            reward_context_ref: common.reward_context_ref.clone(),
            reward_context_hash: common.reward_context_hash.clone(),
            provenance_start_mode: mode.start_mode().to_string(),
            bootstrap_origin_label,
            reward_eligible: common.reward_eligible,
        },
        &common.signer_id,
        &common.signing_key_hex,
        &common.signed_at,
    )?;

    let verification_report = verify_publish_envelope(
        &publish_envelope,
        common.verification_mode,
        &common.trust_policy,
    );
    let signature_bundle = lineage_signature_bundle(&lineage_envelopes)?;

    Ok(DatasetProvenanceBundle {
        source_manifest,
        source_manifest_ref,
        source_manifest_hash,
        transform_envelope,
        checkpoint_manifest,
        checkpoint_manifest_ref,
        checkpoint_manifest_hash,
        publish_envelope,
        verification_report,
        lineage_envelopes,
        signature_bundle,
        store_ref: common.published_artifact.artifact_ref.clone(),
        snapshot_id: common.published_artifact.snapshot_id,
        commit_id: common.published_artifact.commit_id,
    })
}

pub fn build_dataset_bootstrap_bundle(
    input: DatasetBootstrapWorkflowInput,
) -> TrazaeoResult<DatasetProvenanceBundle> {
    build_bundle(
        input.common,
        DatasetWorkflowMode::Bootstrap,
        Some(input.bootstrap_origin_label),
        None,
    )
}

pub fn build_dataset_incremental_bundle(
    input: DatasetIncrementalWorkflowInput,
) -> TrazaeoResult<DatasetProvenanceBundle> {
    build_bundle(
        input.common,
        DatasetWorkflowMode::Incremental,
        None,
        Some(input.prior_bundle),
    )
}

pub fn build_dataset_delivery_proof_package(
    bundle: &DatasetProvenanceBundle,
    range_proof_package: &RangeProofPackage,
) -> TrazaeoResult<DeliveryProofPackage> {
    build_delivery_proof_package(
        range_proof_package,
        bundle.lineage_envelopes.clone(),
        bundle.checkpoint_manifest.clone(),
        bundle.signature_bundle.clone(),
    )
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::content::{
        build_content_descriptor, build_full_root_proof_package, compute_content_descriptor_hash,
        ContentDescriptorInput,
    };
    use crate::utils::Hash;
    use crate::verification::{is_report_success, verify_publish_envelope, VerificationState};

    const TEST_SIGNING_KEY_HEX: &str =
        "4f3edf983ac636a65a842ce7c78d9aa706d3b113bce036f9a4f5762b76f70f18";

    fn trust_policy_for(key_id: &str) -> TrustPolicy {
        let mut policy = TrustPolicy::new();
        policy.allow_key(key_id);
        policy
    }

    fn source_files() -> Vec<SourceFileEntry> {
        vec![SourceFileEntry {
            source_uri: "s3://bucket/a.nc".to_string(),
            content_hash: "abcd".repeat(16),
            byte_length: 128,
            observed_mtime: Some("2026-01-01T00:00:00Z".to_string()),
        }]
    }

    fn artifact_input(version: &str, root: &str) -> DatasetPublishedArtifactInput {
        DatasetPublishedArtifactInput {
            artifact_id: format!("artifact-{version}"),
            artifact_ref: format!("icechunk://eo/sst-{version}.zarr"),
            content_root_hash: root.to_string(),
            media_type: "application/vnd+zarr".to_string(),
            content_descriptor_ref: Some(format!("descriptor://sst-{version}")),
            content_descriptor_hash: Some("beef".repeat(16)),
            snapshot_id: Some(format!("snap-{version}")),
            commit_id: Some(format!("commit-{version}")),
        }
    }

    fn common_input(version: &str, root: &str) -> DatasetWorkflowCommonInput {
        DatasetWorkflowCommonInput {
            schema_version: "1.0.0".to_string(),
            issued_at: "2026-01-01T00:00:00Z".to_string(),
            signed_at: "2026-01-01T00:00:00Z".to_string(),
            signer_id: "publisher".to_string(),
            signing_key_hex: TEST_SIGNING_KEY_HEX.to_string(),
            source_manifest_id: format!("sst-{version}-manifest"),
            source_dataset_id: "sst".to_string(),
            source_files: source_files(),
            transform_subject_id: format!("transform-sst-{version}"),
            transform_job_id: format!("job-sst-{version}"),
            transform_stage: "nc_collection_to_icechunk".to_string(),
            publish_subject_id: format!("publish-sst-{version}"),
            dataset_id: "sst".to_string(),
            dataset_version: version.to_string(),
            published_artifact: artifact_input(version, root),
            toolchain: "python-xarray".to_string(),
            parameters_ref: "params://nc-to-icechunk-v1".to_string(),
            parameters_hash: "1234".repeat(16),
            determinism_profile: "strict-v1".to_string(),
            runtime_env_ref: Some("oci://eo/runtime".to_string()),
            runtime_env_hash: Some("3456".repeat(16)),
            transform_spec_ref: "spec://eo/nc-to-icechunk-v1".to_string(),
            transform_spec_hash: "5678".repeat(16),
            chunking_profile_ref: Some("chunk://eo/default-v1".to_string()),
            chunking_profile_hash: Some("7890".repeat(16)),
            execution_manifest_ref: None,
            execution_manifest_hash: None,
            runtime_manifest_ref: None,
            runtime_manifest_hash: None,
            checkpoint_id: format!("checkpoint-sst-{version}"),
            checkpoint_time_window: "2026-01-01T00:00:00Z/2026-01-01T01:00:00Z".to_string(),
            verification_policy_id: "verify-default".to_string(),
            stac_refs: vec![],
            reward_context_ref: None,
            reward_context_hash: None,
            reward_eligible: false,
            trust_policy: trust_policy_for(
                &make_attestation(
                    "publisher",
                    TEST_SIGNING_KEY_HEX,
                    "2026-01-01T00:00:00Z",
                    b"",
                )
                .expect("seed")
                .key_id,
            ),
            verification_mode: VerificationMode::Full,
        }
    }

    #[test]
    fn dataset_bootstrap_bundle_builds_signed_artifacts() {
        let bundle = build_dataset_bootstrap_bundle(DatasetBootstrapWorkflowInput {
            common: common_input("v1", &"aa".repeat(32)),
            bootstrap_origin_label: "nc-collection:sst".to_string(),
        })
        .expect("bootstrap bundle");

        assert_eq!(
            bundle.publish_envelope.provenance_start_mode,
            "dataset_bootstrap"
        );
        assert_eq!(
            bundle.transform_envelope.provenance_start_mode,
            "dataset_bootstrap"
        );
        assert_eq!(
            bundle.publish_envelope.bootstrap_origin_label.as_deref(),
            Some("nc-collection:sst")
        );
        assert_eq!(
            bundle.checkpoint_manifest.lineage_refs,
            vec!["transform://transform-sst-v1"]
        );
        assert_eq!(bundle.lineage_envelopes.len(), 1);
        assert_eq!(bundle.signature_bundle.len(), 1);
        assert!(is_report_success(&bundle.verification_report));
    }

    #[test]
    fn dataset_incremental_bundle_chains_prior_publish_state() {
        let bootstrap = build_dataset_bootstrap_bundle(DatasetBootstrapWorkflowInput {
            common: common_input("v1", &"aa".repeat(32)),
            bootstrap_origin_label: "nc-collection:sst".to_string(),
        })
        .expect("bootstrap bundle");

        let incremental = build_dataset_incremental_bundle(DatasetIncrementalWorkflowInput {
            common: common_input("v2", &"bb".repeat(32)),
            prior_bundle: bootstrap.clone(),
        })
        .expect("incremental bundle");

        assert_eq!(
            incremental.publish_envelope.provenance_start_mode,
            "dataset_incremental"
        );
        assert_eq!(
            incremental.transform_envelope.provenance_start_mode,
            "dataset_incremental"
        );
        assert_eq!(
            incremental
                .checkpoint_manifest
                .prior_checkpoint_ref
                .as_deref(),
            Some(bootstrap.checkpoint_manifest_ref.as_str())
        );
        assert_eq!(
            incremental.publish_envelope.lineage_refs,
            vec![
                format!("publish://{}", bootstrap.publish_envelope.subject_id),
                "transform://transform-sst-v2".to_string()
            ]
        );
        assert_eq!(incremental.lineage_envelopes.len(), 2);
        assert_eq!(incremental.signature_bundle.len(), 2);
    }

    #[test]
    fn dataset_bundle_builds_delivery_proof_package() {
        let root = Hash(*blake3::hash(b"dataset-bytes").as_bytes());
        let descriptor = build_content_descriptor(ContentDescriptorInput {
            artifact_id: "artifact-v1",
            root,
            chunk_size: 4,
            leaf_count: 3,
            byte_length: 13,
            media_type: "application/vnd+zarr",
            created_at: "2026-01-01T00:00:00Z",
        });
        let descriptor_hash = hex::encode(compute_content_descriptor_hash(&descriptor).0);
        let mut common = common_input("v1", &descriptor.content_root_hash);
        common.published_artifact.content_descriptor_hash = Some(descriptor_hash);
        let bundle = build_dataset_bootstrap_bundle(DatasetBootstrapWorkflowInput {
            common,
            bootstrap_origin_label: "nc-collection:sst".to_string(),
        })
        .expect("bundle");
        let proof = build_full_root_proof_package(&descriptor).expect("proof package");
        let delivery = build_dataset_delivery_proof_package(&bundle, &proof).expect("delivery");

        assert_eq!(delivery.artifact_id, "artifact-v1");
        assert_eq!(
            delivery.checkpoint_manifest.checkpoint_id,
            "checkpoint-sst-v1"
        );
        assert_eq!(delivery.lineage_envelopes.len(), 1);
        assert_eq!(delivery.signature_bundle.len(), 1);
    }

    #[test]
    fn bootstrap_bundle_verifies_publish_envelope() {
        let bundle = build_dataset_bootstrap_bundle(DatasetBootstrapWorkflowInput {
            common: common_input("v1", &"aa".repeat(32)),
            bootstrap_origin_label: "nc-collection:sst".to_string(),
        })
        .expect("bundle");
        let report = verify_publish_envelope(
            &bundle.publish_envelope,
            VerificationMode::Full,
            &trust_policy_for(&bundle.publish_envelope.key_id),
        );
        assert_eq!(report.signature_state, VerificationState::Passed);
        assert_eq!(report.lineage_state, VerificationState::Passed);
    }
}
