//! SC-4 — Inbound reply resolves the approval, in-process.
//!
//! An inbound iMessage from the paired handle naming `<code> approve` (or bare
//! `approve` when exactly one is pending) resolves that approval by calling
//! `HostState::resolve_approval` in-process with `resolution = "approve"`; the
//! row transitions to Resolved, disappears from the live-pending set, and
//! `approval.resolved` fires. `deny` resolves with `"deny"`.
//!
//! Edge: an inbound "approve" must NOT resolve a `ws.method:*` blocking-gate
//! row (it stays Pending — v2-deferred).

mod messaging_harness;
use messaging_harness::{extract_code, inbound, Harness, PAIRED};

#[tokio::test]
async fn coded_approve_resolves_that_approval() {
    let h = Harness::enabled_paired();
    let id = h.seed_fire_and_return("send wire transfer").await;

    // Outbound mints + records the code↔id map.
    h.orch.observe_and_notify().await;
    let body = &h.spy.calls()[0].1;
    let code = extract_code(body).expect("code in prompt");

    // Inbound `<code> approve` from the paired handle.
    h.orch
        .handle_inbound(&inbound(PAIRED, &format!("{code} approve"), 1))
        .await;

    assert!(h.is_resolved(&id).await, "the approval is Resolved");
    assert_eq!(
        h.approval(&id).await.resolution.as_deref(),
        Some("approve"),
        "resolution string is exactly 'approve'"
    );
    assert_eq!(
        h.approval_resolved_count().await,
        1,
        "approval.resolved fired exactly once"
    );
}

#[tokio::test]
async fn bare_deny_resolves_sole_pending() {
    let h = Harness::enabled_paired();
    let id = h.seed_fire_and_return("delete the prod database").await;
    h.orch.observe_and_notify().await;

    // Bare `deny` with exactly one pending ⇒ resolve it with "deny".
    h.orch.handle_inbound(&inbound(PAIRED, "deny", 1)).await;

    assert!(h.is_resolved(&id).await, "the sole pending approval is Resolved");
    assert_eq!(
        h.approval(&id).await.resolution.as_deref(),
        Some("deny"),
        "resolution string is exactly 'deny'"
    );
}

#[tokio::test]
async fn inbound_approve_does_not_resolve_ws_method_row() {
    let h = Harness::enabled_paired();
    // A blocking-gate row (v2-deferred). It is NOT prompted, so no code maps to
    // it; a bare approve must also leave it untouched.
    let ws_id = h.seed_ws_method("messages.send").await;

    h.orch.observe_and_notify().await; // no prompt for the ws.method row
    assert_eq!(h.spy.count(), 0, "no prompt sent for a ws.method:* row");

    // Bare approve from the paired handle.
    h.orch.handle_inbound(&inbound(PAIRED, "approve", 1)).await;

    assert!(
        !h.is_resolved(&ws_id).await,
        "a ws.method:* blocking-gate row must NOT be resolved by an inbound approve"
    );
    assert_eq!(
        h.approval_resolved_count().await,
        0,
        "nothing resolved — the only row is excluded"
    );
}

#[tokio::test]
async fn coded_approve_ignores_ws_method_even_when_both_present() {
    let h = Harness::enabled_paired();
    let far_id = h.seed_fire_and_return("post to twitter").await;
    let ws_id = h.seed_ws_method("automation.run_applescript").await;

    h.orch.observe_and_notify().await;
    // Exactly one prompt — for the fire-and-return row.
    assert_eq!(h.spy.count(), 1, "only the fire-and-return row is prompted");
    let code = extract_code(&h.spy.calls()[0].1).unwrap();

    h.orch
        .handle_inbound(&inbound(PAIRED, &format!("{code} approve"), 1))
        .await;

    assert!(h.is_resolved(&far_id).await, "fire-and-return row resolved");
    assert!(
        !h.is_resolved(&ws_id).await,
        "ws.method:* row stays Pending"
    );
}
