package main

import (
	"context"
	"encoding/json"
	"strings"
	"testing"
	"time"

	"github.com/sashabaranov/go-openai"
)

// TestAgent_Initialize tests agent initialization
func TestAgent_Initialize(t *testing.T) {
	mockMCP := NewMockMCPClient([]Tool{
		{
			Name:        "introspect",
			Description: "Discover the GraphQL schema",
			InputSchema: InputSchema{
				Type:                 "object",
				Properties:           map[string]Property{},
				AdditionalProperties: false,
			},
		},
		{
			Name:        "query",
			Description: "Execute a GraphQL query",
			InputSchema: InputSchema{
				Type: "object",
				Properties: map[string]Property{
					"query": {Type: "string", Description: "The GraphQL query"},
				},
				Required:             []string{"query"},
				AdditionalProperties: false,
			},
		},
	})

	mockLLM := NewMockLLM(nil)
	agent := NewTestAgent(mockLLM, mockMCP)

	err := agent.Initialize()
	if err != nil {
		t.Fatalf("Initialize failed: %v", err)
	}

	if len(agent.tools) != 2 {
		t.Errorf("Expected 2 tools, got %d", len(agent.tools))
	}

	// Verify tools were converted correctly
	toolNames := make([]string, len(agent.tools))
	for i, tool := range agent.tools {
		toolNames[i] = tool.Function.Name
	}

	expectedNames := []string{"introspect", "query"}
	for i, expected := range expectedNames {
		if toolNames[i] != expected {
			t.Errorf("Tool %d: expected name %s, got %s", i, expected, toolNames[i])
		}
	}
}

// TestAgent_ProcessEvent tests event processing
func TestAgent_ProcessEvent(t *testing.T) {
	ctx := context.Background()

	t.Run("TaskCreatedEvent", func(t *testing.T) {
		mockMCP := NewMockMCPClient([]Tool{
			{Name: "query", Description: "Execute a GraphQL query", InputSchema: InputSchema{Type: "object"}},
		})
		mockMCP.SetToolResult("query", &CallToolResult{
			Content: []ContentBlock{{Type: "text", Text: `{"data": {"tasks": []}}`}},
		})

		// Mock LLM that makes a tool call then responds
		mockLLM := NewMockLLM([]*openai.ChatCompletionMessage{
			{
				Role: openai.ChatMessageRoleAssistant,
				ToolCalls: []openai.ToolCall{
					{
						ID: "call-1",
						Function: openai.FunctionCall{
							Name:      "query",
							Arguments: `{"query": "{ tasks { id title } }"}`,
						},
					},
				},
			},
			{
				Role:    openai.ChatMessageRoleAssistant,
				Content: "I've processed the task created event.",
			},
		})

		agent := NewTestAgent(mockLLM, mockMCP)
		agent.Initialize()

		eventData := json.RawMessage(`{"taskId": "task-123", "title": "New Task"}`)
		err := agent.ProcessEvent(ctx, "graphql://subscription/taskCreated", eventData)
		if err != nil {
			t.Errorf("ProcessEvent failed: %v", err)
		}
	})

	t.Run("MessageAddedEvent", func(t *testing.T) {
		mockMCP := NewMockMCPClient([]Tool{
			{Name: "query", Description: "Execute a GraphQL query", InputSchema: InputSchema{Type: "object"}},
		})

		// Mock LLM that responds directly without tool calls
		mockLLM := NewMockLLM([]*openai.ChatCompletionMessage{
			{
				Role:    openai.ChatMessageRoleAssistant,
				Content: "I received the message added event.",
			},
		})

		agent := NewTestAgent(mockLLM, mockMCP)
		agent.Initialize()

		eventData := json.RawMessage(`{"messageId": "msg-456", "content": "Hello!"}`)
		err := agent.ProcessEvent(ctx, "graphql://subscription/messageAdded", eventData)
		if err != nil {
			t.Errorf("ProcessEvent failed: %v", err)
		}
	})
}

// TestAgent_Run tests the interactive Run method
func TestAgent_Run(t *testing.T) {
	ctx := context.Background()

	t.Run("SimpleResponse", func(t *testing.T) {
		mockMCP := NewMockMCPClient([]Tool{})
		mockLLM := NewMockLLM([]*openai.ChatCompletionMessage{
			{
				Role:    openai.ChatMessageRoleAssistant,
				Content: "Hello! How can I help you?",
			},
		})

		agent := NewTestAgent(mockLLM, mockMCP)
		agent.Initialize()

		response, err := agent.Run(ctx, "Hello")
		if err != nil {
			t.Fatalf("Run failed: %v", err)
		}

		if response != "Hello! How can I help you?" {
			t.Errorf("Expected 'Hello! How can I help you?', got '%s'", response)
		}
	})

	t.Run("WithToolCall", func(t *testing.T) {
		mockMCP := NewMockMCPClient([]Tool{
			{Name: "introspect", Description: "Introspect schema", InputSchema: InputSchema{Type: "object"}},
		})
		mockMCP.SetToolResult("introspect", &CallToolResult{
			Content: []ContentBlock{{Type: "text", Text: "Schema: Query, Mutation, Subscription"}},
		})

		mockLLM := NewMockLLM([]*openai.ChatCompletionMessage{
			{
				Role: openai.ChatMessageRoleAssistant,
				ToolCalls: []openai.ToolCall{
					{
						ID: "call-1",
						Function: openai.FunctionCall{
							Name:      "introspect",
							Arguments: `{}`,
						},
					},
				},
			},
			{
				Role:    openai.ChatMessageRoleAssistant,
				Content: "The schema has Query, Mutation, and Subscription types.",
			},
		})

		agent := NewTestAgent(mockLLM, mockMCP)
		agent.Initialize()

		response, err := agent.Run(ctx, "What types are in the schema?")
		if err != nil {
			t.Fatalf("Run failed: %v", err)
		}

		if response != "The schema has Query, Mutation, and Subscription types." {
			t.Errorf("Unexpected response: %s", response)
		}
	})
}

// TestAgent_BuildEventPrompt tests event prompt building
func TestAgent_BuildEventPrompt(t *testing.T) {
	tests := []struct {
		name      string
		uri       string
		eventData json.RawMessage
		wantType  string
	}{
		{
			name:      "TaskCreated",
			uri:       "graphql://subscription/taskCreated",
			eventData: json.RawMessage(`{"id": "1"}`),
			wantType:  "task created",
		},
		{
			name:      "TaskUpdated",
			uri:       "graphql://subscription/taskUpdated",
			eventData: json.RawMessage(`{"id": "2"}`),
			wantType:  "task updated",
		},
		{
			name:      "TaskDeleted",
			uri:       "graphql://subscription/taskDeleted",
			eventData: json.RawMessage(`{"id": "3"}`),
			wantType:  "task deleted",
		},
		{
			name:      "MessageAdded",
			uri:       "graphql://subscription/messageAdded",
			eventData: json.RawMessage(`{"id": "4"}`),
			wantType:  "message added",
		},
		{
			name:      "UnknownEvent",
			uri:       "graphql://subscription/unknown",
			eventData: json.RawMessage(`{}`),
			wantType:  "unknown",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			prompt := buildTestEventPrompt(tt.uri, tt.eventData)
			if !strings.Contains(prompt, tt.wantType) {
				t.Errorf("Expected prompt to contain '%s', got: %s", tt.wantType, prompt)
			}
			if !strings.Contains(prompt, tt.uri) {
				t.Errorf("Expected prompt to contain URI '%s', got: %s", tt.uri, prompt)
			}
		})
	}
}

// TestAgent_ToolNames tests the toolNames helper function
func TestAgent_ToolNames(t *testing.T) {
	tools := []Tool{
		{Name: "introspect"},
		{Name: "query"},
		{Name: "mutate"},
	}

	names := toolNames(tools)

	expected := []string{"introspect", "query", "mutate"}
	if len(names) != len(expected) {
		t.Errorf("Expected %d names, got %d", len(expected), len(names))
	}

	for i, name := range names {
		if name != expected[i] {
			t.Errorf("Name %d: expected %s, got %s", i, expected[i], name)
		}
	}
}

// TestEventQueue tests the EventQueue operations
func TestEventQueue(t *testing.T) {
	t.Run("TryEnqueueSuccess", func(t *testing.T) {
		queue := NewEventQueue("test", 10)
		event := &QueuedEvent{
			URI:       "test://uri",
			Data:      json.RawMessage(`{"test": "data"}`),
			Timestamp: time.Now(),
		}

		success := queue.TryEnqueue(event)
		if !success {
			t.Error("Expected TryEnqueue to succeed")
		}
		if queue.Len() != 1 {
			t.Errorf("Expected queue length 1, got %d", queue.Len())
		}
	})

	t.Run("TryEnqueueFullQueue", func(t *testing.T) {
		queue := NewEventQueue("test", 2)

		// Fill the queue
		for i := 0; i < 2; i++ {
			success := queue.TryEnqueue(&QueuedEvent{URI: "test://uri"})
			if !success {
				t.Errorf("Expected TryEnqueue %d to succeed", i)
			}
		}

		// Try to add one more - should fail
		success := queue.TryEnqueue(&QueuedEvent{URI: "test://overflow"})
		if success {
			t.Error("Expected TryEnqueue to fail on full queue")
		}
		if queue.Len() != 2 {
			t.Errorf("Expected queue length 2, got %d", queue.Len())
		}
	})

	t.Run("Dequeue", func(t *testing.T) {
		queue := NewEventQueue("test", 10)
		event1 := &QueuedEvent{URI: "test://uri1"}
		event2 := &QueuedEvent{URI: "test://uri2"}

		queue.TryEnqueue(event1)
		queue.TryEnqueue(event2)

		// Dequeue should return events in FIFO order
		dequeued1 := queue.Dequeue()
		if dequeued1.URI != "test://uri1" {
			t.Errorf("Expected URI 'test://uri1', got '%s'", dequeued1.URI)
		}

		dequeued2 := queue.Dequeue()
		if dequeued2.URI != "test://uri2" {
			t.Errorf("Expected URI 'test://uri2', got '%s'", dequeued2.URI)
		}
	})

	t.Run("Len", func(t *testing.T) {
		queue := NewEventQueue("test", 10)

		if queue.Len() != 0 {
			t.Errorf("Expected empty queue to have length 0, got %d", queue.Len())
		}

		queue.TryEnqueue(&QueuedEvent{URI: "test://uri1"})
		if queue.Len() != 1 {
			t.Errorf("Expected queue length 1, got %d", queue.Len())
		}

		queue.TryEnqueue(&QueuedEvent{URI: "test://uri2"})
		if queue.Len() != 2 {
			t.Errorf("Expected queue length 2, got %d", queue.Len())
		}
	})
}

// TestAgent_QueueEvent tests event routing to queues
func TestAgent_QueueEvent(t *testing.T) {
	mockMCP := NewMockMCPClient([]Tool{})
	mockLLM := NewMockLLM(nil)
	agent := NewTestAgent(mockLLM, mockMCP)
	agent.SetupQueues(10)

	tests := []struct {
		name         string
		uri          string
		expectedTask int
		expectedMsg  int
	}{
		{
			name:         "TaskCreated",
			uri:          "graphql://subscription/taskCreated",
			expectedTask: 1,
			expectedMsg:  0,
		},
		{
			name:         "TaskUpdated",
			uri:          "graphql://subscription/taskUpdated",
			expectedTask: 1,
			expectedMsg:  0,
		},
		{
			name:         "TaskDeleted",
			uri:          "graphql://subscription/taskDeleted",
			expectedTask: 1,
			expectedMsg:  0,
		},
		{
			name:         "MessageAdded",
			uri:          "graphql://subscription/messageAdded",
			expectedTask: 0,
			expectedMsg:  1,
		},
		{
			name:         "UnknownEvent",
			uri:          "graphql://subscription/unknown",
			expectedTask: 1, // Unknown events go to task queue
			expectedMsg:  0,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// Reset queues
			agent.SetupQueues(10)

			agent.QueueEvent(tt.uri, json.RawMessage(`{}`))

			stats := agent.GetQueueStats()
			if stats.TaskQueueSize != tt.expectedTask {
				t.Errorf("Expected task queue size %d, got %d", tt.expectedTask, stats.TaskQueueSize)
			}
			if stats.MessageQueueSize != tt.expectedMsg {
				t.Errorf("Expected message queue size %d, got %d", tt.expectedMsg, stats.MessageQueueSize)
			}
		})
	}
}

// TestAgent_QueueEventFullQueue tests that events are dropped when queue is full
func TestAgent_QueueEventFullQueue(t *testing.T) {
	mockMCP := NewMockMCPClient([]Tool{})
	mockLLM := NewMockLLM(nil)
	agent := NewTestAgent(mockLLM, mockMCP)
	agent.SetupQueues(2) // Small queue for testing

	// Fill the task queue
	agent.QueueEvent("graphql://subscription/taskCreated", json.RawMessage(`{"id": "1"}`))
	agent.QueueEvent("graphql://subscription/taskCreated", json.RawMessage(`{"id": "2"}`))

	// This should be dropped
	agent.QueueEvent("graphql://subscription/taskCreated", json.RawMessage(`{"id": "3"}`))

	stats := agent.GetQueueStats()
	if stats.TaskQueueSize != 2 {
		t.Errorf("Expected task queue size 2 (full), got %d", stats.TaskQueueSize)
	}
}

// TestAgent_StartStop tests the queue processor lifecycle
func TestAgent_StartStop(t *testing.T) {
	mockMCP := NewMockMCPClient([]Tool{
		{Name: "query", Description: "Execute a GraphQL query", InputSchema: InputSchema{Type: "object"}},
	})
	mockMCP.SetToolResult("query", &CallToolResult{
		Content: []ContentBlock{{Type: "text", Text: `{"data": {}}`}},
	})

	// Mock LLM that responds immediately
	mockLLM := NewMockLLM([]*openai.ChatCompletionMessage{
		{
			Role:    openai.ChatMessageRoleAssistant,
			Content: "Processed",
		},
	})

	agent := NewTestAgent(mockLLM, mockMCP)
	agent.Initialize()
	agent.SetupQueues(10)

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	// Start the queue processor
	agent.Start(ctx)

	// Queue an event
	agent.QueueEvent("graphql://subscription/taskCreated", json.RawMessage(`{"id": "1"}`))

	// Give it time to process
	time.Sleep(100 * time.Millisecond)

	// Stop the processor
	agent.Stop()

	// Verify the queue is empty (event was processed)
	stats := agent.GetQueueStats()
	if stats.TaskQueueSize != 0 {
		t.Errorf("Expected task queue to be empty after processing, got %d", stats.TaskQueueSize)
	}
}

// TestAgent_MultipleEventsInOrder tests that events are processed in arrival order
func TestAgent_MultipleEventsInOrder(t *testing.T) {
	mockMCP := NewMockMCPClient([]Tool{
		{Name: "query", Description: "Execute a GraphQL query", InputSchema: InputSchema{Type: "object"}},
	})
	mockMCP.SetToolResult("query", &CallToolResult{
		Content: []ContentBlock{{Type: "text", Text: `{"data": {}}`}},
	})

	// Track the order of processed events
	var processedOrder []string
	// Mock LLM that responds immediately and tracks order
	mockLLM := NewMockLLM([]*openai.ChatCompletionMessage{
		{
			Role:    openai.ChatMessageRoleAssistant,
			Content: "Processed",
		},
		{
			Role:    openai.ChatMessageRoleAssistant,
			Content: "Processed",
		},
		{
			Role:    openai.ChatMessageRoleAssistant,
			Content: "Processed",
		},
	})

	agent := NewTestAgent(mockLLM, mockMCP)
	agent.Initialize()
	agent.SetupQueues(10)

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	// Start the queue processor
	agent.Start(ctx)

	// Queue multiple events in order: task, message, task
	agent.QueueEvent("graphql://subscription/taskCreated", json.RawMessage(`{"id": "task-1"}`))
	agent.QueueEvent("graphql://subscription/messageAdded", json.RawMessage(`{"id": "msg-1"}`))
	agent.QueueEvent("graphql://subscription/taskUpdated", json.RawMessage(`{"id": "task-2"}`))

	// Give it time to process all events
	time.Sleep(200 * time.Millisecond)

	// Stop the processor
	agent.Stop()

	// Verify all queues are empty
	stats := agent.GetQueueStats()
	if stats.TaskQueueSize != 0 {
		t.Errorf("Expected task queue to be empty, got %d", stats.TaskQueueSize)
	}
	if stats.MessageQueueSize != 0 {
		t.Errorf("Expected message queue to be empty, got %d", stats.MessageQueueSize)
	}

	// Verify order was preserved (we can't easily check this with the mock, but the test validates the mechanism)
	_ = processedOrder
}
