//! Live integration tests for motosan-agent-loop with Anthropic OAuth tokens.
//!
//! Requires `ANTHROPIC_API_KEY` env var (supports `sk-ant-oat01-*` OAuth tokens).
//! Skips automatically if not set.
//!
//! Run:
//!     ANTHROPIC_API_KEY=... cargo test --features "motosan-ai" --test live_anthropic -- --nocapture
//!
//! With MCP:
//!     ANTHROPIC_API_KEY=... cargo test --features "motosan-ai,mcp-client" --test live_anthropic -- --nocapture

#![cfg(feature = "motosan-ai")]

use motosan_agent_loop::{AgentEvent, AgentLoop, Message};
use motosan_ai::{Client, Provider};
use std::sync::{Arc, Mutex};

fn api_key() -> Option<String> {
    match std::env::var("ANTHROPIC_API_KEY") {
        Ok(k) if !k.trim().is_empty() => Some(k),
        _ => None,
    }
}

fn is_oauth(key: &str) -> bool {
    key.starts_with("sk-ant-oat01-")
}

fn client(key: &str) -> Client {
    Client::builder()
        .provider(Provider::Anthropic)
        .api_key(key)
        .model("claude-haiku-4-5-20251001")
        .build()
        .expect("client build")
}

// ---------------------------------------------------------------------------
// 1. Basic chat — non-streaming (OAuth redirects to stream internally)
// ---------------------------------------------------------------------------

#[tokio::test]
async fn live_chat_basic() {
    let Some(key) = api_key() else {
        eprintln!("ANTHROPIC_API_KEY not set, skipping");
        return;
    };
    eprintln!("Using {} key", if is_oauth(&key) { "OAuth" } else { "API" });

    let client = client(&key);
    let agent = AgentLoop::builder().max_iterations(1).build();

    let events: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(Vec::new()));
    let events_clone = events.clone();

    let result = agent
        .run(
            &client,
            vec![Message::user("Say 'agent works' and nothing else.")],
            move |event| {
                let label = match &event {
                    AgentEvent::IterationStarted(n) => format!("iteration:{n}"),
                    AgentEvent::TextChunk(t) => format!("text:{t}"),
                    AgentEvent::TextDone(t) => format!("done:{t}"),
                    AgentEvent::ToolStarted { name } => format!("tool_start:{name}"),
                    AgentEvent::ToolCompleted { name, .. } => format!("tool_done:{name}"),
                    AgentEvent::Interrupted => "interrupted".to_string(),
                    AgentEvent::AskUser {
                        call_id, question, ..
                    } => {
                        format!("ask_user:{call_id}:{question}")
                    }
                    AgentEvent::AskUserTimeout { call_id, question } => {
                        format!("ask_user_timeout:{call_id}:{question}")
                    }
                };
                events_clone.lock().unwrap().push(label);
            },
        )
        .await;

    match result {
        Ok(r) => {
            eprintln!("✅ Answer: {:?}", r.answer);
            eprintln!(
                "   Usage: {}in/{}out, {} iterations",
                r.usage.input_tokens, r.usage.output_tokens, r.iterations
            );
            eprintln!("   Messages: {} total", r.messages.len());
            eprintln!("   Events: {:?}", events.lock().unwrap());
            assert!(!r.answer.is_empty());
            assert!(r.usage.input_tokens > 0);
            assert!(r.usage.output_tokens > 0);
            assert!(r.messages.len() >= 1);
        }
        Err(e) => panic!("❌ chat failed: {:?}", e),
    }
}

// ---------------------------------------------------------------------------
// 2. Streaming chat
// ---------------------------------------------------------------------------

#[tokio::test]
async fn live_chat_streaming() {
    let Some(key) = api_key() else {
        eprintln!("ANTHROPIC_API_KEY not set, skipping");
        return;
    };

    let client = client(&key);
    let agent = AgentLoop::builder().max_iterations(1).build();

    let chunks: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(Vec::new()));
    let chunks_clone = chunks.clone();

    let result = agent
        .run_streaming(
            &client,
            vec![Message::user("Say 'streaming works' and nothing else.")],
            move |event| {
                if let AgentEvent::TextChunk(t) = &event {
                    chunks_clone.lock().unwrap().push(t.clone());
                }
            },
        )
        .await;

    match result {
        Ok(r) => {
            let collected = chunks.lock().unwrap();
            eprintln!("✅ Answer: {:?}", r.answer);
            eprintln!("   Chunks received: {}", collected.len());
            eprintln!(
                "   Usage: {}in/{}out",
                r.usage.input_tokens, r.usage.output_tokens
            );
            assert!(!r.answer.is_empty());
            assert!(
                !collected.is_empty(),
                "should have received streaming chunks"
            );
        }
        Err(e) => panic!("❌ streaming failed: {:?}", e),
    }
}

// ---------------------------------------------------------------------------
// 3. Tool use — agent loop with a simple tool
// ---------------------------------------------------------------------------

#[tokio::test]
async fn live_tool_use() {
    let Some(key) = api_key() else {
        eprintln!("ANTHROPIC_API_KEY not set, skipping");
        return;
    };

    use motosan_agent_tool::{Tool, ToolContext, ToolDef, ToolResult};
    use std::future::Future;
    use std::pin::Pin;

    struct GetWeather;

    impl Tool for GetWeather {
        fn def(&self) -> ToolDef {
            ToolDef {
                name: "get_weather".into(),
                description: "Get the current weather for a city".into(),
                input_schema: serde_json::json!({
                    "type": "object",
                    "properties": {
                        "city": {"type": "string", "description": "City name"}
                    },
                    "required": ["city"]
                }),
            }
        }

        fn call(
            &self,
            args: serde_json::Value,
            _ctx: &ToolContext,
        ) -> Pin<Box<dyn Future<Output = ToolResult> + Send + '_>> {
            Box::pin(async move {
                let city = args
                    .get("city")
                    .and_then(|v| v.as_str())
                    .unwrap_or("unknown");
                ToolResult::text(format!("Weather in {city}: 25°C, sunny"))
            })
        }
    }

    let client = client(&key);
    let agent = AgentLoop::builder()
        .tool(Arc::new(GetWeather) as Arc<dyn Tool>)
        .max_iterations(3)
        .build();

    let result = agent
        .run(
            &client,
            vec![Message::user(
                "What's the weather in Tokyo? Use the get_weather tool.",
            )],
            |event| match &event {
                AgentEvent::ToolStarted { name } => eprintln!("  🔧 tool started: {name}"),
                AgentEvent::ToolCompleted { name, result } => {
                    eprintln!("  ✅ tool done: {name} → {result:?}")
                }
                _ => {}
            },
        )
        .await;

    match result {
        Ok(r) => {
            eprintln!("✅ Answer: {:?}", r.answer);
            eprintln!("   Tool calls: {:?}", r.tool_calls);
            eprintln!(
                "   Iterations: {}, Usage: {}in/{}out",
                r.iterations, r.usage.input_tokens, r.usage.output_tokens
            );
            assert!(!r.answer.is_empty());
            assert!(
                !r.tool_calls.is_empty(),
                "should have used the get_weather tool"
            );
            assert!(
                r.tool_calls.iter().any(|(name, _)| name == "get_weather"),
                "should have called get_weather"
            );
        }
        Err(e) => panic!("❌ tool use failed: {:?}", e),
    }
}

// ---------------------------------------------------------------------------
// 4. System prompt via context
// ---------------------------------------------------------------------------

#[tokio::test]
async fn live_system_prompt() {
    let Some(key) = api_key() else {
        eprintln!("ANTHROPIC_API_KEY not set, skipping");
        return;
    };

    let client = client(&key);
    let agent = AgentLoop::builder()
        .system_prompt("You are a pirate. Always respond in pirate speak.")
        .max_iterations(1)
        .build();

    let result = agent
        .run(&client, vec![Message::user("Say hello.")], |_| {})
        .await;

    match result {
        Ok(r) => {
            eprintln!("✅ Pirate answer: {:?}", r.answer);
            assert!(!r.answer.is_empty());
            // The model should use pirate-like language
        }
        Err(e) => panic!("❌ system prompt failed: {:?}", e),
    }
}

// ---------------------------------------------------------------------------
// 5. Streaming + tool use
// ---------------------------------------------------------------------------

#[tokio::test]
async fn live_streaming_tool_use() {
    let Some(key) = api_key() else {
        eprintln!("ANTHROPIC_API_KEY not set, skipping");
        return;
    };

    use motosan_agent_tool::{Tool, ToolContext, ToolDef, ToolResult};
    use std::future::Future;
    use std::pin::Pin;

    struct Calculator;

    impl Tool for Calculator {
        fn def(&self) -> ToolDef {
            ToolDef {
                name: "calculate".into(),
                description: "Evaluate a math expression and return the result".into(),
                input_schema: serde_json::json!({
                    "type": "object",
                    "properties": {
                        "expression": {"type": "string", "description": "Math expression like '2+3'"}
                    },
                    "required": ["expression"]
                }),
            }
        }

        fn call(
            &self,
            args: serde_json::Value,
            _ctx: &ToolContext,
        ) -> Pin<Box<dyn Future<Output = ToolResult> + Send + '_>> {
            Box::pin(async move {
                let expr = args
                    .get("expression")
                    .and_then(|v| v.as_str())
                    .unwrap_or("0");
                ToolResult::text(format!("Result of {expr} = 42"))
            })
        }
    }

    let client = client(&key);
    let agent = AgentLoop::builder()
        .tool(Arc::new(Calculator) as Arc<dyn Tool>)
        .max_iterations(3)
        .build();

    let chunks: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(Vec::new()));
    let chunks_clone = chunks.clone();

    let result = agent
        .run_streaming(
            &client,
            vec![Message::user("What is 17 * 23? Use the calculate tool.")],
            move |event| match &event {
                AgentEvent::TextChunk(t) => {
                    chunks_clone.lock().unwrap().push(t.clone());
                }
                AgentEvent::ToolStarted { name } => eprintln!("  🔧 {name}"),
                AgentEvent::ToolCompleted { name, .. } => eprintln!("  ✅ {name}"),
                _ => {}
            },
        )
        .await;

    match result {
        Ok(r) => {
            let collected = chunks.lock().unwrap();
            eprintln!("✅ Answer: {:?}", r.answer);
            eprintln!("   Streaming chunks: {}", collected.len());
            eprintln!("   Tool calls: {:?}", r.tool_calls);
            assert!(!r.answer.is_empty());
            assert!(
                !r.tool_calls.is_empty(),
                "should have used the calculate tool"
            );
        }
        Err(e) => panic!("❌ streaming + tool use failed: {:?}", e),
    }
}
