//! Tests for Claude Code JSON event processing — covers both stream event routing
//! and the top-level JSON event dispatcher.

use super::stream::handle_stream_event;
use super::*;
use crate::llm::types::{Message, Role};
use serde_json::json;
use std::sync::atomic::{AtomicBool, Ordering};

type Channels = (
    tokio::sync::mpsc::UnboundedSender<String>,
    tokio::sync::mpsc::UnboundedReceiver<String>,
    tokio::sync::mpsc::UnboundedSender<String>,
    tokio::sync::mpsc::UnboundedReceiver<String>,
    tokio::sync::mpsc::UnboundedSender<String>,
    tokio::sync::mpsc::UnboundedReceiver<String>,
    std::sync::mpsc::Sender<Message>,
    std::sync::mpsc::Receiver<Message>,
);

fn make_channels() -> Channels {
    let (tok_tx, tok_rx) = tokio::sync::mpsc::unbounded_channel();
    let (thi_tx, thi_rx) = tokio::sync::mpsc::unbounded_channel();
    let (act_tx, act_rx) = tokio::sync::mpsc::unbounded_channel();
    let (msg_tx, msg_rx) = std::sync::mpsc::channel();
    (
        tok_tx, tok_rx, thi_tx, thi_rx, act_tx, act_rx, msg_tx, msg_rx,
    )
}

#[test]
fn handle_stream_event_text_delta_sends_token() {
    let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel();
    let (ttx, _trx) = tokio::sync::mpsc::unbounded_channel::<String>();
    let (atx, _arx) = tokio::sync::mpsc::unbounded_channel::<String>();
    let event = json!({
        "type": "content_block_delta",
        "delta": {"type": "text_delta", "text": "hello "}
    });
    handle_stream_event(&event, &tx, &ttx, &atx);
    drop(tx);
    let tokens: Vec<_> = {
        let mut v = vec![];
        while let Ok(t) = rx.try_recv() {
            v.push(t);
        }
        v
    };
    assert_eq!(tokens, vec!["hello "]);
}

#[test]
fn handle_stream_event_input_json_delta_not_sent() {
    // Tool argument JSON deltas should NOT be sent as text tokens
    let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel();
    let (ttx, _trx) = tokio::sync::mpsc::unbounded_channel::<String>();
    let (atx, _arx) = tokio::sync::mpsc::unbounded_channel::<String>();
    let event = json!({
        "type": "content_block_delta",
        "delta": {"type": "input_json_delta", "partial_json": "{\"path\":"}
    });
    handle_stream_event(&event, &tx, &ttx, &atx);
    drop(tx);
    let tokens: Vec<String> = {
        let mut v = vec![];
        while let Ok(t) = rx.try_recv() {
            v.push(t);
        }
        v
    };
    assert!(tokens.is_empty());
}

#[test]
fn handle_stream_event_thinking_delta_routes_to_thinking_channel() {
    let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel();
    let (ttx, mut trx) = tokio::sync::mpsc::unbounded_channel::<String>();
    let (atx, _arx) = tokio::sync::mpsc::unbounded_channel::<String>();
    let event = json!({
        "type": "content_block_delta",
        "delta": {"type": "thinking_delta", "thinking": "I should check the file"}
    });
    handle_stream_event(&event, &tx, &ttx, &atx);
    drop(tx);
    drop(ttx);
    // thinking token must NOT appear in the regular token channel
    let tokens: Vec<String> = {
        let mut v = vec![];
        while let Ok(t) = rx.try_recv() {
            v.push(t);
        }
        v
    };
    assert!(
        tokens.is_empty(),
        "thinking token leaked into token channel"
    );
    // thinking token must appear in the dedicated thinking channel, without prefix
    let thinking: Vec<String> = {
        let mut v = vec![];
        while let Ok(t) = trx.try_recv() {
            v.push(t);
        }
        v
    };
    assert_eq!(thinking, vec!["I should check the file"]);
}

#[test]
fn handle_stream_event_error_sends_error_token() {
    let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel();
    let (ttx, _trx) = tokio::sync::mpsc::unbounded_channel::<String>();
    let (atx, _arx) = tokio::sync::mpsc::unbounded_channel::<String>();
    let event = json!({
        "type": "error",
        "error": {"type": "overloaded_error", "message": "Overloaded"}
    });
    handle_stream_event(&event, &tx, &ttx, &atx);
    drop(tx);
    let tokens: Vec<String> = {
        let mut v = vec![];
        while let Ok(t) = rx.try_recv() {
            v.push(t);
        }
        v
    };
    assert_eq!(tokens, vec!["\n[error: Overloaded]\n"]);
}

#[test]
fn handle_stream_event_unknown_type_is_noop() {
    let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel();
    let (ttx, _trx) = tokio::sync::mpsc::unbounded_channel::<String>();
    let (atx, _arx) = tokio::sync::mpsc::unbounded_channel::<String>();
    let event = json!({"type": "ping"});
    handle_stream_event(&event, &tx, &ttx, &atx);
    drop(tx);
    let tokens: Vec<String> = {
        let mut v = vec![];
        while let Ok(t) = rx.try_recv() {
            v.push(t);
        }
        v
    };
    assert!(tokens.is_empty());
}

#[test]
fn handle_stream_event_tool_use_start_sends_activity() {
    let (tx, _rx) = tokio::sync::mpsc::unbounded_channel::<String>();
    let (ttx, _trx) = tokio::sync::mpsc::unbounded_channel::<String>();
    let (atx, mut arx) = tokio::sync::mpsc::unbounded_channel::<String>();
    let event = json!({
        "type": "content_block_start",
        "index": 1,
        "content_block": {"type": "tool_use", "id": "toolu_1", "name": "Read", "input": {}}
    });
    handle_stream_event(&event, &tx, &ttx, &atx);
    drop(atx);
    let activities: Vec<String> = {
        let mut v = vec![];
        while let Ok(a) = arx.try_recv() {
            v.push(a);
        }
        v
    };
    assert_eq!(activities, vec!["Read"]);
}

#[test]
fn handle_stream_event_text_block_start_no_activity() {
    let (tx, _rx) = tokio::sync::mpsc::unbounded_channel::<String>();
    let (ttx, _trx) = tokio::sync::mpsc::unbounded_channel::<String>();
    let (atx, mut arx) = tokio::sync::mpsc::unbounded_channel::<String>();
    let event = json!({
        "type": "content_block_start",
        "index": 0,
        "content_block": {"type": "text", "text": ""}
    });
    handle_stream_event(&event, &tx, &ttx, &atx);
    drop(atx);
    let activities: Vec<String> = {
        let mut v = vec![];
        while let Ok(a) = arx.try_recv() {
            v.push(a);
        }
        v
    };
    assert!(activities.is_empty());
}

#[test]
fn process_json_event_result_returns_true() {
    let (tok_tx, _tok_rx, thi_tx, _thi_rx, act_tx, _act_rx, msg_tx, _msg_rx) = make_channels();
    let (sid_tx, _sid_rx) = tokio::sync::oneshot::channel::<String>();
    let mut sid_tx_opt = Some(sid_tx);
    let json = json!({"type": "result", "subtype": "success"});
    assert!(process_json_event(
        &json,
        &tok_tx,
        &thi_tx,
        &act_tx,
        &msg_tx,
        &mut sid_tx_opt,
        &mut None,
        &AtomicBool::new(false),
    ));
}

#[test]
fn process_json_event_result_captures_usage() {
    let (tok_tx, _tok_rx, thi_tx, _thi_rx, act_tx, _act_rx, msg_tx, _msg_rx) = make_channels();
    let mut sid_tx = None::<tokio::sync::oneshot::Sender<String>>;
    let (usage_tx, mut usage_rx) = tokio::sync::oneshot::channel::<crate::agents::TokenUsage>();
    let mut usage_tx_opt = Some(usage_tx);
    let json = json!({
        "type": "result",
        "subtype": "success",
        "total_cost_usd": 0.42,
        "usage": {
            "input_tokens": 10,
            "output_tokens": 20,
            "cache_creation_input_tokens": 100,
            "cache_read_input_tokens": 60000
        }
    });
    assert!(process_json_event(
        &json,
        &tok_tx,
        &thi_tx,
        &act_tx,
        &msg_tx,
        &mut sid_tx,
        &mut usage_tx_opt,
        &AtomicBool::new(false),
    ));
    assert!(usage_tx_opt.is_none(), "usage_tx should be consumed");
    let usage = usage_rx.try_recv().unwrap();
    assert_eq!(usage.cache_read_input_tokens, 60000);
}

#[test]
fn process_json_event_result_without_usage_sends_nothing() {
    let (tok_tx, _tok_rx, thi_tx, _thi_rx, act_tx, _act_rx, msg_tx, _msg_rx) = make_channels();
    let mut sid_tx = None::<tokio::sync::oneshot::Sender<String>>;
    let (usage_tx, mut usage_rx) = tokio::sync::oneshot::channel::<crate::agents::TokenUsage>();
    let mut usage_tx_opt = Some(usage_tx);
    let json = json!({"type": "result", "subtype": "success"});
    assert!(process_json_event(
        &json,
        &tok_tx,
        &thi_tx,
        &act_tx,
        &msg_tx,
        &mut sid_tx,
        &mut usage_tx_opt,
        &AtomicBool::new(false),
    ));
    // The result event carried no "usage" field, so the sender is dropped
    // without sending — the receiver observes a closed channel, not a value.
    assert!(
        usage_rx.try_recv().is_err(),
        "no usage field means nothing sent"
    );
}

#[test]
fn process_json_event_system_returns_false() {
    let (tok_tx, _tok_rx, thi_tx, _thi_rx, act_tx, _act_rx, msg_tx, _msg_rx) = make_channels();
    let mut sid_tx = None::<tokio::sync::oneshot::Sender<String>>;
    let json = json!({"type": "system", "subtype": "init", "apiKeySource": "env"});
    assert!(!process_json_event(
        &json,
        &tok_tx,
        &thi_tx,
        &act_tx,
        &msg_tx,
        &mut sid_tx,
        &mut None,
        &AtomicBool::new(false),
    ));
}

#[test]
fn process_json_event_rate_limit_returns_false() {
    let (tok_tx, _tok_rx, thi_tx, _thi_rx, act_tx, _act_rx, msg_tx, _msg_rx) = make_channels();
    let mut sid_tx = None::<tokio::sync::oneshot::Sender<String>>;
    let json = json!({"type": "rate_limit_event"});
    assert!(!process_json_event(
        &json,
        &tok_tx,
        &thi_tx,
        &act_tx,
        &msg_tx,
        &mut sid_tx,
        &mut None,
        &AtomicBool::new(false),
    ));
}

#[test]
fn process_json_event_unknown_type_returns_false() {
    let (tok_tx, _tok_rx, thi_tx, _thi_rx, act_tx, _act_rx, msg_tx, _msg_rx) = make_channels();
    let mut sid_tx = None::<tokio::sync::oneshot::Sender<String>>;
    let json = json!({"type": "some_future_event"});
    assert!(!process_json_event(
        &json,
        &tok_tx,
        &thi_tx,
        &act_tx,
        &msg_tx,
        &mut sid_tx,
        &mut None,
        &AtomicBool::new(false),
    ));
}

#[test]
fn process_json_event_no_type_returns_false() {
    let (tok_tx, _tok_rx, thi_tx, _thi_rx, act_tx, _act_rx, msg_tx, _msg_rx) = make_channels();
    let mut sid_tx = None::<tokio::sync::oneshot::Sender<String>>;
    let json = json!({"content": "no type field"});
    assert!(!process_json_event(
        &json,
        &tok_tx,
        &thi_tx,
        &act_tx,
        &msg_tx,
        &mut sid_tx,
        &mut None,
        &AtomicBool::new(false),
    ));
}

#[test]
fn process_json_event_captures_session_id() {
    let (tok_tx, _tok_rx, thi_tx, _thi_rx, act_tx, _act_rx, msg_tx, _msg_rx) = make_channels();
    let (sid_tx, mut sid_rx) = tokio::sync::oneshot::channel::<String>();
    let mut sid_tx_opt = Some(sid_tx);
    let json = json!({"type": "system", "session_id": "sess-abc-123"});
    process_json_event(
        &json,
        &tok_tx,
        &thi_tx,
        &act_tx,
        &msg_tx,
        &mut sid_tx_opt,
        &mut None,
        &AtomicBool::new(false),
    );
    // sid_tx should have been consumed
    assert!(sid_tx_opt.is_none());
    let received = sid_rx.try_recv().unwrap();
    assert_eq!(received, "sess-abc-123");
}

#[test]
fn process_json_event_preserves_sid_tx_if_no_session_id() {
    let (tok_tx, _tok_rx, thi_tx, _thi_rx, act_tx, _act_rx, msg_tx, _msg_rx) = make_channels();
    let (sid_tx, _sid_rx) = tokio::sync::oneshot::channel::<String>();
    let mut sid_tx_opt = Some(sid_tx);
    let json = json!({"type": "system"});
    process_json_event(
        &json,
        &tok_tx,
        &thi_tx,
        &act_tx,
        &msg_tx,
        &mut sid_tx_opt,
        &mut None,
        &AtomicBool::new(false),
    );
    // sid_tx should still be present since no session_id in event
    assert!(sid_tx_opt.is_some());
}

#[test]
fn process_json_event_stream_event_forwards_token() {
    let (tok_tx, mut tok_rx, thi_tx, _thi_rx, act_tx, _act_rx, msg_tx, _msg_rx) = make_channels();
    let mut sid_tx = None::<tokio::sync::oneshot::Sender<String>>;
    let json = json!({
        "type": "stream_event",
        "session_id": "s1",
        "event": {
            "type": "content_block_delta",
            "delta": {"type": "text_delta", "text": "word"}
        }
    });
    assert!(!process_json_event(
        &json,
        &tok_tx,
        &thi_tx,
        &act_tx,
        &msg_tx,
        &mut sid_tx,
        &mut None,
        &AtomicBool::new(false),
    ));
    drop(tok_tx);
    let tokens: Vec<String> = {
        let mut v = vec![];
        while let Ok(t) = tok_rx.try_recv() {
            v.push(t);
        }
        v
    };
    assert_eq!(tokens, vec!["word"]);
}

#[test]
fn process_json_event_stream_event_tool_use_fires_activity() {
    // This is the primary activity path: stream_event wrapping content_block_start
    // with a tool_use block. Requires --include-partial-messages to be enabled.
    let (tok_tx, _tok_rx, thi_tx, _thi_rx, act_tx, mut act_rx, msg_tx, _msg_rx) = make_channels();
    let mut sid_tx = None::<tokio::sync::oneshot::Sender<String>>;
    let json = json!({
        "type": "stream_event",
        "session_id": "s1",
        "event": {
            "type": "content_block_start",
            "index": 1,
            "content_block": {"type": "tool_use", "id": "toolu_abc", "name": "Bash", "input": {}}
        }
    });
    assert!(!process_json_event(
        &json,
        &tok_tx,
        &thi_tx,
        &act_tx,
        &msg_tx,
        &mut sid_tx,
        &mut None,
        &AtomicBool::new(false),
    ));
    drop(act_tx);
    let activities: Vec<String> = {
        let mut v = vec![];
        while let Ok(a) = act_rx.try_recv() {
            v.push(a);
        }
        v
    };
    assert_eq!(activities, vec!["Bash"]);
}

#[test]
fn process_json_event_assistant_with_tool_use_fires_activity() {
    let (tok_tx, _tok_rx, thi_tx, _thi_rx, act_tx, mut act_rx, msg_tx, _msg_rx) = make_channels();
    let mut sid_tx = None::<tokio::sync::oneshot::Sender<String>>;
    let json = json!({
        "type": "assistant",
        "message": {
            "content": [
                {"type": "text", "text": "Let me read that file."},
                {"type": "tool_use", "id": "toolu_1", "name": "Read", "input": {"file_path": "/foo.rs"}}
            ]
        }
    });
    assert!(!process_json_event(
        &json,
        &tok_tx,
        &thi_tx,
        &act_tx,
        &msg_tx,
        &mut sid_tx,
        &mut None,
        &AtomicBool::new(false),
    ));
    drop(act_tx);
    let activities: Vec<String> = {
        let mut v = vec![];
        while let Ok(a) = act_rx.try_recv() {
            v.push(a);
        }
        v
    };
    assert_eq!(activities, vec!["Read"]);
}

#[test]
fn process_json_event_assistant_with_multiple_tool_uses_fires_all_activities() {
    let (tok_tx, _tok_rx, thi_tx, _thi_rx, act_tx, mut act_rx, msg_tx, _msg_rx) = make_channels();
    let mut sid_tx = None::<tokio::sync::oneshot::Sender<String>>;
    let json = json!({
        "type": "assistant",
        "message": {
            "content": [
                {"type": "tool_use", "id": "id1", "name": "Glob", "input": {}},
                {"type": "tool_use", "id": "id2", "name": "Bash", "input": {}}
            ]
        }
    });
    assert!(!process_json_event(
        &json,
        &tok_tx,
        &thi_tx,
        &act_tx,
        &msg_tx,
        &mut sid_tx,
        &mut None,
        &AtomicBool::new(false),
    ));
    drop(act_tx);
    let activities: Vec<String> = {
        let mut v = vec![];
        while let Ok(a) = act_rx.try_recv() {
            v.push(a);
        }
        v
    };
    assert_eq!(activities, vec!["Glob", "Bash"]);
}

#[test]
fn process_json_event_assistant_text_only_no_activity() {
    let (tok_tx, _tok_rx, thi_tx, _thi_rx, act_tx, mut act_rx, msg_tx, _msg_rx) = make_channels();
    let mut sid_tx = None::<tokio::sync::oneshot::Sender<String>>;
    let json = json!({
        "type": "assistant",
        "message": {
            "content": [{"type": "text", "text": "Just text, no tools."}]
        }
    });
    assert!(!process_json_event(
        &json,
        &tok_tx,
        &thi_tx,
        &act_tx,
        &msg_tx,
        &mut sid_tx,
        &mut None,
        &AtomicBool::new(false),
    ));
    drop(act_tx);
    let activities: Vec<String> = {
        let mut v = vec![];
        while let Ok(a) = act_rx.try_recv() {
            v.push(a);
        }
        v
    };
    assert!(activities.is_empty());
}

#[test]
fn process_json_event_assistant_event_parses_message() {
    let (tok_tx, _tok_rx, thi_tx, _thi_rx, act_tx, _act_rx, msg_tx, msg_rx) = make_channels();
    let mut sid_tx = None::<tokio::sync::oneshot::Sender<String>>;
    let json = json!({
        "type": "assistant",
        "message": {
            "content": [{"type": "text", "text": "Hi!"}]
        }
    });
    assert!(!process_json_event(
        &json,
        &tok_tx,
        &thi_tx,
        &act_tx,
        &msg_tx,
        &mut sid_tx,
        &mut None,
        &AtomicBool::new(false),
    ));
    drop(msg_tx);
    let msgs: Vec<Message> = msg_rx.try_iter().collect();
    assert_eq!(msgs.len(), 1);
    assert_eq!(msgs[0].content, "Hi!");
}

#[test]
fn process_json_event_user_event_parses_tool_results() {
    let (tok_tx, _tok_rx, thi_tx, _thi_rx, act_tx, _act_rx, msg_tx, msg_rx) = make_channels();
    let mut sid_tx = None::<tokio::sync::oneshot::Sender<String>>;
    let json = json!({
        "type": "user",
        "message": {
            "content": [{"type": "tool_result", "tool_use_id": "tid1", "content": "done"}]
        }
    });
    assert!(!process_json_event(
        &json,
        &tok_tx,
        &thi_tx,
        &act_tx,
        &msg_tx,
        &mut sid_tx,
        &mut None,
        &AtomicBool::new(false),
    ));
    drop(msg_tx);
    let msgs: Vec<Message> = msg_rx.try_iter().collect();
    assert_eq!(msgs.len(), 1);
    assert_eq!(msgs[0].role, Role::Tool);
    assert_eq!(msgs[0].content, "done");
}

#[test]
fn process_json_event_assistant_without_content_array_is_noop() {
    let (tok_tx, _tok_rx, thi_tx, _thi_rx, act_tx, _act_rx, msg_tx, msg_rx) = make_channels();
    let mut sid_tx = None::<tokio::sync::oneshot::Sender<String>>;
    let json = json!({
        "type": "assistant",
        "message": {"content": "not an array"}
    });
    assert!(!process_json_event(
        &json,
        &tok_tx,
        &thi_tx,
        &act_tx,
        &msg_tx,
        &mut sid_tx,
        &mut None,
        &AtomicBool::new(false),
    ));
    drop(msg_tx);
    let msgs: Vec<Message> = msg_rx.try_iter().collect();
    assert!(msgs.is_empty());
}

#[test]
fn process_json_event_user_without_content_array_is_noop() {
    let (tok_tx, _tok_rx, thi_tx, _thi_rx, act_tx, _act_rx, msg_tx, msg_rx) = make_channels();
    let mut sid_tx = None::<tokio::sync::oneshot::Sender<String>>;
    let json = json!({"type": "user", "message": {"content": null}});
    assert!(!process_json_event(
        &json,
        &tok_tx,
        &thi_tx,
        &act_tx,
        &msg_tx,
        &mut sid_tx,
        &mut None,
        &AtomicBool::new(false),
    ));
    drop(msg_tx);
    let msgs: Vec<Message> = msg_rx.try_iter().collect();
    assert!(msgs.is_empty());
}

#[test]
fn process_json_event_detects_authentication_failed() {
    let (tok_tx, _tok_rx, thi_tx, _thi_rx, act_tx, _act_rx, msg_tx, _msg_rx) = make_channels();
    let mut sid_tx = None::<tokio::sync::oneshot::Sender<String>>;
    let auth_failed = AtomicBool::new(false);
    let json = json!({
        "type": "assistant",
        "error": "authentication_failed",
        "message": {
            "content": [{"type": "text", "text": "Failed to authenticate."}]
        }
    });
    assert!(!process_json_event(
        &json,
        &tok_tx,
        &thi_tx,
        &act_tx,
        &msg_tx,
        &mut sid_tx,
        &mut None,
        &auth_failed,
    ));
    assert!(auth_failed.load(Ordering::Relaxed));
}

#[test]
fn process_json_event_no_auth_failed_for_normal_events() {
    let (tok_tx, _tok_rx, thi_tx, _thi_rx, act_tx, _act_rx, msg_tx, _msg_rx) = make_channels();
    let mut sid_tx = None::<tokio::sync::oneshot::Sender<String>>;
    let auth_failed = AtomicBool::new(false);
    let json = json!({
        "type": "assistant",
        "message": {
            "content": [{"type": "text", "text": "Hello!"}]
        }
    });
    assert!(!process_json_event(
        &json,
        &tok_tx,
        &thi_tx,
        &act_tx,
        &msg_tx,
        &mut sid_tx,
        &mut None,
        &auth_failed,
    ));
    assert!(!auth_failed.load(Ordering::Relaxed));
}
