//! Asserts that `audited_escalation` / `audited_worklog` / `audited_handoff`
//! pick up the SupervisedAgent run context automatically.
//!
//! Inside a `scope_run_context` scope (which `SupervisedAgent::run` binds),
//! helpers produce audit-wired primitives. Outside a scope, they fall back to
//! unaudited pass-through instances.

use async_trait::async_trait;
use klieo_bus_memory::MemoryBus;
use klieo_core::memory::{Episode, EpisodicMemory};
use klieo_core::test_utils::{fake_context, InMemoryEpisodic};
use klieo_core::{Agent, AgentContext, RunId, ToolDef};
use klieo_ops::escalation::{audited_escalation, Escalation, EscalationTicket, Severity};
use klieo_ops::handoff::{audited_handoff, Handoff, HandoffState};
use klieo_ops::ops_event::OpsEvent;
use klieo_ops::run_context::{scope_run_context, RunContext};
use klieo_ops::source_identity_registry::StaticSourceIdentityRegistry;
use klieo_ops::worklog::{audited_worklog, WorkItem, WorkLog};
use klieo_ops::{OpsRuntime, TenantId, TenantResolver};
use std::collections::HashMap;
use std::sync::Arc;

// ── Helpers ───────────────────────────────────────────────────────────────────

struct NoTenant;

#[async_trait]
impl TenantResolver for NoTenant {
    async fn resolve(&self) -> Option<TenantId> {
        None
    }
}

fn make_ctx(episodic: Arc<InMemoryEpisodic>, run_id: RunId, bus: &MemoryBus) -> AgentContext {
    let mut ctx = fake_context("auto-wire-test");
    ctx.episodic = episodic;
    ctx.run_id = run_id;
    ctx.kv = bus.kv.clone();
    ctx.jobs = bus.jobs.clone();
    ctx
}

fn ops_events_from_episodes(episodes: &[Episode]) -> Vec<OpsEvent> {
    episodes
        .iter()
        .filter_map(|e| match e {
            Episode::Ops(payload) => OpsEvent::from_episode_payload(payload),
            _ => None,
        })
        .collect()
}

fn fresh_work_item(title: &str) -> WorkItem {
    WorkItem::new(title, serde_json::json!({}), None, vec![])
}

// ── audited_escalation inside scope emits EscalationRaised ───────────────────

/// Agent that calls `audited_escalation` inside the SupervisedAgent run scope.
struct EscalationAgent;

#[async_trait]
impl Agent for EscalationAgent {
    type Input = ();
    type Output = ();
    type Error = std::io::Error;

    fn name(&self) -> &str {
        "escalation-agent"
    }
    fn system_prompt(&self) -> &str {
        ""
    }
    fn tools(&self) -> &[ToolDef] {
        &[]
    }
    async fn run(&self, ctx: AgentContext, _input: ()) -> Result<(), std::io::Error> {
        let esc = audited_escalation(ctx.jobs.clone());
        esc.raise(EscalationTicket::new(
            Severity::High,
            "auto-wire test",
            None,
            None,
        ))
        .await
        .map_err(|e| std::io::Error::other(format!("escalation: {e}")))?;
        Ok(())
    }
}

#[tokio::test]
async fn audited_escalation_emits_inside_run_scope() {
    let episodic = Arc::new(InMemoryEpisodic::default());
    let run_id = RunId::new();
    let bus = MemoryBus::new();

    let supervised = OpsRuntime::builder()
        .tenant_resolver(Arc::new(NoTenant))
        .spawn(EscalationAgent)
        .expect("build supervised agent");

    let ctx = make_ctx(episodic.clone(), run_id, &bus);
    supervised.run(ctx, ()).await.expect("run ok");

    let episodes = episodic.replay(run_id).await.expect("replay ok");
    let events = ops_events_from_episodes(&episodes);

    assert!(
        events
            .iter()
            .any(|e| matches!(e, OpsEvent::EscalationRaised { .. })),
        "audited_escalation must auto-wire and emit EscalationRaised; events: {events:?}"
    );
}

// ── audited_worklog inside scope emits WorkPlanned ────────────────────────────

/// Agent that calls `audited_worklog` inside the SupervisedAgent run scope.
struct WorklogAgent;

#[async_trait]
impl Agent for WorklogAgent {
    type Input = ();
    type Output = ();
    type Error = std::io::Error;

    fn name(&self) -> &str {
        "worklog-agent"
    }
    fn system_prompt(&self) -> &str {
        ""
    }
    fn tools(&self) -> &[ToolDef] {
        &[]
    }
    async fn run(&self, ctx: AgentContext, _input: ()) -> Result<(), std::io::Error> {
        let wl = audited_worklog(ctx.kv.clone());
        wl.plan(fresh_work_item("auto-wire task"))
            .await
            .map_err(|e| std::io::Error::other(format!("worklog: {e}")))?;
        Ok(())
    }
}

#[tokio::test]
async fn audited_worklog_emits_inside_run_scope() {
    let episodic = Arc::new(InMemoryEpisodic::default());
    let run_id = RunId::new();
    let bus = MemoryBus::new();

    let supervised = OpsRuntime::builder()
        .tenant_resolver(Arc::new(NoTenant))
        .spawn(WorklogAgent)
        .expect("build supervised agent");

    let ctx = make_ctx(episodic.clone(), run_id, &bus);
    supervised.run(ctx, ()).await.expect("run ok");

    let episodes = episodic.replay(run_id).await.expect("replay ok");
    let events = ops_events_from_episodes(&episodes);

    assert!(
        events
            .iter()
            .any(|e| matches!(e, OpsEvent::WorkPlanned { .. })),
        "audited_worklog must auto-wire and emit WorkPlanned; events: {events:?}"
    );
}

// ── audited_handoff inside scope emits HandoffPackaged ────────────────────────

/// Agent that calls `audited_handoff` inside the SupervisedAgent run scope.
struct HandoffAgent;

#[async_trait]
impl Agent for HandoffAgent {
    type Input = ();
    type Output = ();
    type Error = std::io::Error;

    fn name(&self) -> &str {
        "handoff-agent"
    }
    fn system_prompt(&self) -> &str {
        ""
    }
    fn tools(&self) -> &[ToolDef] {
        &[]
    }
    async fn run(&self, ctx: AgentContext, _input: ()) -> Result<(), std::io::Error> {
        let raw_sk = ed25519_dalek::SigningKey::from_bytes(&[42u8; 32]);
        let trust_registry = Arc::new(StaticSourceIdentityRegistry::from_map({
            let mut m = HashMap::new();
            m.insert(
                klieo_ops::types::AgentId("handoff-agent".into()),
                raw_sk.verifying_key(),
            );
            m
        }));
        let handoff = audited_handoff(ctx.kv.clone(), trust_registry);
        let state = HandoffState::new(
            ctx.run_id.to_string(),
            "deadbeef",
            b"state-bytes".to_vec(),
            klieo_ops::types::AgentId("handoff-agent".into()),
            None,
            Some(1),
        );
        let signer = klieo_ops::SoftwareSigner::new(raw_sk);
        handoff
            .package(state, &signer, chrono::Duration::hours(1))
            .await
            .map_err(|e| std::io::Error::other(format!("handoff: {e}")))?;
        Ok(())
    }
}

#[tokio::test]
async fn audited_handoff_emits_inside_run_scope() {
    let episodic = Arc::new(InMemoryEpisodic::default());
    let run_id = RunId::new();
    let bus = MemoryBus::new();

    let supervised = OpsRuntime::builder()
        .tenant_resolver(Arc::new(NoTenant))
        .spawn(HandoffAgent)
        .expect("build supervised agent");

    let ctx = make_ctx(episodic.clone(), run_id, &bus);
    supervised.run(ctx, ()).await.expect("run ok");

    let episodes = episodic.replay(run_id).await.expect("replay ok");
    let events = ops_events_from_episodes(&episodes);

    assert!(
        events
            .iter()
            .any(|e| matches!(e, OpsEvent::HandoffPackaged { .. })),
        "audited_handoff must auto-wire and emit HandoffPackaged; events: {events:?}"
    );
}

// ── Helpers pass through outside run scope (no panic) ────────────────────────

#[tokio::test]
async fn helpers_pass_through_outside_run_scope() {
    let bus = MemoryBus::new();

    // None of these should panic. They return unaudited instances because
    // current_run_context() returns None outside a SupervisedAgent::run scope.
    let _esc = audited_escalation(bus.jobs.clone());
    let _wl = audited_worklog(bus.kv.clone());

    let trust_registry = Arc::new(StaticSourceIdentityRegistry::from_map(HashMap::new()));
    let _ho = audited_handoff(bus.kv.clone(), trust_registry);
}

// ── scope_run_context wires helpers directly (unit-style integration) ─────────

#[tokio::test]
async fn scope_run_context_wires_audited_escalation() {
    let bus = MemoryBus::new();
    let episodic = Arc::new(InMemoryEpisodic::default());
    let run_id = RunId::new();

    let ctx = RunContext::new(episodic.clone() as Arc<dyn EpisodicMemory>, run_id);

    scope_run_context(Some(ctx), async {
        let esc = audited_escalation(bus.jobs.clone());
        esc.raise(EscalationTicket::new(
            Severity::Low,
            "scope test",
            None,
            None,
        ))
        .await
        .expect("raise");
    })
    .await;

    let episodes = episodic.replay(run_id).await.expect("replay");
    let events = ops_events_from_episodes(&episodes);
    assert!(
        events
            .iter()
            .any(|e| matches!(e, OpsEvent::EscalationRaised { .. })),
        "scope_run_context must wire audited_escalation; events: {events:?}"
    );
}
