//! Pure trusted-evidence reducer.
//!
//! `verify(input, witnesses, now, max_age) -> VerifiedTrustState`. No I/O. No
//! filesystem reads, no network calls, no clock reads — `now` is injected.
//!
//! Order of checks (each is a hard fail when violated):
//!
//! 1. Advisory-only short-circuit — local / dev / pre-v2 evidence cannot be
//!    promoted by witnesses (`witness.tier_insufficient`).
//! 2. Subject binding — every witness's `asserted_subject_blake3` must match
//!    `input.evidence_blake3` (`witness.disagreement`).
//! 3. Domain integrity — every witness's `authority_domain` must equal
//!    `class.required_authority_domain()` and no two witnesses may share a
//!    domain (`witness.authority_overlap`).
//! 4. Freshness — `now - witness.asserted_at <= max_age` (`witness.stale`).
//! 5. Tier sufficiency — remote-CI and reproducible-build witnesses must be
//!    `ThirdParty` (`witness.tier_insufficient`).
//! 6. Signature — Ed25519 verify over the canonical preimage
//!    (`witness.signature_invalid`).
//! 7. Class coverage — `FullChainVerified` requires one witness from each of
//!    the four ADR 0041 classes; missing classes drop the result to
//!    `Partial`, never to a falsely-trusted state.
//! 8. Composition — effective ceiling for the input must be at or above the
//!    required ceiling for the claim kind
//!    (`composition.ceiling_below_required`).

use std::collections::HashSet;

use chrono::{DateTime, Duration, Utc};
use cortex_core::{effective_ceiling, ClaimCeiling, PolicyDecision, PolicyOutcome};
use cortex_runtime::RuntimeClaimKind;
use ed25519_dalek::{Signature, Verifier, VerifyingKey};

use crate::input::{EvidenceInput, EvidenceKind};
use crate::invariant::{
    COMPOSITION_CEILING_BELOW_REQUIRED, COMPOSITION_POLICY_FAIL_CLOSED, WITNESS_AUTHORITY_OVERLAP,
    WITNESS_DISAGREEMENT, WITNESS_MISSING, WITNESS_SIGNATURE_INVALID, WITNESS_STALE,
    WITNESS_TIER_INSUFFICIENT,
};
use crate::state::{BrokenEdge, VerifiedTrustState};
use crate::witness::{
    AuthorityDomain, IndependentWitness, SelfSignedAlgorithm, SelfSignedKeyRegistry, WitnessClass,
    WitnessSignature, WitnessSummary, WitnessTier,
};

/// Pure reducer over independent witnesses.
///
/// `now` and `max_age` are injected by the caller so the trust path remains
/// deterministic and clock-independent. The CLI is responsible for resolving
/// `now` (typically `Utc::now()`) and passing it in.
#[must_use]
pub fn verify(
    input: &EvidenceInput,
    witnesses: &[IndependentWitness],
    now: DateTime<Utc>,
    max_age: Duration,
) -> VerifiedTrustState {
    verify_with_policy(input, witnesses, now, max_age, None)
}

/// Options for [`verify_with_options`]. Groups the optional policy decision
/// and the optional operator-supplied key registry so the call site stays
/// readable as the option count grows.
#[derive(Debug)]
pub struct VerifyOptions<'a> {
    /// ADR 0026 policy decision to compose with the witness result. When
    /// `Reject` or `Quarantine`, the trust path falls closed before witness
    /// checks run. `None` means no policy composition.
    pub policy: Option<&'a cortex_core::PolicyDecision>,
    /// Operator-supplied key registry for `SelfSigned` witnesses. When
    /// `None`, `SelfSigned` witnesses fail with `UnsupportedAlgorithm`.
    /// When `Some`, the registry is consulted and verification proceeds for
    /// known key ids.
    pub self_signed_keys: Option<&'a SelfSignedKeyRegistry>,
}

/// Full-featured verify entry point: composes ADR 0026 policy AND resolves
/// `SelfSigned` witnesses against the operator-supplied key registry.
///
/// Use this from CLI surfaces that accept `--witness-key-registry`. The
/// simpler [`verify`] and [`verify_with_policy`] entry points delegate here
/// with `self_signed_keys: None`.
#[must_use]
pub fn verify_with_options(
    input: &EvidenceInput,
    witnesses: &[IndependentWitness],
    now: DateTime<Utc>,
    max_age: Duration,
    opts: VerifyOptions<'_>,
) -> VerifiedTrustState {
    let summaries: Vec<WitnessSummary> =
        witnesses.iter().map(WitnessSummary::from_witness).collect();

    // 0. Policy fail-closed.
    if let Some(decision) = opts.policy {
        if matches!(
            decision.final_outcome,
            cortex_core::PolicyOutcome::Reject | cortex_core::PolicyOutcome::Quarantine
        ) {
            return VerifiedTrustState::Broken {
                edge: BrokenEdge::new(
                    COMPOSITION_POLICY_FAIL_CLOSED,
                    format!(
                        "policy outcome {:?} fails closed for {:?}",
                        decision.final_outcome, input.kind
                    ),
                ),
                witnesses: summaries,
            };
        }
    }

    // Delegate the remaining checks to the shared inner path.
    verify_inner(
        input,
        witnesses,
        now,
        max_age,
        opts.self_signed_keys,
        summaries,
    )
}

/// Same as [`verify`], but composes with an ADR 0026 policy decision so the
/// trust path falls closed independently of witness composition when policy
/// returns `Reject` or `Quarantine`. The verifier honours `Allow` and `Warn`
/// without ceiling impact (mirrors `runtime_claim_preflight_with_policy`).
#[must_use]
pub fn verify_with_policy(
    input: &EvidenceInput,
    witnesses: &[IndependentWitness],
    now: DateTime<Utc>,
    max_age: Duration,
    policy: Option<&PolicyDecision>,
) -> VerifiedTrustState {
    let summaries: Vec<WitnessSummary> =
        witnesses.iter().map(WitnessSummary::from_witness).collect();

    // 0. Policy fail-closed has highest precedence so a verifier bug cannot
    //    bypass an ADR 0026 `Reject` / `Quarantine`.
    if let Some(decision) = policy {
        if matches!(
            decision.final_outcome,
            PolicyOutcome::Reject | PolicyOutcome::Quarantine
        ) {
            return VerifiedTrustState::Broken {
                edge: BrokenEdge::new(
                    COMPOSITION_POLICY_FAIL_CLOSED,
                    format!(
                        "policy outcome {:?} fails closed for {:?}",
                        decision.final_outcome, input.kind
                    ),
                ),
                witnesses: summaries,
            };
        }
    }

    verify_inner(input, witnesses, now, max_age, None, summaries)
}

/// Shared inner reducer. Called by all public entry points after policy
/// fail-closed is resolved. `registry` is `None` when the caller does not
/// supply a `--witness-key-registry`; `SelfSigned` witnesses then fail closed
/// with `UnsupportedAlgorithm`.
fn verify_inner(
    input: &EvidenceInput,
    witnesses: &[IndependentWitness],
    now: DateTime<Utc>,
    max_age: Duration,
    registry: Option<&SelfSignedKeyRegistry>,
    summaries: Vec<WitnessSummary>,
) -> VerifiedTrustState {
    // 1. Advisory-only short-circuit. ADR 0041 acceptance §132: absent or
    //    advisory-only paths cannot be promoted by witnesses. We bind this to
    //    `tier_insufficient` because the runtime mode itself is below trust.
    if input.is_advisory_only() {
        return VerifiedTrustState::Broken {
            edge: BrokenEdge::new(
                WITNESS_TIER_INSUFFICIENT,
                format!(
                    "evidence input is advisory-only and cannot be promoted by witnesses (runtime_mode={:?}, advisory_only=true)",
                    input.runtime_mode
                ),
            ),
            witnesses: summaries,
        };
    }

    // 2. Subject binding — every witness must declare the same digest as the
    //    producer-supplied input.
    for witness in witnesses {
        if witness.asserted_subject_blake3 != input.evidence_blake3 {
            return VerifiedTrustState::Broken {
                edge: BrokenEdge::new(
                    WITNESS_DISAGREEMENT,
                    format!(
                        "witness class={} asserted subject {} but input declares {}",
                        witness.class.wire_str(),
                        witness.asserted_subject_blake3,
                        input.evidence_blake3,
                    ),
                ),
                witnesses: summaries,
            };
        }
    }

    // 3a. Domain integrity per witness — declared `authority_domain` must
    //     match the class.
    for witness in witnesses {
        let expected = witness.class.required_authority_domain();
        if witness.authority_domain != expected {
            return VerifiedTrustState::Broken {
                edge: BrokenEdge::new(
                    WITNESS_AUTHORITY_OVERLAP,
                    format!(
                        "witness class={} declared authority_domain={} but class requires {}",
                        witness.class.wire_str(),
                        witness.authority_domain.wire_str(),
                        expected.wire_str(),
                    ),
                ),
                witnesses: summaries,
            };
        }
    }

    // 3b. Pairwise disjointness — no two witnesses may share a domain.
    let mut seen_domains: HashSet<AuthorityDomain> = HashSet::new();
    for witness in witnesses {
        if !seen_domains.insert(witness.authority_domain) {
            return VerifiedTrustState::Broken {
                edge: BrokenEdge::new(
                    WITNESS_AUTHORITY_OVERLAP,
                    format!(
                        "two witnesses share authority_domain={}",
                        witness.authority_domain.wire_str()
                    ),
                ),
                witnesses: summaries,
            };
        }
    }

    // 4. Freshness.
    for witness in witnesses {
        let age = now - witness.asserted_at;
        if age > max_age {
            return VerifiedTrustState::Broken {
                edge: BrokenEdge::new(
                    WITNESS_STALE,
                    format!(
                        "witness class={} is stale: age {}s exceeds max_age {}s",
                        witness.class.wire_str(),
                        age.num_seconds(),
                        max_age.num_seconds(),
                    ),
                ),
                witnesses: summaries,
            };
        }
    }

    // 5. Tier sufficiency. Remote-CI and reproducible-build witnesses must be
    //    `ThirdParty` for both supported `EvidenceKind`s.
    for witness in witnesses {
        if requires_third_party(input.kind, witness.class)
            && witness.tier != WitnessTier::ThirdParty
        {
            return VerifiedTrustState::Broken {
                edge: BrokenEdge::new(
                    WITNESS_TIER_INSUFFICIENT,
                    format!(
                        "witness class={} requires tier=third_party for {}, got {}",
                        witness.class.wire_str(),
                        input.kind.wire_str(),
                        witness.tier.wire_str(),
                    ),
                ),
                witnesses: summaries,
            };
        }
    }

    // 6. Signature verification.
    for witness in witnesses {
        if let Err(detail) = verify_witness_signature(witness, registry) {
            return VerifiedTrustState::Broken {
                edge: BrokenEdge::new(
                    WITNESS_SIGNATURE_INVALID,
                    format!(
                        "witness class={} signature did not verify: {}",
                        witness.class.wire_str(),
                        detail
                    ),
                ),
                witnesses: summaries,
            };
        }
    }

    // 7. Class coverage. Missing required classes drop to Partial — never
    //    silently promote.
    let required_classes = required_classes_for(input.kind);
    let present_classes: HashSet<WitnessClass> = witnesses.iter().map(|w| w.class).collect();
    let missing: Vec<WitnessClass> = required_classes
        .iter()
        .copied()
        .filter(|class| !present_classes.contains(class))
        .collect();

    if !missing.is_empty() {
        let reasons: Vec<String> = missing
            .iter()
            .map(|class| {
                format!(
                    "{}: required witness class {} is missing",
                    WITNESS_MISSING,
                    class.wire_str()
                )
            })
            .collect();
        return VerifiedTrustState::Partial {
            reasons,
            witnesses: summaries,
        };
    }

    // 8. Composition — effective ceiling must reach the required ceiling for
    //    the claim kind.
    let runtime_kind = runtime_claim_kind_for(input.kind);
    let required_ceiling = runtime_kind.required_ceiling();
    let effective = effective_ceiling(
        input.runtime_mode,
        input.authority_class,
        input.proof_state,
        input.requested_ceiling,
    );
    if effective < required_ceiling {
        return VerifiedTrustState::Broken {
            edge: BrokenEdge::new(
                COMPOSITION_CEILING_BELOW_REQUIRED,
                format!(
                    "effective ceiling {effective:?} is below required ceiling {required_ceiling:?} for {runtime_kind:?}"
                ),
            ),
            witnesses: summaries,
        };
    }

    VerifiedTrustState::FullChainVerified {
        ceiling: effective,
        witnesses: summaries,
    }
}

/// Required witness classes per evidence kind. Per ADR 0041 §4, both
/// `ReleaseReadiness` and `ComplianceEvidence` require all four classes for
/// `FullChainVerified`.
fn required_classes_for(kind: EvidenceKind) -> &'static [WitnessClass] {
    match kind {
        EvidenceKind::ReleaseReadiness | EvidenceKind::ComplianceEvidence => &[
            WitnessClass::SignedLedgerChainHead,
            WitnessClass::ExternalAnchorCrossing,
            WitnessClass::RemoteCiConclusion,
            WitnessClass::ReproducibleBuildProvenance,
        ],
    }
}

/// Project an [`EvidenceKind`] onto the existing [`RuntimeClaimKind`] without
/// adding new variants (design choice from
/// `DESIGN_release_compliance_verifier.md` §11).
const fn runtime_claim_kind_for(kind: EvidenceKind) -> RuntimeClaimKind {
    match kind {
        EvidenceKind::ReleaseReadiness => RuntimeClaimKind::ReleaseReadiness,
        EvidenceKind::ComplianceEvidence => RuntimeClaimKind::ComplianceEvidence,
    }
}

/// Whether a witness class must be `ThirdParty` for the given evidence kind.
fn requires_third_party(kind: EvidenceKind, class: WitnessClass) -> bool {
    matches!(
        (kind, class),
        (
            EvidenceKind::ReleaseReadiness | EvidenceKind::ComplianceEvidence,
            WitnessClass::RemoteCiConclusion | WitnessClass::ReproducibleBuildProvenance,
        )
    )
}

/// Verify a witness's signature over its canonical preimage. Dispatches on the
/// [`WitnessSignature`] variant. Pure: no I/O, no key fetch — all material is
/// already in memory.
///
/// `EcdsaP256` returns `Err` with an `UnsupportedAlgorithm` detail until the
/// matching crate dependencies are wired in; the adapter-boundary verifier in
/// `crates/cortex-ledger/src/external_sink/rekor.rs` handles P-256 today.
///
/// `SelfSigned` resolves the `key_id` against `registry` when `Some`. If the
/// registry is `None` or the key is absent, the witness fails closed.
fn verify_witness_signature(
    witness: &IndependentWitness,
    registry: Option<&SelfSignedKeyRegistry>,
) -> Result<(), String> {
    if witness.payload.class() != witness.class {
        return Err(format!(
            "payload class {} does not match declared class {}",
            witness.payload.class().wire_str(),
            witness.class.wire_str()
        ));
    }
    match &witness.signature {
        WitnessSignature::Ed25519 {
            public_key_bytes,
            signature_bytes,
            ..
        } => {
            let key = VerifyingKey::from_bytes(public_key_bytes)
                .map_err(|err| format!("public_key_bytes is not a valid Ed25519 point: {err}"))?;
            let sig = Signature::from_bytes(signature_bytes);
            let preimage = witness.canonical_preimage();
            key.verify(&preimage, &sig)
                .map_err(|err| format!("Ed25519 verification failed: {err}"))?;
            Ok(())
        }
        WitnessSignature::EcdsaP256 { .. } => {
            // ECDSA P-256 is verified at the adapter boundary
            // (crates/cortex-ledger/src/external_sink/rekor.rs). Verifier-layer
            // dispatch is deferred per ADR 0041 amendment; add the `p256` crate
            // and implement here when the trigger conditions are met.
            Err("UnsupportedAlgorithm: EcdsaP256 verification not yet implemented at the verifier layer".to_string())
        }
        WitnessSignature::SelfSigned {
            key_id,
            signature_bytes,
        } => {
            let reg = registry.ok_or_else(|| {
                format!(
                    "SelfSigned key_id={key_id}: no SelfSignedKeyRegistry supplied \
                     (pass --witness-key-registry to provide one)"
                )
            })?;
            let entry = reg.get(key_id).ok_or_else(|| {
                format!(
                    "SelfSigned key_id={key_id} is not in SelfSignedKeyRegistry \
                     (registry contains {} entries)",
                    reg.len()
                )
            })?;
            let key_bytes = entry.key_bytes().map_err(|e| {
                format!("SelfSigned key_id={key_id}: malformed key_bytes_hex in registry: {e}")
            })?;
            let preimage = witness.canonical_preimage();
            match entry.algorithm {
                SelfSignedAlgorithm::Ed25519 => {
                    let raw: [u8; 32] = key_bytes.as_slice().try_into().map_err(|_| {
                        format!(
                            "SelfSigned key_id={key_id}: Ed25519 key must be 32 bytes, got {}",
                            key_bytes.len()
                        )
                    })?;
                    let key = VerifyingKey::from_bytes(&raw).map_err(|e| {
                        format!("SelfSigned key_id={key_id}: invalid Ed25519 public key: {e}")
                    })?;
                    let sig_raw: [u8; 64] =
                        signature_bytes.as_slice().try_into().map_err(|_| {
                            format!(
                                "SelfSigned key_id={key_id}: Ed25519 signature must be 64 bytes, \
                                 got {}",
                                signature_bytes.len()
                            )
                        })?;
                    let sig = Signature::from_bytes(&sig_raw);
                    key.verify(&preimage, &sig).map_err(|e| {
                        format!("SelfSigned key_id={key_id}: Ed25519 verification failed: {e}")
                    })
                }
                SelfSignedAlgorithm::EcdsaP256 => {
                    // P-256 SelfSigned verification is deferred — the same
                    // trigger conditions apply as for EcdsaP256 witnesses.
                    Err(format!(
                        "SelfSigned key_id={key_id}: \
                         UnsupportedAlgorithm: EcdsaP256 SelfSigned verification \
                         not yet implemented at the verifier layer"
                    ))
                }
            }
        }
    }
}

/// Map a [`VerifiedTrustState`] result to a `ClaimCeiling` for downstream
/// composition with `runtime_claim_preflight_with_policy`. Non-promoted
/// states fall to `DevOnly` so any verifier-side bug cannot lift the trust
/// path silently.
#[must_use]
pub fn ceiling_from_state(state: &VerifiedTrustState) -> ClaimCeiling {
    match state {
        VerifiedTrustState::FullChainVerified { ceiling, .. } => *ceiling,
        VerifiedTrustState::Partial { .. } | VerifiedTrustState::Broken { .. } => {
            ClaimCeiling::DevOnly
        }
    }
}
