#![allow(clippy::unwrap_used, clippy::expect_used)]

use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;

use async_trait::async_trait;
use capo_agent::{AppBuilder, Config, Decision, UiEvent};
use futures::StreamExt;
use motosan_agent_loop::{ChatOutput, LlmClient, LlmResponse, Message, ToolCallItem};
use motosan_agent_tool::ToolDef;
use tempfile::tempdir;
use tokio::sync::mpsc;

#[derive(Default)]
struct BashLlm {
    turn: AtomicUsize,
}

#[async_trait]
impl LlmClient for BashLlm {
    async fn chat(
        &self,
        _messages: &[Message],
        _tools: &[ToolDef],
    ) -> motosan_agent_loop::Result<ChatOutput> {
        let turn = self.turn.fetch_add(1, Ordering::SeqCst);
        let r = match turn {
            0 => LlmResponse::ToolCalls(vec![ToolCallItem {
                id: "b1".into(),
                name: "bash".into(),
                args: serde_json::json!({"command":"rm -rf /"}),
            }]),
            _ => LlmResponse::Message("done".into()),
        };
        Ok(ChatOutput::new(r))
    }
}

#[tokio::test]
async fn bash_call_triggers_permission_prompt_and_deny_produces_tool_error() {
    let dir = tempdir().unwrap();

    // Empty policy file → nothing on the allowlist.
    let pol = dir.path().join("permissions.toml");
    tokio::fs::write(&pol, "").await.unwrap();

    let mut cfg = Config::default();
    cfg.anthropic.api_key = Some("sk-unused".into());

    let (ui_tx, mut ui_rx) = mpsc::channel::<UiEvent>(32);

    let app = AppBuilder::new()
        .with_config(cfg)
        .with_cwd(dir.path())
        .with_builtin_tools()
        .with_llm(Arc::new(BashLlm::default()))
        .with_permissions_config(pol)
        .with_ui_channel(ui_tx.clone())
        .build()
        .await
        .expect("build");

    // Drive the agent.
    let agent_handle = tokio::spawn(async move {
        let events: Vec<UiEvent> = app.send_user_message("do it".into()).collect().await;
        events
    });

    // Intercept the permission prompt and deny it.
    let prompt = tokio::time::timeout(std::time::Duration::from_secs(3), async {
        while let Some(ev) = ui_rx.recv().await {
            if let UiEvent::PermissionRequested { resolver, .. } = ev {
                return resolver;
            }
        }
        panic!("no PermissionRequested emitted");
    })
    .await
    .expect("timeout waiting for prompt");

    prompt.send(Decision::Denied("test denies".into())).ok();

    let events = tokio::time::timeout(std::time::Duration::from_secs(3), agent_handle)
        .await
        .expect("agent timeout")
        .expect("agent panicked");

    let has_tool_completed_with_error = events.iter().any(|e| {
        matches!(
            e,
            UiEvent::ToolCallCompleted { result, .. } if result.is_error
        )
    });
    assert!(
        has_tool_completed_with_error,
        "expected denied tool error, got: {events:?}"
    );
}
