package test

import (
	"bytes"
	"encoding/json"
	"strconv"
	"strings"
	"testing"
	"time"

	"github.com/CalmoAI/mcp-server-signoz/internal/domain"
	"github.com/CalmoAI/mcp-server-signoz/pkg/mcp"
	"github.com/CalmoAI/mcp-server-signoz/pkg/mcp/transport"
)

// TestUtilities provides common testing utilities for MCP server tests
type TestUtilities struct {
	t *testing.T
}

// NewTestUtilities creates a new test utilities instance
func NewTestUtilities(t *testing.T) *TestUtilities {
	return &TestUtilities{t: t}
}

// AssertNoError fails the test if err is not nil
func (tu *TestUtilities) AssertNoError(err error, msgAndArgs ...interface{}) {
	if err != nil {
		if len(msgAndArgs) > 0 {
			tu.t.Errorf("Unexpected error: %v. %v", err, msgAndArgs[0])
		} else {
			tu.t.Errorf("Unexpected error: %v", err)
		}
	}
}

// AssertError fails the test if err is nil
func (tu *TestUtilities) AssertError(err error, msgAndArgs ...interface{}) {
	if err == nil {
		if len(msgAndArgs) > 0 {
			tu.t.Errorf("Expected error but got none. %v", msgAndArgs[0])
		} else {
			tu.t.Error("Expected error but got none")
		}
	}
}

// AssertEqual fails the test if expected != actual
func (tu *TestUtilities) AssertEqual(expected, actual interface{}, msgAndArgs ...interface{}) {
	if expected != actual {
		if len(msgAndArgs) > 0 {
			tu.t.Errorf("Expected %v, got %v. %v", expected, actual, msgAndArgs[0])
		} else {
			tu.t.Errorf("Expected %v, got %v", expected, actual)
		}
	}
}

// AssertTrue fails the test if condition is false
func (tu *TestUtilities) AssertTrue(condition bool, msgAndArgs ...interface{}) {
	if !condition {
		if len(msgAndArgs) > 0 {
			tu.t.Errorf("Expected true but got false. %v", msgAndArgs[0])
		} else {
			tu.t.Error("Expected true but got false")
		}
	}
}

// AssertFalse fails the test if condition is true
func (tu *TestUtilities) AssertFalse(condition bool, msgAndArgs ...interface{}) {
	if condition {
		if len(msgAndArgs) > 0 {
			tu.t.Errorf("Expected false but got true. %v", msgAndArgs[0])
		} else {
			tu.t.Error("Expected false but got true")
		}
	}
}

// AssertContains fails the test if str does not contain substr
func (tu *TestUtilities) AssertContains(str, substr string, msgAndArgs ...interface{}) {
	if !strings.Contains(str, substr) {
		if len(msgAndArgs) > 0 {
			tu.t.Errorf("Expected '%s' to contain '%s'. %v", str, substr, msgAndArgs[0])
		} else {
			tu.t.Errorf("Expected '%s' to contain '%s'", str, substr)
		}
	}
}

// AssertJSONEqual compares two JSON strings for equality
func (tu *TestUtilities) AssertJSONEqual(expected, actual string, msgAndArgs ...interface{}) {
	var expectedData, actualData interface{}

	err := json.Unmarshal([]byte(expected), &expectedData)
	tu.AssertNoError(err, "Failed to parse expected JSON")

	err = json.Unmarshal([]byte(actual), &actualData)
	tu.AssertNoError(err, "Failed to parse actual JSON")

	expectedBytes, _ := json.Marshal(expectedData)
	actualBytes, _ := json.Marshal(actualData)

	tu.AssertEqual(string(expectedBytes), string(actualBytes), msgAndArgs...)
}

// MCPTestClient provides a test client for MCP protocol testing
type MCPTestClient struct {
	transport *transport.StdioTransport
	stdin     *bytes.Buffer
	stdout    *bytes.Buffer
	requestID int64
	tu        *TestUtilities
}

// NewMCPTestClient creates a new MCP test client
func NewMCPTestClient(tu *TestUtilities) *MCPTestClient {
	stdin := &bytes.Buffer{}
	stdout := &bytes.Buffer{}
	transport := transport.NewStdioTransportWithStreams(stdin, stdout)

	return &MCPTestClient{
		transport: transport,
		stdin:     stdin,
		stdout:    stdout,
		requestID: 0,
		tu:        tu,
	}
}

// NextRequestID generates the next request ID
func (client *MCPTestClient) NextRequestID() int64 {
	client.requestID++
	return client.requestID
}

// SendInitialize sends an initialize request
func (client *MCPTestClient) SendInitialize() *mcp.MCPMessage {
	initRequest := mcp.InitializeRequest{
		ProtocolVersion: mcp.MCPVersion,
		Capabilities:    mcp.Capabilities{},
		ClientInfo: mcp.ClientInfo{
			Name:    "test-client",
			Version: "1.0.0",
		},
	}

	return client.SendRequest("initialize", initRequest)
}

// SendToolsList sends a tools/list request
func (client *MCPTestClient) SendToolsList() *mcp.MCPMessage {
	return client.SendRequest("tools/list", nil)
}

// SendToolsCall sends a tools/call request
func (client *MCPTestClient) SendToolsCall(toolName string, args map[string]interface{}) *mcp.MCPMessage {
	request := mcp.ToolsCallRequest{
		Name:      toolName,
		Arguments: args,
	}

	return client.SendRequest("tools/call", request)
}

// SendRequest sends a generic request
func (client *MCPTestClient) SendRequest(method string, params interface{}) *mcp.MCPMessage {
	id := client.NextRequestID()

	var paramsData json.RawMessage
	if params != nil {
		data, err := json.Marshal(params)
		client.tu.AssertNoError(err, "Failed to marshal request params")
		paramsData = data
	}

	message := &mcp.MCPMessage{
		JSONRPC: "2.0",
		ID:      &id,
		Method:  method,
		Params:  paramsData,
	}

	return client.SendMessage(message)
}

// SendMessage sends a message and returns the response
func (client *MCPTestClient) SendMessage(message *mcp.MCPMessage) *mcp.MCPMessage {
	// Write message to stdin
	data, err := json.Marshal(message)
	client.tu.AssertNoError(err, "Failed to marshal message")

	client.stdin.Write(data)
	client.stdin.Write([]byte("\n"))

	// Read response from stdout
	response, err := client.ReceiveMessage()
	client.tu.AssertNoError(err, "Failed to receive response")

	return response
}

// ReceiveMessage receives a message from the transport
func (client *MCPTestClient) ReceiveMessage() (*mcp.MCPMessage, error) {
	return client.transport.Receive()
}

// GetStdout returns the stdout buffer content
func (client *MCPTestClient) GetStdout() string {
	return client.stdout.String()
}

// Reset clears the stdin and stdout buffers
func (client *MCPTestClient) Reset() {
	client.stdin.Reset()
	client.stdout.Reset()
}

// MockDataGenerator provides mock data for testing
type MockDataGenerator struct{}

// NewMockDataGenerator creates a new mock data generator
func NewMockDataGenerator() *MockDataGenerator {
	return &MockDataGenerator{}
}

// GenerateConnectionStatus generates mock connection status
func (m *MockDataGenerator) GenerateConnectionStatus() *domain.ConnectionStatus {
	return &domain.ConnectionStatus{
		Connected:   true,
		URL:         "http://localhost:3301",
		Version:     "1.0.0",
		LastChecked: time.Now(),
		Latency:     time.Millisecond * 50,
	}
}

// GenerateDashboards generates mock dashboards
func (m *MockDataGenerator) GenerateDashboards(count int) []domain.Dashboard {
	dashboards := make([]domain.Dashboard, count)
	for i := 0; i < count; i++ {
		dashboards[i] = domain.Dashboard{
			ID:          "dashboard-" + strconv.Itoa(i+1),
			Name:        "Dashboard " + strconv.Itoa(i+1),
			Description: "Test dashboard number " + strconv.Itoa(i+1),
			Tags:        []string{"test", "dashboard"},
			CreatedAt:   time.Now().Add(-time.Duration(i+1) * time.Hour),
			UpdatedAt:   time.Now(),
		}
	}
	return dashboards
}

// GenerateServices generates mock services
func (m *MockDataGenerator) GenerateServices(count int) []domain.Service {
	services := make([]domain.Service, count)
	serviceNames := []string{"user-service", "order-service", "payment-service", "notification-service", "inventory-service"}

	for i := 0; i < count; i++ {
		services[i] = domain.Service{
			ServiceName: serviceNames[i%len(serviceNames)] + "-" + strconv.Itoa(i+1),
			Environment: "test",
			Tags:        map[string]string{"version": "1." + strconv.Itoa(i) + ".0"},
			Operations:  []string{"GET /api/test", "POST /api/test"},
			CallRate:    100.0 + float64(i),
			ErrorRate:   0.01 * float64(i),
			P99Latency:  250.0 + float64(i)*10,
			AvgLatency:  125.0 + float64(i)*5,
			LastSeen:    time.Now().Add(-time.Duration(i+1) * time.Minute),
		}
	}
	return services
}

// GenerateTraces generates mock traces
func (m *MockDataGenerator) GenerateTraces(count int, serviceName string) []domain.TraceLog {
	traces := make([]domain.TraceLog, count)
	operations := []string{"GET /api/users", "POST /api/orders", "PUT /api/users/{id}", "DELETE /api/orders/{id}"}
	statuses := []string{"success", "error", "timeout"}

	for i := 0; i < count; i++ {
		traces[i] = domain.TraceLog{
			ID:        "trace-" + strconv.Itoa(i),
			Timestamp: time.Now().Add(-time.Duration(i) * time.Minute),
			Service:   serviceName,
			Operation: operations[i%len(operations)],
			Duration:  time.Duration(100+i*10) * time.Millisecond,
			Status:    statuses[i%len(statuses)],
			Message:   "Trace message " + strconv.Itoa(i+1),
			Attributes: map[string]interface{}{
				"user_id":       "user-" + strconv.Itoa(i+1),
				"request_size":  1024 + i*100,
				"response_size": 2048 + i*200,
			},
			SpanID:   "span-" + strconv.Itoa(i),
			TraceID:  "trace-" + strconv.Itoa(i),
			ParentID: "",
		}
	}
	return traces
}

// GenerateQueryResult generates mock query results
func (m *MockDataGenerator) GenerateQueryResult(rowCount int) *domain.QueryResult {
	rows := make([][]interface{}, rowCount)
	baseTime := time.Now().Add(-time.Hour)

	for i := 0; i < rowCount; i++ {
		rows[i] = []interface{}{
			baseTime.Add(time.Duration(i) * time.Minute).Unix(),
			"service-" + strconv.Itoa(i%5+1),
			100.0 + float64(i)*1.5,
			200 + (i%10)*10, // HTTP status codes
		}
	}

	return &domain.QueryResult{
		Columns: []domain.ColumnInfo{
			{Name: "timestamp", Type: "UInt64"},
			{Name: "service_name", Type: "String"},
			{Name: "duration_ms", Type: "Float64"},
			{Name: "status_code", Type: "UInt32"},
		},
		Rows: rows,
		Meta: domain.QueryMeta{
			QueryID:       "mock-query-123",
			ExecutionTime: time.Duration(25+rowCount/10) * time.Millisecond,
			RowsAffected:  int64(rowCount),
			Statistics: domain.QueryStats{
				ReadRows:  int64(rowCount),
				ReadBytes: int64(rowCount * 128),
			},
		},
	}
}

// GenerateAPMMetrics generates mock APM metrics
func (m *MockDataGenerator) GenerateAPMMetrics(serviceName string) *domain.APMMetrics {
	return &domain.APMMetrics{
		ServiceName: serviceName,
		TimeRange: domain.TimeRange{
			Start: time.Now().Add(-time.Hour),
			End:   time.Now(),
		},
		Metrics: map[string]domain.Metric{
			"requests_per_minute": {
				Name:        "requests_per_minute",
				Unit:        "requests/min",
				Description: "Number of requests per minute",
				DataPoints: []domain.DataPoint{
					{Timestamp: time.Now().Add(-time.Hour), Value: 1250.5},
					{Timestamp: time.Now(), Value: 1275.2},
				},
			},
		},
	}
}

// TestRunner provides utilities for running test suites
type TestRunner struct {
	tu *TestUtilities
}

// NewTestRunner creates a new test runner
func NewTestRunner(tu *TestUtilities) *TestRunner {
	return &TestRunner{tu: tu}
}

// RunMCPProtocolTests runs a complete MCP protocol test suite
func (tr *TestRunner) RunMCPProtocolTests(client *MCPTestClient) {
	// Test Initialize
	tr.tu.t.Run("Initialize", func(t *testing.T) {
		response := client.SendInitialize()

		tr.tu.AssertEqual("2.0", response.JSONRPC, "Invalid JSONRPC version")
		tr.tu.AssertTrue(response.ID != nil, "Response should have ID")
		tr.tu.AssertTrue(response.Error == nil, "Initialize should not return error")
		tr.tu.AssertTrue(response.Result != nil, "Initialize should return result")

		var initResponse mcp.InitializeResponse
		err := json.Unmarshal(response.Result, &initResponse)
		tr.tu.AssertNoError(err, "Failed to parse initialize response")
		tr.tu.AssertEqual("signoz-mcp-server", initResponse.ServerInfo.Name, "Invalid server name")
	})

	// Test Tools List
	tr.tu.t.Run("ToolsList", func(t *testing.T) {
		response := client.SendToolsList()

		tr.tu.AssertTrue(response.Error == nil, "Tools list should not return error")
		tr.tu.AssertTrue(response.Result != nil, "Tools list should return result")

		var toolsResponse mcp.ToolsListResponse
		err := json.Unmarshal(response.Result, &toolsResponse)
		tr.tu.AssertNoError(err, "Failed to parse tools response")
		tr.tu.AssertTrue(len(toolsResponse.Tools) > 0, "Should return at least one tool")
	})

	// Test Tool Call
	tr.tu.t.Run("ToolCall", func(t *testing.T) {
		response := client.SendToolsCall("signoz_test_connection", map[string]interface{}{})

		tr.tu.AssertTrue(response.Error == nil, "Tool call should not return error")
		tr.tu.AssertTrue(response.Result != nil, "Tool call should return result")

		var toolResponse mcp.ToolsCallResponse
		err := json.Unmarshal(response.Result, &toolResponse)
		tr.tu.AssertNoError(err, "Failed to parse tool response")
		tr.tu.AssertFalse(toolResponse.IsError, "Tool should not return error")
		tr.tu.AssertTrue(len(toolResponse.Content) > 0, "Tool should return content")
	})
}

// PerformanceTestConfig configures performance tests
type PerformanceTestConfig struct {
	MessageCount    int
	ConcurrentUsers int
	TestDuration    time.Duration
}

// DefaultPerformanceConfig returns a default performance test configuration
func DefaultPerformanceConfig() *PerformanceTestConfig {
	return &PerformanceTestConfig{
		MessageCount:    100,
		ConcurrentUsers: 10,
		TestDuration:    time.Second * 30,
	}
}

// RunPerformanceTests runs performance tests with the given configuration
func (tr *TestRunner) RunPerformanceTests(config *PerformanceTestConfig) {
	tr.tu.t.Run("Performance", func(t *testing.T) {
		// This would be implemented with actual performance testing logic
		// For now, it's a placeholder showing the structure
		tr.tu.t.Logf("Running performance tests with %d messages, %d concurrent users, %v duration",
			config.MessageCount, config.ConcurrentUsers, config.TestDuration)

		// Simulate performance test
		start := time.Now()
		for i := 0; i < config.MessageCount; i++ {
			// Simulate message processing
			time.Sleep(time.Microsecond * 100)
		}
		duration := time.Since(start)

		tr.tu.t.Logf("Processed %d messages in %v (%.2f msg/sec)",
			config.MessageCount, duration, float64(config.MessageCount)/duration.Seconds())
	})
}
