//! Smoke tests for [`klieo_core::test_utils::TestContext`].

use klieo_core::agent::{Agent, AgentContext};
use klieo_core::ids::ThreadId;
use klieo_core::llm::{Message, Role, ToolCall, ToolDef};
use klieo_core::memory::Episode;
use klieo_core::runtime::{run_steps, RunOptions};
use klieo_core::test_utils::{FakeLlmStep, TestContext};

#[derive(Clone)]
struct EchoAgent;

#[async_trait::async_trait]
impl Agent for EchoAgent {
    type Input = String;
    type Output = String;
    type Error = klieo_core::Error;

    fn name(&self) -> &str {
        "echo"
    }
    fn system_prompt(&self) -> &str {
        ""
    }
    fn tools(&self) -> &[ToolDef] {
        &[]
    }

    async fn run(
        &self,
        ctx: AgentContext,
        input: Self::Input,
    ) -> Result<Self::Output, Self::Error> {
        let thread = ThreadId::new("test");
        ctx.short_term
            .append(
                thread.clone(),
                Message {
                    role: Role::User,
                    content: input,
                    tool_calls: vec![],
                    tool_call_id: None,
                },
            )
            .await?;
        run_steps(&ctx, "", thread, RunOptions::default()).await
    }
}

#[tokio::test]
async fn test_context_default_runs_an_agent() {
    let mut ctx = TestContext::default()
        .with_canned_llm_responses(vec![FakeLlmStep::Text("hello back".into())]);
    let out = ctx.run(&EchoAgent, "hello".into()).await.unwrap();
    assert_eq!(out, "hello back");
    // The fake LLM was invoked exactly once for this single-step run.
    // We can't assert call count directly (FakeLlmClient exposes only
    // stream call count), but the scripted response landing on the
    // output is sufficient evidence.
}

#[tokio::test]
async fn test_context_with_tool_dispatches() {
    let mut ctx = TestContext::default()
        .with_canned_llm_responses(vec![
            FakeLlmStep::ToolCalls(vec![ToolCall::new(
                "t1",
                "echo",
                serde_json::json!({"x": 1}),
            )]),
            FakeLlmStep::Text("done".into()),
        ])
        .with_tool("echo", "echoes args back", |args| {
            Ok(serde_json::json!({"echoed": args}))
        });

    let out = ctx.run(&EchoAgent, "trigger".into()).await.unwrap();
    assert_eq!(out, "done");

    let eps = ctx.recorded_episodes().await;
    let tool_count = eps
        .iter()
        .filter(|e| matches!(e, Episode::ToolCall { .. }))
        .count();
    assert_eq!(tool_count, 1, "exactly one tool call recorded");
}

#[tokio::test]
async fn test_context_with_short_term_history_seeds_run() {
    // Seeded user message lands in short-term memory before the agent
    // runs. After the run, short-term should hold:
    //   seeded user → user (from agent.run) → assistant
    let mut ctx = TestContext::default()
        .with_canned_llm_responses(vec![FakeLlmStep::Text("ack".into())])
        .with_short_term_history(vec![Message {
            role: Role::User,
            content: "prior turn".into(),
            tool_calls: vec![],
            tool_call_id: None,
        }]);

    let out = ctx.run(&EchoAgent, "now".into()).await.unwrap();
    assert_eq!(out, "ack");
}

#[tokio::test]
async fn recorded_episodes_returns_all_recorded_events() {
    let mut ctx = TestContext::default()
        .with_canned_llm_responses(vec![FakeLlmStep::Text("ok".into())])
        .with_agent_name("named-agent");
    let _ = ctx.run(&EchoAgent, "hi".into()).await.unwrap();

    let eps = ctx.recorded_episodes().await;
    assert!(
        eps.iter().any(|e| matches!(e, Episode::Started { .. })),
        "Started recorded"
    );
    assert!(
        eps.iter().any(|e| matches!(e, Episode::LlmCall { .. })),
        "LlmCall recorded"
    );
    assert!(
        eps.iter().any(|e| matches!(e, Episode::Completed)),
        "Completed recorded"
    );
}
