use picobot::protocol::{HistoryMessage, SessionSummary, WsInbound, WsOutbound};
use picobot::providers::{ChatCompletionRequest, Message, ToolCall};
use picobot::work::{TaskItem, TaskPlan};

/// Test that message with special characters is properly escaped
#[test]
fn test_message_special_characters() {
    let msg = Message::user("Hello \"world\"\nNew line\tTab");

    let json = serde_json::to_string(&msg).unwrap();
    let deserialized: Message = serde_json::from_str(&json).unwrap();

    assert_eq!(deserialized.role, "user");
    assert_eq!(deserialized.content.len(), 1);
    let encoded = serde_json::to_string(&deserialized.content).unwrap();
    assert!(encoded.contains("Hello \\\"world\\\"\\nNew line\\tTab"));
}

/// Test that multi-line system prompt is preserved
#[test]
fn test_multiline_system_prompt() {
    let messages = [
        Message::system(
            "You are a helpful assistant.\n\nFollow these rules:\n1. Be kind\n2. Be accurate",
        ),
        Message::user("Hi"),
    ];

    let json = serde_json::to_string(&messages[0]).unwrap();
    assert!(json.contains("helpful assistant"));
    assert!(json.contains("rules"));
    assert!(json.contains("1. Be kind"));
}

/// Test ChatCompletionRequest serialization (without model field)
#[test]
fn test_chat_request_serialization() {
    let request = ChatCompletionRequest {
        messages: vec![Message::system("You are helpful"), Message::user("Hello")],
        temperature: Some(0.7),
        max_tokens: Some(100),
        tools: None,
    };

    let json = serde_json::to_string(&request).unwrap();

    // Verify structure
    assert!(json.contains(r#""role":"system""#));
    assert!(json.contains(r#""role":"user""#));
    assert!(json.contains("You are helpful"));
    assert!(json.contains("Hello"));
    assert!(json.contains(r#""temperature":0.7"#));
    assert!(json.contains(r#""max_tokens":100"#));
}

#[test]
fn test_session_inbound_serialization() {
    let msg = WsInbound::CreateSession {
        title: Some("demo".to_string()),
    };

    let json = serde_json::to_string(&msg).unwrap();
    assert!(json.contains(r#""type":"create_session""#));
    assert!(json.contains(r#""title":"demo""#));

    let decoded: WsInbound = serde_json::from_str(&json).unwrap();
    match decoded {
        WsInbound::CreateSession { title } => {
            assert_eq!(title.as_deref(), Some("demo"));
        }
        other => panic!("unexpected decoded variant: {:?}", other),
    }
}

#[test]
fn test_session_list_outbound_serialization() {
    let msg = WsOutbound::SessionList {
        sessions: vec![SessionSummary {
            session_id: "session-1".to_string(),
            title: "demo".to_string(),
            channel_name: "cli".to_string(),
            chat_id: "session-1".to_string(),
            message_count: 2,
            last_active_at: 123,
            archived_at: None,
        }],
        current_session_id: Some("session-1".to_string()),
    };

    let json = serde_json::to_string(&msg).unwrap();
    assert!(json.contains(r#""type":"session_list""#));
    assert!(json.contains(r#""session_id":"session-1""#));
    assert!(json.contains(r#""message_count":2"#));

    let decoded: WsOutbound = serde_json::from_str(&json).unwrap();
    match decoded {
        WsOutbound::SessionList {
            sessions,
            current_session_id,
        } => {
            assert_eq!(sessions.len(), 1);
            assert_eq!(sessions[0].title, "demo");
            assert_eq!(current_session_id.as_deref(), Some("session-1"));
        }
        other => panic!("unexpected decoded variant: {:?}", other),
    }
}

#[test]
fn test_clear_history_with_session_id_serialization() {
    let msg = WsInbound::ClearHistory {
        chat_id: None,
        session_id: Some("session-1".to_string()),
    };

    let json = serde_json::to_string(&msg).unwrap();
    assert!(json.contains(r#""type":"clear_history""#));
    assert!(json.contains(r#""session_id":"session-1""#));
}

#[test]
fn test_bounded_session_history_protocol() {
    let inbound = WsInbound::GetSessionHistory {
        session_id: "cli_chat:client:dialog".to_string(),
        limit: Some(1000),
    };
    let json = serde_json::to_string(&inbound).unwrap();
    assert!(json.contains(r#""type":"get_session_history""#));
    assert!(json.contains(r#""limit":1000"#));

    let outbound = WsOutbound::SessionHistory {
        session_id: "cli_chat:client:dialog".to_string(),
        messages: vec![HistoryMessage {
            id: "m1".to_string(),
            seq: 1,
            role: "user".to_string(),
            content: "你好".to_string(),
            reasoning_content: None,
            completion_status: picobot::bus::CompletionStatus::Completed,
            created_at: 123,
            tool_call_id: None,
            tool_name: None,
            tool_calls: None,
            attachments: Vec::new(),
            turn_origin: picobot::bus::TurnOrigin::User,
        }],
    };
    let decoded: WsOutbound =
        serde_json::from_str(&serde_json::to_string(&outbound).unwrap()).unwrap();
    match decoded {
        WsOutbound::SessionHistory { messages, .. } => {
            assert_eq!(messages.len(), 1);
            assert_eq!(messages[0].content, "你好");
        }
        other => panic!("unexpected decoded variant: {other:?}"),
    }
}

#[test]
fn test_user_input_accepts_upload_ids_and_old_payloads() {
    let old: WsInbound =
        serde_json::from_str(r#"{"type":"user_input","content":"hello"}"#).unwrap();
    match old {
        WsInbound::UserInput { upload_ids, .. } => assert!(upload_ids.is_empty()),
        other => panic!("unexpected decoded variant: {other:?}"),
    }

    let message = WsInbound::UserInput {
        content: "处理文件".to_string(),
        upload_ids: vec!["upload-1".to_string()],
        client_message_id: None,
        channel: None,
        chat_id: None,
        sender_id: None,
    };
    let json = serde_json::to_string(&message).unwrap();
    assert!(json.contains(r#""upload_ids":["upload-1"]"#));
}

#[test]
fn test_session_history_preserves_tool_call_metadata() {
    let outbound = WsOutbound::SessionHistory {
        session_id: "cli_chat:client:dialog".to_string(),
        messages: vec![HistoryMessage {
            id: "m-tool".to_string(),
            seq: 2,
            role: "assistant".to_string(),
            content: String::new(),
            reasoning_content: Some("checking".to_string()),
            completion_status: picobot::bus::CompletionStatus::Completed,
            created_at: 124,
            tool_call_id: None,
            tool_name: None,
            tool_calls: Some(vec![ToolCall {
                id: "call-1".to_string(),
                name: "read_file".to_string(),
                arguments: serde_json::json!({ "path": "README.md" }),
            }]),
            attachments: Vec::new(),
            turn_origin: picobot::bus::TurnOrigin::User,
        }],
    };

    let json = serde_json::to_string(&outbound).unwrap();
    assert!(json.contains(r#""tool_calls""#));
    assert!(json.contains(r#""read_file""#));
    assert!(json.contains(r#""reasoning_content":"checking""#));
    let decoded: WsOutbound = serde_json::from_str(&json).unwrap();
    match decoded {
        WsOutbound::SessionHistory { messages, .. } => {
            let calls = messages[0].tool_calls.as_ref().unwrap();
            assert_eq!(calls[0].name, "read_file");
            assert_eq!(calls[0].arguments["path"], "README.md");
        }
        other => panic!("unexpected decoded variant: {other:?}"),
    }
}

#[test]
fn test_session_plan_protocol_is_structured_and_versioned() {
    let inbound = WsInbound::GetSessionPlan {
        session_id: "cli_chat:client:dialog".to_string(),
    };
    assert!(
        serde_json::to_string(&inbound)
            .unwrap()
            .contains(r#""type":"get_session_plan""#)
    );

    let plan = TaskPlan {
        id: "plan-1".to_string(),
        session_id: "cli_chat:client:dialog".to_string(),
        objective: "实现 Todo".to_string(),
        status: "active".to_string(),
        version: 3,
        created_at: 1,
        updated_at: 2,
        closed_at: None,
        items: vec![TaskItem {
            id: "T1".to_string(),
            ordinal: 1,
            title: "实现协议".to_string(),
            status: "in_progress".to_string(),
            executor_kind: Some("sub_agent".to_string()),
            execution_id: Some("run-1".to_string()),
            result_summary: None,
            error: None,
            version: 2,
            updated_at: 2,
        }],
    };
    let outbound = WsOutbound::PlanUpdated {
        session_id: plan.session_id.clone(),
        reason: "item_assigned".to_string(),
        changed_item_ids: vec!["T1".to_string()],
        plan: Some(plan),
    };
    let decoded: WsOutbound =
        serde_json::from_str(&serde_json::to_string(&outbound).unwrap()).unwrap();
    match decoded {
        WsOutbound::PlanUpdated { plan, reason, .. } => {
            let plan = plan.unwrap();
            assert_eq!(plan.version, 3);
            assert_eq!(plan.items[0].execution_id.as_deref(), Some("run-1"));
            assert_eq!(reason, "item_assigned");
        }
        other => panic!("unexpected decoded variant: {other:?}"),
    }
}
