//! Smoke tests for `KvHandoff`: package → verify happy path,
//! signature-tamper rejection, expiry rejection, delivery persistence,
//! untrusted-source rejection, verifying-key mismatch rejection, and
//! `verify_opaque` collapsing behaviour (B6).

use ed25519_dalek::SigningKey;
use klieo_bus_memory::MemoryBus;
use klieo_ops::handoff::{Handoff, HandoffEnvelope, HandoffError, HandoffState, KvHandoff};
use klieo_ops::types::{AgentId, TenantId};
use klieo_ops::{SoftwareSigner, SourceIdentityRegistry, StaticSourceIdentityRegistry};
use std::collections::HashMap;
use std::sync::Arc;

const SOURCE_AGENT: &str = "claims-triage";

fn test_signing_key() -> SigningKey {
    SigningKey::from_bytes(&[7u8; 32])
}

fn fresh_state() -> HandoffState {
    HandoffState::new(
        "rn_01HXX0000001",
        "deadbeef",
        b"[REDACTED]".to_vec(),
        AgentId(SOURCE_AGENT.into()),
        Some(TenantId("BL_auto".into())),
        Some(42),
    )
}

fn registry_for_key(sk: &SigningKey) -> Arc<dyn SourceIdentityRegistry> {
    let mut keys = HashMap::new();
    keys.insert(AgentId(SOURCE_AGENT.into()), sk.verifying_key());
    Arc::new(StaticSourceIdentityRegistry::from_map(keys))
}

fn kv_handoff() -> KvHandoff {
    let bus = MemoryBus::new();
    let registry = registry_for_key(&test_signing_key());
    KvHandoff::new(bus.kv, registry)
}

#[tokio::test]
async fn package_then_verify_roundtrip() {
    let h = kv_handoff();
    let signer = SoftwareSigner::from_bytes([7u8; 32]);
    let envelope = h
        .package(fresh_state(), &signer, chrono::Duration::hours(1))
        .await
        .expect("package");
    let proof = h.verify(&envelope).await.expect("verify");
    assert_eq!(proof.from_run, "rn_01HXX0000001");
    assert_eq!(proof.source_identity.0, SOURCE_AGENT);
    assert_eq!(proof.tenant.as_ref().map(|t| t.0.as_str()), Some("BL_auto"));
    assert_eq!(proof.redacted_state, b"[REDACTED]");
}

#[tokio::test]
async fn signature_tamper_via_redacted_state_is_rejected() {
    let h = kv_handoff();
    let signer = SoftwareSigner::from_bytes([7u8; 32]);
    let mut envelope: HandoffEnvelope = h
        .package(fresh_state(), &signer, chrono::Duration::hours(1))
        .await
        .expect("package");
    // Flip a byte in `redacted_state` — the recomputed `state_hash` in the
    // canonical payload will no longer match what was signed.
    envelope.redacted_state[0] ^= 0xff;
    let err = h.verify(&envelope).await.expect_err("tamper must reject");
    assert!(
        matches!(err, HandoffError::SignatureInvalid(_)),
        "expected SignatureInvalid, got {err}"
    );
}

#[tokio::test]
async fn expired_envelope_is_rejected() {
    let h = kv_handoff();
    let signer = SoftwareSigner::from_bytes([7u8; 32]);
    // TTL of zero seconds means the envelope expires immediately at issue time.
    let envelope = h
        .package(fresh_state(), &signer, chrono::Duration::seconds(0))
        .await
        .expect("package");
    // Sleep briefly to guarantee we are past the expiry instant.
    tokio::time::sleep(std::time::Duration::from_millis(50)).await;
    let err = h.verify(&envelope).await.expect_err("expired must reject");
    assert!(
        matches!(err, HandoffError::Expired { .. }),
        "expected Expired, got {err}"
    );
}

#[tokio::test]
async fn deliver_persists_and_returns_from_run() {
    let h = kv_handoff();
    let signer = SoftwareSigner::from_bytes([7u8; 32]);
    let envelope = h
        .package(fresh_state(), &signer, chrono::Duration::hours(1))
        .await
        .expect("package");
    let from_run = h
        .deliver(envelope, AgentId("downstream-agent".into()))
        .await
        .expect("deliver");
    assert_eq!(from_run, "rn_01HXX0000001");
}

#[tokio::test]
async fn untrusted_source_is_rejected() {
    let bus = MemoryBus::new();
    // Empty registry — no agent is trusted.
    let registry: Arc<dyn SourceIdentityRegistry> =
        Arc::new(StaticSourceIdentityRegistry::from_map(HashMap::new()));
    let h = KvHandoff::new(bus.kv, registry);

    let signer = SoftwareSigner::from_bytes([7u8; 32]);
    let envelope = h
        .package(fresh_state(), &signer, chrono::Duration::hours(1))
        .await
        .expect("package");
    let err = h
        .verify(&envelope)
        .await
        .expect_err("unknown source must reject");
    assert!(
        matches!(err, HandoffError::UntrustedSource(_)),
        "expected UntrustedSource, got {err}"
    );
}

#[tokio::test]
async fn signature_invalid_takes_precedence_over_untrusted_source() {
    // Verify reorder lock (v0.4 §1.7): when BOTH signature math fails
    // AND source identity is not in the registry, verify() must return
    // SignatureInvalid (constant-time-ish) not UntrustedSource (which
    // would confirm registry membership to an attacker).
    let bus = MemoryBus::new();
    let alice_sk = SigningKey::from_bytes(&[3u8; 32]);
    // Registry contains ONLY alice — bob is NOT registered.
    let mut keys = HashMap::new();
    keys.insert(AgentId("alice".into()), alice_sk.verifying_key());
    let registry: Arc<dyn SourceIdentityRegistry> =
        Arc::new(StaticSourceIdentityRegistry::from_map(keys));
    let handoff = KvHandoff::new(bus.kv, registry);

    let signer = SoftwareSigner::from_bytes([3u8; 32]);
    let state = HandoffState::new(
        "rn_order",
        "deadbeef",
        b"[REDACTED]".to_vec(),
        AgentId("bob".into()), // claims to be bob (unknown to registry)
        Some(TenantId("BL_test".into())),
        Some(0),
    );
    let mut envelope = handoff
        .package(state, &signer, chrono::Duration::hours(1))
        .await
        .expect("package");

    // Tamper the signature so signature-math check would ALSO fail.
    envelope.redacted_state[0] ^= 0xff;

    let err = handoff.verify(&envelope).await.expect_err("must reject");
    // Verify order lock: sig math runs BEFORE identity lookup, so we
    // get SignatureInvalid not UntrustedSource.
    assert!(
        matches!(err, HandoffError::SignatureInvalid(_)),
        "expected SignatureInvalid (sig-first order lock); got {err:?}"
    );
}

#[tokio::test]
async fn vk_mismatch_is_rejected() {
    let bus = MemoryBus::new();
    // Registry contains a DIFFERENT key for the same source identity.
    let different_sk = SigningKey::from_bytes(&[99u8; 32]);
    let mut keys = HashMap::new();
    keys.insert(AgentId(SOURCE_AGENT.into()), different_sk.verifying_key());
    let registry: Arc<dyn SourceIdentityRegistry> =
        Arc::new(StaticSourceIdentityRegistry::from_map(keys));
    let h = KvHandoff::new(bus.kv, registry);

    // Package with the test key (seed 7), but registry holds seed-99 key.
    let signer = SoftwareSigner::from_bytes([7u8; 32]);
    let envelope = h
        .package(fresh_state(), &signer, chrono::Duration::hours(1))
        .await
        .expect("package");
    let err = h
        .verify(&envelope)
        .await
        .expect_err("vk mismatch must reject");
    assert!(
        matches!(err, HandoffError::UntrustedSource(_)),
        "expected UntrustedSource, got {err}"
    );
}

// ── B6: verify_opaque collapsing tests ───────────────────────────────────────

/// `verify_opaque` must collapse both `SignatureInvalid` and `UntrustedSource`
/// into `Unverifiable { reason: "" }` so remote callers cannot distinguish
/// registry membership from signature validity.
#[tokio::test]
async fn verify_opaque_collapses_signature_and_untrusted_into_one_error() {
    // Envelope signed with an unknown key, source identity not in registry.
    let bus = MemoryBus::new();
    let registry: Arc<dyn SourceIdentityRegistry> =
        Arc::new(StaticSourceIdentityRegistry::from_map(HashMap::new()));
    let h = KvHandoff::new(bus.kv, registry);

    let signer = SoftwareSigner::from_bytes([7u8; 32]);
    let mut envelope = h
        .package(fresh_state(), &signer, chrono::Duration::hours(1))
        .await
        .expect("package");

    // Tamper the signature so SignatureInvalid would fire.
    envelope.redacted_state[0] ^= 0xff;

    let err = h.verify_opaque(&envelope).await.expect_err("must reject");
    assert!(
        matches!(err, HandoffError::Unverifiable { ref reason } if reason.is_empty()),
        "expected Unverifiable with empty reason, got {err:?}"
    );
}

/// `verify_opaque` with a valid signature but untrusted source (not in registry)
/// must also collapse to `Unverifiable { reason: "" }`.
#[tokio::test]
async fn verify_opaque_collapses_untrusted_source_into_unverifiable() {
    let bus = MemoryBus::new();
    // Empty registry — no identity is trusted.
    let registry: Arc<dyn SourceIdentityRegistry> =
        Arc::new(StaticSourceIdentityRegistry::from_map(HashMap::new()));
    let h = KvHandoff::new(bus.kv, registry);

    let signer = SoftwareSigner::from_bytes([7u8; 32]);
    let envelope = h
        .package(fresh_state(), &signer, chrono::Duration::hours(1))
        .await
        .expect("package");

    // Signature is valid, but source identity is not in the registry.
    let err = h.verify_opaque(&envelope).await.expect_err("must reject");
    assert!(
        matches!(err, HandoffError::Unverifiable { ref reason } if reason.is_empty()),
        "expected Unverifiable with empty reason, got {err:?}"
    );
}

/// `verify_opaque` must NOT collapse `Expired` — it passes through unchanged.
/// Callers must still handle expiry distinctly from opaque identity failures.
#[tokio::test]
async fn verify_opaque_passes_through_expired() {
    let h = kv_handoff();
    let signer = SoftwareSigner::from_bytes([7u8; 32]);
    let envelope = h
        .package(fresh_state(), &signer, chrono::Duration::seconds(0))
        .await
        .expect("package");
    tokio::time::sleep(std::time::Duration::from_millis(50)).await;

    let err = h
        .verify_opaque(&envelope)
        .await
        .expect_err("expired must reject");
    assert!(
        matches!(err, HandoffError::Expired { .. }),
        "expected Expired to pass through verify_opaque unchanged, got {err:?}"
    );
}
