use crate::checkpoint::{
    build_checkpoint_inclusion_proof_for_artifact, canonical_checkpoint_manifest_payload_bytes,
    checkpoint_leaf_hash, compute_checkpoint_binding_hash, validate_checkpoint_manifest,
    verify_checkpoint_inclusion, CheckpointInclusionProof, CheckpointManifest,
};
#[cfg(test)]
use crate::checkpoint::{build_checkpoint_manifest, CheckpointArtifact};
use crate::content::{
    compute_content_descriptor_hash, validate_content_descriptor, verify_full_root_proof_package,
    ContentDescriptor, RangeProofPackage, VerifiedRange, CONTENT_PROOF_TYPE_FULL_ROOT_V1,
};
#[cfg(feature = "bao-range-proofs")]
use crate::content::{verify_bao_range_proof_package, CONTENT_PROOF_TYPE_BAO_OUTBOARD_V1};
use crate::envelope::{
    verify_attestation, Attestation, CaptureEnvelope, PublishEnvelope, TransformEnvelope,
};
use crate::error::{TrazaeoError, TrazaeoResult};
use crate::utils::Hash;
use crate::verification::VerificationState;
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct DeliveryProofPackage {
    pub proof_version: String,
    pub artifact_id: String,
    pub content_descriptor: ContentDescriptor,
    pub content_proof_type: String,
    pub content_proof_bytes: Vec<u8>,
    pub verified_ranges: Vec<VerifiedRange>,
    pub lineage_envelopes: Vec<String>,
    pub checkpoint_manifest: CheckpointManifest,
    pub checkpoint_inclusion_proof: CheckpointInclusionProof,
    pub signature_bundle: Vec<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct DeliveryProofVerificationReport {
    pub package_state: VerificationState,
    pub signature_state: VerificationState,
    pub trust_state: VerificationState,
    pub binding_state: VerificationState,
    pub reproducibility_state: VerificationState,
    pub lineage_state: VerificationState,
    pub content_state: VerificationState,
    pub checkpoint_state: VerificationState,
    pub storage_state: VerificationState,
    pub reasons: Vec<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct DeliveryProofVerificationResult {
    pub report: DeliveryProofVerificationReport,
    pub verified_bytes: Option<Vec<u8>>,
}

#[derive(Debug, Clone)]
enum ParsedLineageEnvelope {
    Capture(CaptureEnvelope),
    Transform(TransformEnvelope),
    Publish(PublishEnvelope),
}

impl ParsedLineageEnvelope {
    fn envelope_type(&self) -> &'static str {
        match self {
            Self::Capture(_) => "capture",
            Self::Transform(_) => "transform",
            Self::Publish(_) => "publish",
        }
    }

    fn subject_id(&self) -> &str {
        match self {
            Self::Capture(envelope) => &envelope.subject_id,
            Self::Transform(envelope) => &envelope.subject_id,
            Self::Publish(envelope) => &envelope.subject_id,
        }
    }

    fn lineage_ref(&self) -> String {
        format!("{}://{}", self.envelope_type(), self.subject_id())
    }

    fn attestations(&self) -> &[Attestation] {
        match self {
            Self::Capture(envelope) => &envelope.attestations,
            Self::Transform(envelope) => &envelope.attestations,
            Self::Publish(envelope) => &envelope.attestations,
        }
    }

    fn canonical_attestation_payload_bytes(&self) -> Vec<u8> {
        match self {
            Self::Capture(envelope) => envelope.canonical_attestation_payload_bytes(),
            Self::Transform(envelope) => envelope.canonical_attestation_payload_bytes(),
            Self::Publish(envelope) => envelope.canonical_attestation_payload_bytes(),
        }
    }

    fn validate(&self) -> Result<(), Vec<String>> {
        match self {
            Self::Capture(envelope) => envelope.validate(),
            Self::Transform(envelope) => envelope.validate(),
            Self::Publish(envelope) => envelope.validate(),
        }
    }
}

/// Builds delivery proof package.
pub fn build_delivery_proof_package(
    range_proof_package: &RangeProofPackage,
    lineage_envelopes: Vec<String>,
    checkpoint_manifest: CheckpointManifest,
    signature_bundle: Vec<String>,
) -> TrazaeoResult<DeliveryProofPackage> {
    validate_content_descriptor(&range_proof_package.content_descriptor)?;
    validate_checkpoint_manifest(&checkpoint_manifest)?;
    let checkpoint_inclusion_proof = build_checkpoint_inclusion_proof_for_artifact(
        &checkpoint_manifest,
        &range_proof_package.artifact_id,
    )?;
    Ok(DeliveryProofPackage {
        proof_version: "1.0.0".to_string(),
        artifact_id: range_proof_package.artifact_id.clone(),
        content_descriptor: range_proof_package.content_descriptor.clone(),
        content_proof_type: range_proof_package.content_proof_type.clone(),
        content_proof_bytes: range_proof_package.content_proof_bytes.clone(),
        verified_ranges: range_proof_package.verified_ranges.clone(),
        lineage_envelopes,
        checkpoint_manifest,
        checkpoint_inclusion_proof,
        signature_bundle,
    })
}

fn validate_ref_hash_pair_local(
    reasons: &mut Vec<String>,
    ref_name: &str,
    hash_name: &str,
    ref_value: &Option<String>,
    hash_value: &Option<String>,
) {
    let has_ref = ref_value
        .as_ref()
        .is_some_and(|value| !value.trim().is_empty());
    let has_hash = hash_value
        .as_ref()
        .is_some_and(|value| !value.trim().is_empty());
    if has_ref ^ has_hash {
        reasons.push(format!("{ref_name} and {hash_name} must be set together"));
    }
}

fn parse_attestation_json(raw: &str, context: &'static str) -> TrazaeoResult<Attestation> {
    serde_json::from_str(raw)
        .map_err(|_| TrazaeoError::serialization(context, "malformed serialized attestation entry"))
}

fn parse_lineage_envelope(raw: &str) -> TrazaeoResult<ParsedLineageEnvelope> {
    let value: serde_json::Value = serde_json::from_str(raw).map_err(|_| {
        TrazaeoError::serialization(
            "parse lineage envelope",
            "lineage_envelopes entries must be serialized envelope JSON",
        )
    })?;
    let envelope_type = value
        .get("envelope_type")
        .and_then(serde_json::Value::as_str)
        .ok_or_else(|| {
            TrazaeoError::invalid_input(
                "parse lineage envelope",
                "lineage envelope missing envelope_type",
            )
        })?;
    match envelope_type {
        "capture" => Ok(ParsedLineageEnvelope::Capture(
            serde_json::from_value(value).map_err(|_| {
                TrazaeoError::serialization(
                    "parse lineage envelope",
                    "invalid capture envelope payload",
                )
            })?,
        )),
        "transform" => Ok(ParsedLineageEnvelope::Transform(
            serde_json::from_value(value).map_err(|_| {
                TrazaeoError::serialization(
                    "parse lineage envelope",
                    "invalid transform envelope payload",
                )
            })?,
        )),
        "publish" => Ok(ParsedLineageEnvelope::Publish(
            serde_json::from_value(value).map_err(|_| {
                TrazaeoError::serialization(
                    "parse lineage envelope",
                    "invalid publish envelope payload",
                )
            })?,
        )),
        _ => Err(TrazaeoError::invalid_input(
            "parse lineage envelope",
            "unsupported lineage envelope type",
        )),
    }
}

fn parse_lineage_envelopes(
    lineage_envelopes: &[String],
    reasons: &mut Vec<String>,
) -> Option<Vec<ParsedLineageEnvelope>> {
    if lineage_envelopes.is_empty() {
        reasons.push("lineage_envelopes must not be empty".to_string());
        return None;
    }
    let mut parsed = Vec::with_capacity(lineage_envelopes.len());
    for envelope_json in lineage_envelopes {
        match parse_lineage_envelope(envelope_json) {
            Ok(envelope) => match envelope.validate() {
                Ok(()) => parsed.push(envelope),
                Err(errors) => {
                    reasons.extend(errors);
                    return None;
                }
            },
            Err(err) => {
                reasons.push(err.to_string());
                return None;
            }
        }
    }
    Some(parsed)
}

fn verify_lineage_signatures(
    parsed_lineage: Option<&[ParsedLineageEnvelope]>,
    signature_bundle: &[String],
    reasons: &mut Vec<String>,
) -> VerificationState {
    let Some(parsed_lineage) = parsed_lineage else {
        reasons.push(
            "unable to verify lineage signatures without valid lineage envelopes".to_string(),
        );
        return VerificationState::Failed;
    };
    if signature_bundle.is_empty() {
        reasons.push("signature_bundle must not be empty".to_string());
        return VerificationState::Failed;
    }
    let mut provided_bundle = Vec::with_capacity(signature_bundle.len());
    for entry in signature_bundle {
        match parse_attestation_json(entry, "verify delivery proof package") {
            Ok(attestation) => provided_bundle.push(attestation),
            Err(err) => {
                reasons.push(err.to_string());
                return VerificationState::Failed;
            }
        }
    }
    let expected_bundle = parsed_lineage
        .iter()
        .flat_map(|envelope| envelope.attestations().iter().cloned())
        .collect::<Vec<_>>();
    if provided_bundle != expected_bundle {
        reasons.push(
            "signature_bundle must exactly match the attestations embedded in lineage_envelopes"
                .to_string(),
        );
        return VerificationState::Failed;
    }
    for envelope in parsed_lineage {
        if envelope.attestations().is_empty() {
            reasons.push("lineage envelopes must contain attestations".to_string());
            return VerificationState::Failed;
        }
        let payload = envelope.canonical_attestation_payload_bytes();
        if !envelope
            .attestations()
            .iter()
            .all(|attestation| verify_attestation(attestation, &payload))
        {
            reasons.push("invalid lineage envelope attestation signature".to_string());
            return VerificationState::Failed;
        }
    }
    VerificationState::Passed
}

fn verify_checkpoint_signatures(
    checkpoint_manifest: &CheckpointManifest,
    reasons: &mut Vec<String>,
) -> VerificationState {
    if checkpoint_manifest.checkpoint_signature_bundle.is_empty() {
        reasons.push("checkpoint_signature_bundle must not be empty".to_string());
        return VerificationState::Failed;
    }
    let payload = canonical_checkpoint_manifest_payload_bytes(checkpoint_manifest);
    for entry in &checkpoint_manifest.checkpoint_signature_bundle {
        let attestation = match parse_attestation_json(entry, "verify delivery proof package") {
            Ok(attestation) => attestation,
            Err(err) => {
                reasons.push(err.to_string());
                return VerificationState::Failed;
            }
        };
        if !verify_attestation(&attestation, &payload) {
            reasons.push("invalid checkpoint manifest signature".to_string());
            return VerificationState::Failed;
        }
    }
    VerificationState::Passed
}

fn verify_binding_state(
    package: &DeliveryProofPackage,
    reasons: &mut Vec<String>,
) -> VerificationState {
    let mut ok = true;
    if package.artifact_id != package.content_descriptor.artifact_id {
        reasons.push("artifact_id must match content_descriptor.artifact_id".to_string());
        ok = false;
    }

    let mut pair_errors = Vec::new();
    validate_ref_hash_pair_local(
        &mut pair_errors,
        "outboard_ref",
        "outboard_hash",
        &package.content_descriptor.outboard_ref,
        &package.content_descriptor.outboard_hash,
    );
    if !pair_errors.is_empty() {
        reasons.extend(pair_errors);
        ok = false;
    }

    let matching_artifacts = package
        .checkpoint_manifest
        .published_artifacts
        .iter()
        .filter(|artifact| artifact.artifact_id == package.artifact_id)
        .collect::<Vec<_>>();
    if matching_artifacts.len() != 1 {
        reasons.push("checkpoint_manifest must contain exactly one published_artifacts entry for artifact_id".to_string());
        ok = false;
    }
    if let Some(artifact) = matching_artifacts.first() {
        if artifact.content_root_hash != package.content_descriptor.content_root_hash {
            reasons.push("published_artifact content_root_hash must match content_descriptor.content_root_hash".to_string());
            ok = false;
        }
        if artifact.media_type != package.content_descriptor.media_type {
            reasons.push(
                "published_artifact media_type must match content_descriptor.media_type"
                    .to_string(),
            );
            ok = false;
        }
        let mut artifact_pair_errors = Vec::new();
        validate_ref_hash_pair_local(
            &mut artifact_pair_errors,
            "content_descriptor_ref",
            "content_descriptor_hash",
            &artifact.content_descriptor_ref,
            &artifact.content_descriptor_hash,
        );
        if !artifact_pair_errors.is_empty() {
            reasons.extend(artifact_pair_errors);
            ok = false;
        }
        if artifact.content_descriptor_ref.is_some() || artifact.content_descriptor_hash.is_some() {
            let expected_descriptor_hash =
                hex::encode(compute_content_descriptor_hash(&package.content_descriptor).0);
            if artifact.content_descriptor_hash.as_deref()
                != Some(expected_descriptor_hash.as_str())
            {
                reasons.push(
                    "content_descriptor_hash must match the canonical hash of content_descriptor"
                        .to_string(),
                );
                ok = false;
            }
        }
    }

    let expected_manifest_hash =
        hex::encode(compute_checkpoint_binding_hash(&package.checkpoint_manifest).0);
    let expected_leaf_hash =
        hex::encode(checkpoint_leaf_hash(&package.artifact_id, &expected_manifest_hash).0);
    if package.checkpoint_inclusion_proof.leaf_hash != expected_leaf_hash {
        reasons.push(
            "checkpoint_inclusion_proof.leaf_hash must match artifact_id bound to checkpoint manifest"
                .to_string(),
        );
        ok = false;
    }

    if ok {
        VerificationState::Passed
    } else {
        VerificationState::Failed
    }
}

fn verify_lineage_state(
    checkpoint_manifest: &CheckpointManifest,
    parsed_lineage: Option<&[ParsedLineageEnvelope]>,
    reasons: &mut Vec<String>,
) -> VerificationState {
    let Some(parsed_lineage) = parsed_lineage else {
        reasons.push(
            "lineage completeness could not be evaluated because lineage_envelopes are invalid"
                .to_string(),
        );
        return VerificationState::Failed;
    };
    let derived_refs = parsed_lineage
        .iter()
        .map(ParsedLineageEnvelope::lineage_ref)
        .collect::<Vec<_>>();
    if derived_refs != checkpoint_manifest.lineage_refs {
        reasons.push(
            "lineage_envelopes must exactly cover checkpoint_manifest.lineage_refs in order"
                .to_string(),
        );
        VerificationState::Failed
    } else {
        VerificationState::Passed
    }
}

fn verify_content_state(
    package: &DeliveryProofPackage,
    artifact_bytes: Option<&[u8]>,
    reasons: &mut Vec<String>,
) -> (VerificationState, Option<Vec<u8>>) {
    let range_package = RangeProofPackage {
        proof_version: package.proof_version.clone(),
        artifact_id: package.artifact_id.clone(),
        content_descriptor: package.content_descriptor.clone(),
        content_proof_type: package.content_proof_type.clone(),
        content_proof_bytes: package.content_proof_bytes.clone(),
        verified_ranges: package.verified_ranges.clone(),
    };
    let verified = match package.content_proof_type.as_str() {
        CONTENT_PROOF_TYPE_FULL_ROOT_V1 => {
            let Some(artifact_bytes) = artifact_bytes else {
                reasons
                    .push("artifact bytes are required for full_root_v1 verification".to_string());
                return (VerificationState::Failed, None);
            };
            verify_full_root_proof_package(&range_package, artifact_bytes)
        }
        #[cfg(feature = "bao-range-proofs")]
        CONTENT_PROOF_TYPE_BAO_OUTBOARD_V1 => verify_bao_range_proof_package(&range_package),
        #[cfg(not(feature = "bao-range-proofs"))]
        "bao_outboard_v1" => Err(TrazaeoError::invalid_input(
            "verify delivery proof package",
            "bao_outboard_v1 verification requires the bao-range-proofs feature",
        )),
        _ => Err(TrazaeoError::invalid_input(
            "verify delivery proof package",
            "unsupported content_proof_type",
        )),
    };
    match verified {
        Ok(bytes) => (VerificationState::Passed, Some(bytes)),
        Err(err) => {
            reasons.push(err.to_string());
            (VerificationState::Failed, None)
        }
    }
}

fn verify_checkpoint_state(
    package: &DeliveryProofPackage,
    trusted_checkpoint_log_root: Option<&Hash>,
    reasons: &mut Vec<String>,
) -> VerificationState {
    let checkpoint_log_root =
        match hex::decode(&package.checkpoint_manifest.checkpoint_log_root_hash) {
            Ok(bytes) => bytes,
            Err(err) => {
                reasons.push(format!(
                    "invalid checkpoint_log_root_hash hex in checkpoint_manifest: {err}"
                ));
                return VerificationState::Failed;
            }
        };
    let checkpoint_log_root: [u8; 32] = match checkpoint_log_root.as_slice().try_into() {
        Ok(root) => root,
        Err(_) => {
            reasons.push(
                "checkpoint_manifest.checkpoint_log_root_hash must be exactly 32 bytes".to_string(),
            );
            return VerificationState::Failed;
        }
    };
    let manifest_root = Hash(checkpoint_log_root);
    if !verify_checkpoint_inclusion(&manifest_root, &package.checkpoint_inclusion_proof) {
        reasons.push("checkpoint inclusion proof does not match checkpoint_manifest.checkpoint_log_root_hash".to_string());
        return VerificationState::Failed;
    }
    if let Some(trusted_checkpoint_log_root) = trusted_checkpoint_log_root {
        if !verify_checkpoint_inclusion(
            trusted_checkpoint_log_root,
            &package.checkpoint_inclusion_proof,
        ) {
            reasons.push(
                "checkpoint inclusion proof does not match the trusted checkpoint-log root"
                    .to_string(),
            );
            return VerificationState::Failed;
        }
        VerificationState::Passed
    } else {
        reasons.push(
            "checkpoint inclusion was only verified against the self-supplied manifest root"
                .to_string(),
        );
        VerificationState::NotEvaluated
    }
}

/// Returns whether a delivery proof report satisfies baseline success criteria.
pub fn is_delivery_proof_report_success(report: &DeliveryProofVerificationReport) -> bool {
    let trust_ok = report.trust_state == VerificationState::Passed
        || report.trust_state == VerificationState::NotEvaluated;
    let repro_ok = report.reproducibility_state == VerificationState::Passed
        || report.reproducibility_state == VerificationState::NotEvaluated;
    let checkpoint_ok = report.checkpoint_state == VerificationState::Passed
        || report.checkpoint_state == VerificationState::NotEvaluated;
    let storage_ok = report.storage_state == VerificationState::Passed
        || report.storage_state == VerificationState::NotEvaluated;
    report.package_state == VerificationState::Passed
        && report.signature_state == VerificationState::Passed
        && trust_ok
        && report.binding_state == VerificationState::Passed
        && repro_ok
        && report.lineage_state == VerificationState::Passed
        && report.content_state == VerificationState::Passed
        && checkpoint_ok
        && storage_ok
}

/// Verifies delivery proof package and emits deterministic per-dimension states.
pub fn verify_delivery_proof_package_with_report(
    package: &DeliveryProofPackage,
    artifact_bytes: Option<&[u8]>,
    trusted_checkpoint_log_root: Option<&Hash>,
) -> DeliveryProofVerificationResult {
    let mut report = DeliveryProofVerificationReport {
        package_state: VerificationState::Passed,
        signature_state: VerificationState::NotEvaluated,
        trust_state: VerificationState::NotEvaluated,
        binding_state: VerificationState::NotEvaluated,
        reproducibility_state: VerificationState::NotEvaluated,
        lineage_state: VerificationState::NotEvaluated,
        content_state: VerificationState::NotEvaluated,
        checkpoint_state: VerificationState::NotEvaluated,
        storage_state: VerificationState::NotEvaluated,
        reasons: Vec::new(),
    };

    if package.proof_version != "1.0.0" {
        report
            .reasons
            .push("proof_version must be 1.0.0".to_string());
        report.package_state = VerificationState::Failed;
    }
    if package.artifact_id.trim().is_empty() {
        report
            .reasons
            .push("artifact_id must not be empty".to_string());
        report.package_state = VerificationState::Failed;
    }
    if package.content_proof_type.trim().is_empty() {
        report
            .reasons
            .push("content_proof_type must not be empty".to_string());
        report.package_state = VerificationState::Failed;
    }
    if package.verified_ranges.is_empty() {
        report
            .reasons
            .push("verified_ranges must not be empty".to_string());
        report.package_state = VerificationState::Failed;
    }
    if let Err(err) = validate_content_descriptor(&package.content_descriptor) {
        report.reasons.push(err.to_string());
        report.package_state = VerificationState::Failed;
    }
    if let Err(err) = validate_checkpoint_manifest(&package.checkpoint_manifest) {
        report.reasons.push(err.to_string());
        report.package_state = VerificationState::Failed;
    }

    let parsed_lineage = parse_lineage_envelopes(&package.lineage_envelopes, &mut report.reasons);
    report.binding_state = verify_binding_state(package, &mut report.reasons);
    report.lineage_state = verify_lineage_state(
        &package.checkpoint_manifest,
        parsed_lineage.as_deref(),
        &mut report.reasons,
    );
    let lineage_signature_state = verify_lineage_signatures(
        parsed_lineage.as_deref(),
        &package.signature_bundle,
        &mut report.reasons,
    );
    let checkpoint_signature_state =
        verify_checkpoint_signatures(&package.checkpoint_manifest, &mut report.reasons);
    report.signature_state = if lineage_signature_state == VerificationState::Passed
        && checkpoint_signature_state == VerificationState::Passed
    {
        VerificationState::Passed
    } else {
        VerificationState::Failed
    };
    let (content_state, verified_bytes) =
        verify_content_state(package, artifact_bytes, &mut report.reasons);
    report.content_state = content_state;
    report.checkpoint_state =
        verify_checkpoint_state(package, trusted_checkpoint_log_root, &mut report.reasons);

    DeliveryProofVerificationResult {
        report,
        verified_bytes,
    }
}

/// Verifies delivery proof package.
pub fn verify_delivery_proof_package(
    package: &DeliveryProofPackage,
    artifact_bytes: Option<&[u8]>,
) -> TrazaeoResult<Vec<u8>> {
    verify_delivery_proof_package_against_root(package, artifact_bytes, None)
}

/// Verifies delivery proof package against an optional trusted checkpoint-log root.
pub fn verify_delivery_proof_package_against_root(
    package: &DeliveryProofPackage,
    artifact_bytes: Option<&[u8]>,
    trusted_checkpoint_log_root: Option<&Hash>,
) -> TrazaeoResult<Vec<u8>> {
    let result = verify_delivery_proof_package_with_report(
        package,
        artifact_bytes,
        trusted_checkpoint_log_root,
    );
    if !is_delivery_proof_report_success(&result.report) {
        let message = if result.report.reasons.is_empty() {
            "delivery proof verification failed".to_string()
        } else {
            result.report.reasons.join("; ")
        };
        return Err(TrazaeoError::invalid_input(
            "verify delivery proof package",
            message,
        ));
    }
    result.verified_bytes.ok_or_else(|| {
        TrazaeoError::external(
            "verify delivery proof package",
            "content verification succeeded without returning verified bytes",
        )
    })
}

#[cfg(test)]
mod tests {
    use super::*;
    #[cfg(feature = "bao-range-proofs")]
    use crate::content::{
        attach_bao_outboard, build_bao_range_proof_package, generate_bao_outboard,
    };
    use crate::content::{
        build_content_descriptor, build_full_root_proof_package, compute_content_descriptor_hash,
        ContentDescriptorInput,
    };
    use crate::envelope::{make_attestation, ArtifactRecord};
    use crate::transform_pipeline::{build_transform_envelope, TransformStageInput};

    const TEST_SIGNING_KEY_HEX: &str =
        "4f3edf983ac636a65a842ce7c78d9aa706d3b113bce036f9a4f5762b76f70f18";

    fn signed_transform_envelope(content_root_hash: &str) -> (String, String, String) {
        let seed = make_attestation(
            "transformer",
            TEST_SIGNING_KEY_HEX,
            "2026-01-01T00:00:00Z",
            b"",
        )
        .expect("seed attestation");
        let stage = TransformStageInput {
            schema_version: "1.0.0".to_string(),
            issued_at: "2026-01-01T00:00:00Z".to_string(),
            subject_id: "transform-1".to_string(),
            transform_job_id: "job-1".to_string(),
            transform_stage: "raw_to_release".to_string(),
            input_refs: vec!["obj://raw/1".to_string()],
            output_refs: vec!["obj://release/1".to_string()],
            input_artifacts: vec![ArtifactRecord {
                artifact_id: "input-1".to_string(),
                artifact_ref: "obj://raw/1".to_string(),
                content_root_hash: "input-root".to_string(),
            }],
            output_artifacts: vec![ArtifactRecord {
                artifact_id: "artifact-1".to_string(),
                artifact_ref: "obj://release/1".to_string(),
                content_root_hash: content_root_hash.to_string(),
            }],
            toolchain: "rust".to_string(),
            parameters_ref: "cfg://1".to_string(),
            parameters_hash: "cfg-hash".to_string(),
            determinism_profile: "det-v1".to_string(),
            runtime_env_ref: Some("oci://img".to_string()),
            runtime_env_hash: Some("img-hash".to_string()),
            provenance_start_mode: "transport_capture".to_string(),
            source_manifest_ref: None,
            source_manifest_hash: None,
            source_root_hash: None,
            transform_spec_ref: None,
            transform_spec_hash: None,
            chunking_profile_ref: None,
            chunking_profile_hash: None,
            execution_manifest_ref: None,
            execution_manifest_hash: None,
            runtime_manifest_ref: None,
            runtime_manifest_hash: None,
            key_id: seed.key_id.clone(),
        };
        let mut envelope = build_transform_envelope(&stage, seed.clone());
        let attestation = make_attestation(
            "transformer",
            TEST_SIGNING_KEY_HEX,
            "2026-01-01T00:00:00Z",
            &envelope.canonical_attestation_payload_bytes(),
        )
        .expect("transform attestation");
        envelope.attestations = vec![attestation.clone()];
        (
            serde_json::to_string(&envelope).expect("serialize transform envelope"),
            serde_json::to_string(&attestation).expect("serialize transform attestation"),
            format!("transform://{}", envelope.subject_id),
        )
    }

    fn signed_checkpoint_manifest(
        descriptor: &crate::content::ContentDescriptor,
        lineage_ref: &str,
    ) -> CheckpointManifest {
        let mut manifest = build_checkpoint_manifest(
            "checkpoint-1",
            "2026-01-01T00:00:00Z/2026-01-01T01:00:00Z",
            None,
            Vec::new(),
            vec![CheckpointArtifact {
                artifact_id: descriptor.artifact_id.clone(),
                content_root_hash: descriptor.content_root_hash.clone(),
                content_descriptor_ref: Some("descriptor://artifact-1".to_string()),
                content_descriptor_hash: Some(hex::encode(
                    compute_content_descriptor_hash(descriptor).0,
                )),
                media_type: descriptor.media_type.clone(),
            }],
            vec![lineage_ref.to_string()],
        );
        let attestation = make_attestation(
            "checkpoint-signer",
            TEST_SIGNING_KEY_HEX,
            "2026-01-01T00:00:00Z",
            &canonical_checkpoint_manifest_payload_bytes(&manifest),
        )
        .expect("checkpoint attestation");
        manifest.checkpoint_signature_bundle =
            vec![serde_json::to_string(&attestation).expect("serialize checkpoint attestation")];
        manifest
    }

    #[test]
    fn full_root_delivery_proof_package_roundtrip() {
        let data = b"abcdefghijklmno";
        let descriptor = build_content_descriptor(ContentDescriptorInput {
            artifact_id: "artifact-1",
            root: Hash(*blake3::hash(data).as_bytes()),
            chunk_size: 1024,
            leaf_count: 1,
            byte_length: data.len() as u64,
            media_type: "application/octet-stream",
            created_at: "2026-01-01T00:00:00Z",
        });
        let range = build_full_root_proof_package(&descriptor).expect("range");
        let (lineage_envelope_json, signature_json, lineage_ref) =
            signed_transform_envelope(&descriptor.content_root_hash);
        let checkpoint_manifest = signed_checkpoint_manifest(&descriptor, &lineage_ref);
        let package = build_delivery_proof_package(
            &range,
            vec![lineage_envelope_json],
            checkpoint_manifest,
            vec![signature_json],
        )
        .expect("delivery package");
        let verified =
            verify_delivery_proof_package(&package, Some(data)).expect("verify delivery package");
        assert_eq!(verified, data);
    }

    #[test]
    fn delivery_proof_report_emits_explicit_states() {
        let data = b"abcdefghijklmno";
        let descriptor = build_content_descriptor(ContentDescriptorInput {
            artifact_id: "artifact-1",
            root: Hash(*blake3::hash(data).as_bytes()),
            chunk_size: 1024,
            leaf_count: 1,
            byte_length: data.len() as u64,
            media_type: "application/octet-stream",
            created_at: "2026-01-01T00:00:00Z",
        });
        let range = build_full_root_proof_package(&descriptor).expect("range");
        let (lineage_envelope_json, signature_json, lineage_ref) =
            signed_transform_envelope(&descriptor.content_root_hash);
        let checkpoint_manifest = signed_checkpoint_manifest(&descriptor, &lineage_ref);
        let trusted_root = Hash(
            hex::decode(&checkpoint_manifest.checkpoint_log_root_hash)
                .expect("trusted root hex")
                .as_slice()
                .try_into()
                .expect("trusted root bytes"),
        );
        let package = build_delivery_proof_package(
            &range,
            vec![lineage_envelope_json],
            checkpoint_manifest,
            vec![signature_json],
        )
        .expect("delivery package");
        let result =
            verify_delivery_proof_package_with_report(&package, Some(data), Some(&trusted_root));
        assert_eq!(result.report.package_state, VerificationState::Passed);
        assert_eq!(result.report.signature_state, VerificationState::Passed);
        assert_eq!(result.report.trust_state, VerificationState::NotEvaluated);
        assert_eq!(result.report.binding_state, VerificationState::Passed);
        assert_eq!(
            result.report.reproducibility_state,
            VerificationState::NotEvaluated
        );
        assert_eq!(result.report.lineage_state, VerificationState::Passed);
        assert_eq!(result.report.content_state, VerificationState::Passed);
        assert_eq!(result.report.checkpoint_state, VerificationState::Passed);
        assert_eq!(result.report.storage_state, VerificationState::NotEvaluated);
        assert_eq!(result.verified_bytes.as_deref(), Some(data.as_slice()));
    }

    #[test]
    fn delivery_proof_report_fails_for_incomplete_lineage() {
        let data = b"abcdefghijklmno";
        let descriptor = build_content_descriptor(ContentDescriptorInput {
            artifact_id: "artifact-1",
            root: Hash(*blake3::hash(data).as_bytes()),
            chunk_size: 1024,
            leaf_count: 1,
            byte_length: data.len() as u64,
            media_type: "application/octet-stream",
            created_at: "2026-01-01T00:00:00Z",
        });
        let range = build_full_root_proof_package(&descriptor).expect("range");
        let (lineage_envelope_json, signature_json, lineage_ref) =
            signed_transform_envelope(&descriptor.content_root_hash);
        let mut checkpoint_manifest = signed_checkpoint_manifest(&descriptor, &lineage_ref);
        checkpoint_manifest
            .lineage_refs
            .push("capture://capture-1".to_string());
        let package = build_delivery_proof_package(
            &range,
            vec![lineage_envelope_json],
            checkpoint_manifest,
            vec![signature_json],
        )
        .expect("delivery package");
        let result = verify_delivery_proof_package_with_report(&package, Some(data), None);
        assert_eq!(result.report.lineage_state, VerificationState::Failed);
        assert!(!is_delivery_proof_report_success(&result.report));
        assert!(verify_delivery_proof_package(&package, Some(data)).is_err());
    }

    /// Tests that delivery proof package roundtrip.
    #[cfg(feature = "bao-range-proofs")]
    #[test]
    fn delivery_proof_package_roundtrip() {
        let data = b"abcdefghijklmno";
        let descriptor = build_content_descriptor(ContentDescriptorInput {
            artifact_id: "artifact-1",
            root: Hash(*blake3::hash(data).as_bytes()),
            chunk_size: 1024,
            leaf_count: 1,
            byte_length: data.len() as u64,
            media_type: "application/octet-stream",
            created_at: "2026-01-01T00:00:00Z",
        });
        let outboard = generate_bao_outboard(data);
        let descriptor = attach_bao_outboard(&descriptor, Some("outboard://1"), &outboard);
        let (lineage_envelope_json, signature_json, lineage_ref) =
            signed_transform_envelope(&descriptor.content_root_hash);
        let range =
            build_bao_range_proof_package(&descriptor, data, &outboard.outboard_bytes, 0, 4)
                .expect("range");
        let checkpoint_manifest = signed_checkpoint_manifest(&descriptor, &lineage_ref);
        let package = build_delivery_proof_package(
            &range,
            vec![lineage_envelope_json],
            checkpoint_manifest,
            vec![signature_json],
        )
        .expect("delivery package");
        let decoded =
            verify_delivery_proof_package(&package, None).expect("verify delivery package");
        assert_eq!(decoded, b"abcd");
    }
}
