use dirigent_protocol::types::{ContentBlock, SessionUpdate, ToolCallStatus};

/// Test that all fixture JSONs parse correctly
#[test]
fn test_all_fixtures_parse() {
    let fixture = include_str!("fixtures/session_updates.json");
    let updates: Vec<SessionUpdate> =
        serde_json::from_str(fixture).expect("Failed to parse session_updates.json fixture");

    // We should have exactly 17 update examples
    assert_eq!(
        updates.len(),
        17,
        "Expected 17 session update examples in fixture"
    );
}

/// Test UserMessageChunk variants
#[test]
fn test_user_message_chunk_fixtures() {
    let fixture = include_str!("fixtures/session_updates.json");
    let updates: Vec<SessionUpdate> = serde_json::from_str(fixture).unwrap();

    // Find all UserMessageChunk variants
    let user_chunks: Vec<_> = updates
        .iter()
        .filter(|u| matches!(u, SessionUpdate::UserMessageChunk { .. }))
        .collect();

    assert_eq!(
        user_chunks.len(),
        3,
        "Expected 3 UserMessageChunk examples"
    );

    // Test first one without meta
    match &user_chunks[0] {
        SessionUpdate::UserMessageChunk {
            message_id,
            content,
            _meta,
        } => {
            assert_eq!(message_id, "msg_user_001");
            assert!(matches!(content, ContentBlock::Text { .. }));
            assert!(_meta.is_none(), "First example should not have _meta");
        }
        _ => panic!("Expected UserMessageChunk"),
    }

    // Test second one with meta
    match &user_chunks[1] {
        SessionUpdate::UserMessageChunk {
            message_id,
            content,
            _meta,
        } => {
            assert_eq!(message_id, "msg_user_002");
            assert!(matches!(content, ContentBlock::Text { .. }));
            assert!(_meta.is_some(), "Second example should have _meta");
            let meta = _meta.as_ref().unwrap();
            assert!(meta.extra.contains_key("timestamp"));
        }
        _ => panic!("Expected UserMessageChunk"),
    }

    // Test third one with ResourceLink
    match &user_chunks[2] {
        SessionUpdate::UserMessageChunk {
            message_id,
            content,
            _meta,
        } => {
            assert_eq!(message_id, "msg_user_003");
            assert!(
                matches!(content, ContentBlock::ResourceLink { .. }),
                "Third example should have ResourceLink content"
            );
            assert!(_meta.is_none());
        }
        _ => panic!("Expected UserMessageChunk"),
    }
}

/// Test AgentMessageChunk variants
#[test]
fn test_agent_message_chunk_fixtures() {
    let fixture = include_str!("fixtures/session_updates.json");
    let updates: Vec<SessionUpdate> = serde_json::from_str(fixture).unwrap();

    let agent_chunks: Vec<_> = updates
        .iter()
        .filter(|u| matches!(u, SessionUpdate::AgentMessageChunk { .. }))
        .collect();

    assert_eq!(
        agent_chunks.len(),
        3,
        "Expected 3 AgentMessageChunk examples"
    );

    // Test first one with Text content
    match &agent_chunks[0] {
        SessionUpdate::AgentMessageChunk {
            message_id,
            content,
            _meta,
        } => {
            assert_eq!(message_id, "msg_agent_001");
            assert!(matches!(content, ContentBlock::Text { .. }));
            assert!(_meta.is_none());
        }
        _ => panic!("Expected AgentMessageChunk"),
    }

    // Test second one with ResourceLink and provider meta
    match &agent_chunks[1] {
        SessionUpdate::AgentMessageChunk {
            message_id,
            content,
            _meta,
        } => {
            assert_eq!(message_id, "msg_agent_002");
            assert!(matches!(content, ContentBlock::ResourceLink { .. }));
            assert!(_meta.is_some());
            let meta = _meta.as_ref().unwrap();
            assert!(meta.provider.is_some());
            let provider = meta.provider.as_ref().unwrap();
            assert_eq!(provider.name, "opencode");
        }
        _ => panic!("Expected AgentMessageChunk"),
    }

    // Test third one with code block text
    match &agent_chunks[2] {
        SessionUpdate::AgentMessageChunk {
            message_id,
            content,
            _meta,
        } => {
            assert_eq!(message_id, "msg_agent_011");
            if let ContentBlock::Text { text } = content {
                assert!(text.contains("```rust"), "Should contain code block");
            }
            assert!(_meta.is_none());
        }
        _ => panic!("Expected AgentMessageChunk"),
    }
}

/// Test AgentThoughtChunk variants
#[test]
fn test_agent_thought_chunk_fixtures() {
    let fixture = include_str!("fixtures/session_updates.json");
    let updates: Vec<SessionUpdate> = serde_json::from_str(fixture).unwrap();

    let thought_chunks: Vec<_> = updates
        .iter()
        .filter(|u| matches!(u, SessionUpdate::AgentThoughtChunk { .. }))
        .collect();

    assert_eq!(
        thought_chunks.len(),
        3,
        "Expected 3 AgentThoughtChunk examples"
    );

    // Test first one without meta
    match &thought_chunks[0] {
        SessionUpdate::AgentThoughtChunk {
            message_id,
            content,
            _meta,
        } => {
            assert_eq!(message_id, "msg_agent_003");
            assert!(matches!(content, ContentBlock::Text { .. }));
            assert!(_meta.is_none());
        }
        _ => panic!("Expected AgentThoughtChunk"),
    }

    // Test second one with complex meta
    match &thought_chunks[1] {
        SessionUpdate::AgentThoughtChunk {
            message_id,
            content,
            _meta,
        } => {
            assert_eq!(message_id, "msg_agent_004");
            assert!(matches!(content, ContentBlock::Text { .. }));
            assert!(_meta.is_some());
            let meta = _meta.as_ref().unwrap();
            assert!(meta.provider.is_some());
            assert!(meta.extra.contains_key("duration_ms"));
        }
        _ => panic!("Expected AgentThoughtChunk"),
    }

    // Test third one with ResourceLink content
    match &thought_chunks[2] {
        SessionUpdate::AgentThoughtChunk {
            message_id,
            content,
            _meta,
        } => {
            assert_eq!(message_id, "msg_agent_012");
            assert!(matches!(content, ContentBlock::ResourceLink { .. }));
        }
        _ => panic!("Expected AgentThoughtChunk"),
    }
}

/// Test ToolCall variants covering all status types
#[test]
fn test_tool_call_fixtures() {
    let fixture = include_str!("fixtures/session_updates.json");
    let updates: Vec<SessionUpdate> = serde_json::from_str(fixture).unwrap();

    let tool_calls: Vec<_> = updates
        .iter()
        .filter(|u| matches!(u, SessionUpdate::ToolCall { .. }))
        .collect();

    assert_eq!(tool_calls.len(), 5, "Expected 5 ToolCall examples");

    // Test Pending status
    match &tool_calls[0] {
        SessionUpdate::ToolCall {
            message_id,
            tool_call,
            _meta,
        } => {
            assert_eq!(message_id, "msg_agent_005");
            assert_eq!(tool_call.id, "call_001");
            assert_eq!(tool_call.tool_name, "bash");
            assert_eq!(tool_call.status, ToolCallStatus::Pending);
            assert!(tool_call.content.is_empty());
            assert!(tool_call.raw_input.is_some());
            assert!(tool_call.title.is_some());
            assert!(_meta.is_none());
        }
        _ => panic!("Expected ToolCall"),
    }

    // Test Running status
    match &tool_calls[1] {
        SessionUpdate::ToolCall {
            message_id,
            tool_call,
            _meta,
        } => {
            assert_eq!(message_id, "msg_agent_006");
            assert_eq!(tool_call.id, "call_002");
            assert_eq!(tool_call.status, ToolCallStatus::Running);
            assert!(!tool_call.content.is_empty(), "Running should have content");
            assert!(_meta.is_some(), "Should have meta with started_at");
        }
        _ => panic!("Expected ToolCall"),
    }

    // Test Completed status
    match &tool_calls[2] {
        SessionUpdate::ToolCall {
            message_id,
            tool_call,
            _meta,
        } => {
            assert_eq!(message_id, "msg_agent_007");
            assert_eq!(tool_call.id, "call_003");
            assert_eq!(tool_call.status, ToolCallStatus::Completed);
            assert!(tool_call.raw_output.is_some());
            assert!(tool_call.error.is_none());
        }
        _ => panic!("Expected ToolCall"),
    }

    // Test Error status
    match &tool_calls[3] {
        SessionUpdate::ToolCall {
            message_id,
            tool_call,
            _meta,
        } => {
            assert_eq!(message_id, "msg_agent_008");
            assert_eq!(tool_call.id, "call_004");
            assert_eq!(tool_call.status, ToolCallStatus::Error);
            assert!(
                tool_call.error.is_some(),
                "Error status should have error message"
            );
            let error = tool_call.error.as_ref().unwrap();
            assert!(error.contains("Permission denied"));
            assert!(_meta.is_some());
        }
        _ => panic!("Expected ToolCall"),
    }

    // Test Completed with complex metadata
    match &tool_calls[4] {
        SessionUpdate::ToolCall {
            message_id,
            tool_call,
            _meta,
        } => {
            assert_eq!(message_id, "msg_agent_013");
            assert_eq!(tool_call.status, ToolCallStatus::Completed);
            assert!(tool_call.metadata.is_some());
            assert!(_meta.is_some());
            let meta = _meta.as_ref().unwrap();
            assert!(meta.provider.is_some());
            let provider = meta.provider.as_ref().unwrap();
            assert!(provider.original_ids.is_some());
        }
        _ => panic!("Expected ToolCall"),
    }
}

/// Test ToolCallUpdate variants
#[test]
fn test_tool_call_update_fixtures() {
    let fixture = include_str!("fixtures/session_updates.json");
    let updates: Vec<SessionUpdate> = serde_json::from_str(fixture).unwrap();

    let tool_call_updates: Vec<_> = updates
        .iter()
        .filter(|u| matches!(u, SessionUpdate::ToolCallUpdate { .. }))
        .collect();

    assert_eq!(
        tool_call_updates.len(),
        3,
        "Expected 3 ToolCallUpdate examples"
    );

    // Test completed update
    match &tool_call_updates[0] {
        SessionUpdate::ToolCallUpdate {
            message_id,
            tool_call_id,
            tool_call,
            _meta,
        } => {
            assert_eq!(message_id, "msg_agent_009");
            assert_eq!(tool_call_id, "call_002");
            assert_eq!(tool_call.id, "call_002");
            assert_eq!(tool_call.status, ToolCallStatus::Completed);
            assert!(tool_call.raw_output.is_some());
            assert!(_meta.is_none());
        }
        _ => panic!("Expected ToolCallUpdate"),
    }

    // Test running update with metadata
    match &tool_call_updates[1] {
        SessionUpdate::ToolCallUpdate {
            message_id,
            tool_call_id,
            tool_call,
            _meta,
        } => {
            assert_eq!(message_id, "msg_agent_010");
            assert_eq!(tool_call_id, "call_005");
            assert_eq!(tool_call.status, ToolCallStatus::Running);
            assert_eq!(tool_call.content.len(), 2, "Should have 2 content blocks");
            assert!(tool_call.metadata.is_some());
            assert!(_meta.is_some());
        }
        _ => panic!("Expected ToolCallUpdate"),
    }

    // Test error update
    match &tool_call_updates[2] {
        SessionUpdate::ToolCallUpdate {
            message_id,
            tool_call_id,
            tool_call,
            _meta,
        } => {
            assert_eq!(message_id, "msg_agent_014");
            assert_eq!(tool_call_id, "call_007");
            assert_eq!(tool_call.status, ToolCallStatus::Error);
            assert!(tool_call.error.is_some());
            assert!(_meta.is_some());
        }
        _ => panic!("Expected ToolCallUpdate"),
    }
}

/// Test roundtrip serialization for all fixture examples
#[test]
fn test_all_fixtures_roundtrip() {
    let fixture = include_str!("fixtures/session_updates.json");
    let updates: Vec<SessionUpdate> = serde_json::from_str(fixture).unwrap();

    for (idx, original) in updates.iter().enumerate() {
        let json = serde_json::to_string(&original)
            .unwrap_or_else(|e| panic!("Failed to serialize update {}: {}", idx, e));

        let deserialized: SessionUpdate = serde_json::from_str(&json)
            .unwrap_or_else(|e| panic!("Failed to deserialize update {}: {}", idx, e));

        assert_eq!(
            original, &deserialized,
            "Roundtrip failed for update {}",
            idx
        );
    }
}

/// Test that each variant has correct type tag
#[test]
fn test_type_tags() {
    let fixture = include_str!("fixtures/session_updates.json");
    let updates: Vec<serde_json::Value> = serde_json::from_str(fixture).unwrap();

    let expected_types = [
        "user_message_chunk",    // 0
        "user_message_chunk",    // 1
        "agent_message_chunk",   // 2
        "agent_message_chunk",   // 3
        "agent_thought_chunk",   // 4
        "agent_thought_chunk",   // 5
        "tool_call",             // 6
        "tool_call",             // 7
        "tool_call",             // 8
        "tool_call",             // 9
        "tool_call_update",      // 10
        "tool_call_update",      // 11
        "user_message_chunk",    // 12
        "agent_message_chunk",   // 13
        "agent_thought_chunk",   // 14
        "tool_call",             // 15
        "tool_call_update",      // 16
    ];

    for (idx, (update, expected_type)) in updates.iter().zip(expected_types.iter()).enumerate() {
        let type_field = update
            .get("type")
            .and_then(|v| v.as_str())
            .unwrap_or_else(|| panic!("Update {} missing type field", idx));

        assert_eq!(
            type_field, *expected_type,
            "Update {} has wrong type tag",
            idx
        );
    }
}

/// Test that optional fields work correctly
#[test]
fn test_optional_fields() {
    let fixture = include_str!("fixtures/session_updates.json");
    let updates: Vec<SessionUpdate> = serde_json::from_str(fixture).unwrap();

    // Count updates with and without _meta
    let with_meta = updates.iter().filter(|u| match u {
        SessionUpdate::UserMessageChunk { _meta, .. }
        | SessionUpdate::AgentMessageChunk { _meta, .. }
        | SessionUpdate::AgentThoughtChunk { _meta, .. }
        | SessionUpdate::ToolCall { _meta, .. }
        | SessionUpdate::ToolCallUpdate { _meta, .. } => _meta.is_some(),
        SessionUpdate::Unknown { .. } => false,
    });

    let without_meta = updates.iter().filter(|u| match u {
        SessionUpdate::UserMessageChunk { _meta, .. }
        | SessionUpdate::AgentMessageChunk { _meta, .. }
        | SessionUpdate::AgentThoughtChunk { _meta, .. }
        | SessionUpdate::ToolCall { _meta, .. }
        | SessionUpdate::ToolCallUpdate { _meta, .. } => _meta.is_none(),
        SessionUpdate::Unknown { .. } => false,
    });

    assert!(
        with_meta.count() > 0,
        "Should have examples with _meta field"
    );
    assert!(
        without_meta.count() > 0,
        "Should have examples without _meta field"
    );
}

/// Test content block variations
#[test]
fn test_content_block_variations() {
    let fixture = include_str!("fixtures/session_updates.json");
    let updates: Vec<SessionUpdate> = serde_json::from_str(fixture).unwrap();

    let mut text_count = 0;
    let mut resource_link_count = 0;

    for update in updates.iter() {
        let content = match update {
            SessionUpdate::UserMessageChunk { content, .. } => Some(content),
            SessionUpdate::AgentMessageChunk { content, .. } => Some(content),
            SessionUpdate::AgentThoughtChunk { content, .. } => Some(content),
            _ => None,
        };

        if let Some(content) = content {
            match content {
                ContentBlock::Text { .. } => text_count += 1,
                ContentBlock::ResourceLink { .. } => resource_link_count += 1,
            }
        }
    }

    assert!(
        text_count > 0,
        "Should have Text content blocks: {}",
        text_count
    );
    assert!(
        resource_link_count > 0,
        "Should have ResourceLink content blocks: {}",
        resource_link_count
    );
}

/// Test tool call status distribution
#[test]
fn test_tool_call_status_coverage() {
    let fixture = include_str!("fixtures/session_updates.json");
    let updates: Vec<SessionUpdate> = serde_json::from_str(fixture).unwrap();

    let mut pending_count = 0;
    let mut running_count = 0;
    let mut completed_count = 0;
    let mut error_count = 0;

    for update in updates.iter() {
        let tool_call = match update {
            SessionUpdate::ToolCall { tool_call, .. } => Some(tool_call),
            SessionUpdate::ToolCallUpdate { tool_call, .. } => Some(tool_call),
            _ => None,
        };

        if let Some(tool_call) = tool_call {
            match tool_call.status {
                ToolCallStatus::Pending => pending_count += 1,
                ToolCallStatus::Running => running_count += 1,
                ToolCallStatus::Completed => completed_count += 1,
                ToolCallStatus::Error => error_count += 1,
            }
        }
    }

    assert!(
        pending_count > 0,
        "Should have Pending tool calls: {}",
        pending_count
    );
    assert!(
        running_count > 0,
        "Should have Running tool calls: {}",
        running_count
    );
    assert!(
        completed_count > 0,
        "Should have Completed tool calls: {}",
        completed_count
    );
    assert!(
        error_count > 0,
        "Should have Error tool calls: {}",
        error_count
    );
}

/// Test edge cases: empty content arrays, missing optional fields
#[test]
fn test_edge_cases() {
    // Test tool call with empty content array
    let json = r#"{
        "type": "tool_call",
        "message_id": "msg_test",
        "tool_call": {
            "id": "call_test",
            "tool_name": "test",
            "status": "pending",
            "content": []
        }
    }"#;

    let update: SessionUpdate = serde_json::from_str(json).unwrap();
    match update {
        SessionUpdate::ToolCall { tool_call, .. } => {
            assert!(tool_call.content.is_empty());
            assert!(tool_call.raw_input.is_none());
            assert!(tool_call.raw_output.is_none());
            assert!(tool_call.title.is_none());
            assert!(tool_call.error.is_none());
        }
        _ => panic!("Expected ToolCall"),
    }

    // Test resource link without optional fields
    let json = r#"{
        "type": "user_message_chunk",
        "message_id": "msg_test",
        "content": {
            "type": "resource_link",
            "uri": "file:///test.txt"
        }
    }"#;

    let update: SessionUpdate = serde_json::from_str(json).unwrap();
    match update {
        SessionUpdate::UserMessageChunk { content, .. } => {
            if let ContentBlock::ResourceLink { name, mime_type, .. } = content {
                assert!(name.is_none());
                assert!(mime_type.is_none());
            } else {
                panic!("Expected ResourceLink");
            }
        }
        _ => panic!("Expected UserMessageChunk"),
    }
}
