//! Integration tests for the six-rule validator (v0.2 uses AinsResolver trait).

use tibet_iddrop::{
    ains::StubAinsResolver,
    claim::SemanticType,
    nonce::ChallengeNonce,
    validator::{validate, ReceiverContext},
    IdClaim,
};

fn fixture_claim(receiver_pubkey: &str, sender_pubkey: &str) -> IdClaim {
    IdClaim::new(
        "test-req-001",
        Some(ChallengeNonce::new()),
        sender_pubkey,
        receiver_pubkey,
        "jasper.aint",
        SemanticType::Identity,
        "ains:jasper.aint",
        300,
    )
}

fn resolver_for(sender_pubkey: &str) -> StubAinsResolver {
    StubAinsResolver::new().with_entry("jasper.aint", sender_pubkey)
}

#[test]
fn rule3_receiver_binding_mismatch_rejected() {
    let claim = fixture_claim("aaaa", "bbbb");
    let resolver = resolver_for("bbbb");
    let outstanding = vec![(
        claim.request_id.clone(),
        claim.challenge_nonce.unwrap(),
    )];
    let ctx = ReceiverContext {
        local_pubkey: "zzzz", // mismatch
        outstanding_requests: &outstanding,
        outstanding_offers: &[],
        ains_resolver: &resolver,
    };
    let r = validate(&claim, true, &ctx, "request-first");
    assert!(matches!(
        r,
        Err(tibet_iddrop::IddropError::ReceiverMismatch { .. })
    ));
}

#[test]
fn rule4_provenance_mismatch_rejected() {
    let claim = fixture_claim("aaaa", "bbbb");
    let resolver = resolver_for("cccc"); // wrong
    let outstanding = vec![(
        claim.request_id.clone(),
        claim.challenge_nonce.unwrap(),
    )];
    let ctx = ReceiverContext {
        local_pubkey: "aaaa",
        outstanding_requests: &outstanding,
        outstanding_offers: &[],
        ains_resolver: &resolver,
    };
    let r = validate(&claim, true, &ctx, "request-first");
    assert!(matches!(
        r,
        Err(tibet_iddrop::IddropError::SenderAinsMismatch { .. })
    ));
}

#[test]
fn rule4_ains_resolution_failed_rejected() {
    let claim = fixture_claim("aaaa", "bbbb");
    let resolver = StubAinsResolver::new(); // empty - not found
    let outstanding = vec![(
        claim.request_id.clone(),
        claim.challenge_nonce.unwrap(),
    )];
    let ctx = ReceiverContext {
        local_pubkey: "aaaa",
        outstanding_requests: &outstanding,
        outstanding_offers: &[],
        ains_resolver: &resolver,
    };
    let r = validate(&claim, true, &ctx, "request-first");
    assert!(matches!(
        r,
        Err(tibet_iddrop::IddropError::AinsResolutionFailed { .. })
    ));
}

#[test]
fn rule5_causal_binding_unknown_request_rejected() {
    let claim = fixture_claim("aaaa", "bbbb");
    let resolver = resolver_for("bbbb");
    let ctx = ReceiverContext {
        local_pubkey: "aaaa",
        outstanding_requests: &[], // no outstanding request
        outstanding_offers: &[],
        ains_resolver: &resolver,
    };
    let r = validate(&claim, true, &ctx, "request-first");
    assert!(matches!(
        r,
        Err(tibet_iddrop::IddropError::RequestIdUnknown(_))
    ));
}

#[test]
fn rule1_carrier_unverified_rejected() {
    let claim = fixture_claim("aaaa", "bbbb");
    let resolver = resolver_for("bbbb");
    let outstanding = vec![(
        claim.request_id.clone(),
        claim.challenge_nonce.unwrap(),
    )];
    let ctx = ReceiverContext {
        local_pubkey: "aaaa",
        outstanding_requests: &outstanding,
        outstanding_offers: &[],
        ains_resolver: &resolver,
    };
    let r = validate(&claim, false /* unverified */, &ctx, "request-first");
    assert!(matches!(r, Err(tibet_iddrop::IddropError::Envelope(_))));
}

#[test]
fn happy_path_request_first() {
    let claim = fixture_claim("aaaa", "bbbb");
    let resolver = resolver_for("bbbb");
    let outstanding = vec![(
        claim.request_id.clone(),
        claim.challenge_nonce.unwrap(),
    )];
    let ctx = ReceiverContext {
        local_pubkey: "aaaa",
        outstanding_requests: &outstanding,
        outstanding_offers: &[],
        ains_resolver: &resolver,
    };
    let out = validate(&claim, true, &ctx, "request-first").expect("should pass");
    assert_eq!(out.passed.len(), 6);
    assert_eq!(out.mode, "request-first");
    assert_eq!(out.provenance_ref, "ains:jasper.aint");
}

#[test]
fn happy_path_offer_first() {
    let mut claim = fixture_claim("aaaa", "bbbb");
    claim.request_id = "offer-abc".to_string();
    let resolver = resolver_for("bbbb");
    let offers = vec!["offer-abc".to_string()];
    let ctx = ReceiverContext {
        local_pubkey: "aaaa",
        outstanding_requests: &[],
        outstanding_offers: &offers,
        ains_resolver: &resolver,
    };
    let out = validate(&claim, true, &ctx, "offer-first").expect("should pass");
    assert_eq!(out.passed.len(), 6);
    assert_eq!(out.mode, "offer-first");
}

#[test]
fn unknown_mode_rejected() {
    let claim = fixture_claim("aaaa", "bbbb");
    let resolver = resolver_for("bbbb");
    let ctx = ReceiverContext {
        local_pubkey: "aaaa",
        outstanding_requests: &[],
        outstanding_offers: &[],
        ains_resolver: &resolver,
    };
    let r = validate(&claim, true, &ctx, "smoke-signals");
    assert!(matches!(r, Err(tibet_iddrop::IddropError::Internal(_))));
}
