package main

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"os"
	"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"
)

// Test configuration repository for integration tests
type TestConfigRepository struct{}

func (t *TestConfigRepository) GetSignozURL() string {
	return "http://localhost:3301"
}

func (t *TestConfigRepository) GetSignozAPIKey() string {
	return "test-api-key"
}

func (t *TestConfigRepository) GetTimeout() time.Duration {
	return 30 * time.Second
}

func (t *TestConfigRepository) GetRetryAttempts() int {
	return 3
}

func (t *TestConfigRepository) GetRetryDelay() time.Duration {
	return time.Second
}

func (t *TestConfigRepository) Validate() error {
	return nil
}

func (t *TestConfigRepository) GetLogConfig() *LogConfig {
	return &LogConfig{
		Level:      "info",
		Format:     "json",
		Output:     "stderr",
		Structured: true,
	}
}

// Additional methods to make TestConfigRepository compatible with the real config repository
func (t *TestConfigRepository) Load() error {
	return nil
}

func (t *TestConfigRepository) GetServerConfig() *ServerConfig {
	return &ServerConfig{
		Name:    "signoz-mcp-server",
		Version: "1.0.0",
		Host:    "localhost",
		Port:    8080,
		Debug:   false,
		Timeout: 30 * time.Second,
	}
}

func (t *TestConfigRepository) GetSignozConfig() *SignozConfig {
	return &SignozConfig{
		URL:            t.GetSignozURL(),
		APIKey:         t.GetSignozAPIKey(),
		Timeout:        t.GetTimeout(),
		RetryAttempts:  t.GetRetryAttempts(),
		RetryDelay:     t.GetRetryDelay(),
		MaxRetryDelay:  10 * time.Second,
		MaxConnections: 10,
	}
}

func (t *TestConfigRepository) GetTelemetryConfig() *TelemetryConfig {
	return &TelemetryConfig{
		Enabled: false,
	}
}

func (t *TestConfigRepository) GetCacheConfig() *CacheConfig {
	return &CacheConfig{
		Enabled: false,
	}
}

func (t *TestConfigRepository) IsDebugEnabled() bool {
	return false
}

func (t *TestConfigRepository) IsTelemetryEnabled() bool {
	return false
}

func (t *TestConfigRepository) IsCacheEnabled() bool {
	return false
}

func (t *TestConfigRepository) GetConfig() *Config {
	return &Config{
		Server:    *t.GetServerConfig(),
		Signoz:    *t.GetSignozConfig(),
		Log:       *t.GetLogConfig(),
		Telemetry: *t.GetTelemetryConfig(),
		Cache:     *t.GetCacheConfig(),
	}
}

// Type definitions for integration test compatibility
type ServerConfig struct {
	Name    string        `mapstructure:"name"`
	Version string        `mapstructure:"version"`
	Host    string        `mapstructure:"host"`
	Port    int           `mapstructure:"port"`
	Debug   bool          `mapstructure:"debug"`
	Timeout time.Duration `mapstructure:"timeout"`
}

type SignozConfig struct {
	URL            string        `mapstructure:"url"`
	APIKey         string        `mapstructure:"api_key"`
	Timeout        time.Duration `mapstructure:"timeout"`
	RetryAttempts  int           `mapstructure:"retry_attempts"`
	RetryDelay     time.Duration `mapstructure:"retry_delay"`
	MaxRetryDelay  time.Duration `mapstructure:"max_retry_delay"`
	MaxConnections int           `mapstructure:"max_connections"`
}

type TelemetryConfig struct {
	Enabled bool `mapstructure:"enabled"`
}

type CacheConfig struct {
	Enabled bool `mapstructure:"enabled"`
}

type Config struct {
	Server    ServerConfig    `mapstructure:"server"`
	Signoz    SignozConfig    `mapstructure:"signoz"`
	Log       LogConfig       `mapstructure:"log"`
	Telemetry TelemetryConfig `mapstructure:"telemetry"`
	Cache     CacheConfig     `mapstructure:"cache"`
}

// LogConfig for testing (simplified version)
type LogConfig struct {
	Level      string
	Format     string
	Output     string
	Structured bool
}

// Mock Signoz Client for integration tests
type MockSignozClient struct{}

func (m *MockSignozClient) TestConnection(ctx context.Context) (*domain.ConnectionStatus, error) {
	return &domain.ConnectionStatus{
		Connected:   true,
		URL:         "http://localhost:3301",
		Version:     "1.0.0",
		LastChecked: time.Now(),
		Latency:     time.Millisecond * 50,
	}, nil
}

func (m *MockSignozClient) FetchDashboards(ctx context.Context) ([]domain.Dashboard, error) {
	return []domain.Dashboard{
		{
			ID:          "dashboard-1",
			Name:        "Application Performance",
			Description: "APM dashboard showing application metrics",
			Tags:        []string{"apm", "performance"},
			CreatedAt:   time.Now().Add(-24 * time.Hour),
			UpdatedAt:   time.Now(),
		},
		{
			ID:          "dashboard-2",
			Name:        "Infrastructure Monitoring",
			Description: "Infrastructure metrics and alerts",
			Tags:        []string{"infrastructure", "monitoring"},
			CreatedAt:   time.Now().Add(-48 * time.Hour),
			UpdatedAt:   time.Now().Add(-time.Hour),
		},
	}, nil
}

func (m *MockSignozClient) FetchDashboardDetails(ctx context.Context, dashboardID string) (*domain.Dashboard, error) {
	return &domain.Dashboard{
		ID:          dashboardID,
		Name:        "Detailed Dashboard",
		Description: "Detailed dashboard with panels",
		Tags:        []string{"detailed", "test"},
		CreatedAt:   time.Now().Add(-24 * time.Hour),
		UpdatedAt:   time.Now(),
		Panels: []domain.Panel{
			{
				ID:    "panel-1",
				Title: "Response Time",
				Type:  "timeseries",
				Query: "SELECT avg(response_time) FROM metrics",
			},
			{
				ID:    "panel-2",
				Title: "Error Rate",
				Type:  "stat",
				Query: "SELECT error_rate FROM metrics",
			},
		},
	}, nil
}

func (m *MockSignozClient) FetchDashboardData(ctx context.Context, dashboardName string, timeRange domain.TimeRange) (*domain.DashboardData, error) {
	return &domain.DashboardData{
		DashboardID: "dashboard-1",
		TimeRange:   timeRange,
		Timestamp:   time.Now(),
		Panels: []domain.PanelData{
			{
				PanelID: "panel-1",
				Status:  "success",
				Data: map[string]interface{}{
					"series": []map[string]interface{}{
						{
							"name": "avg_response_time",
							"values": [][]interface{}{
								{timeRange.Start.Unix(), 150.5},
								{timeRange.End.Unix(), 175.2},
							},
						},
					},
				},
			},
		},
	}, nil
}

func (m *MockSignozClient) FetchAPMMetrics(ctx context.Context, serviceName string, timeRange domain.TimeRange) (*domain.APMMetrics, error) {
	return &domain.APMMetrics{
		ServiceName: serviceName,
		TimeRange:   timeRange,
		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: timeRange.Start, Value: 1500},
					{Timestamp: timeRange.End, Value: 1520},
				},
			},
		},
	}, nil
}

func (m *MockSignozClient) FetchServices(ctx context.Context, timeRange domain.TimeRange) ([]domain.Service, error) {
	return []domain.Service{
		{
			ServiceName: "user-service",
			Environment: "production",
			Tags:        map[string]string{"version": "1.2.3"},
			Operations:  []string{"GET /api/users", "POST /api/users"},
			CallRate:    150.5,
			ErrorRate:   0.01,
			P99Latency:  200.3,
			AvgLatency:  125.7,
			LastSeen:    time.Now().Add(-5 * time.Minute),
		},
		{
			ServiceName: "order-service",
			Environment: "production",
			Tags:        map[string]string{"version": "2.1.0"},
			Operations:  []string{"GET /api/orders", "POST /api/orders"},
			CallRate:    250.7,
			ErrorRate:   0.02,
			P99Latency:  350.1,
			AvgLatency:  175.3,
			LastSeen:    time.Now().Add(-2 * time.Minute),
		},
		{
			ServiceName: "payment-service",
			Environment: "production",
			Tags:        map[string]string{"version": "1.5.7"},
			Operations:  []string{"POST /api/payments"},
			CallRate:    75.2,
			ErrorRate:   0.05,
			P99Latency:  500.8,
			AvgLatency:  225.6,
			LastSeen:    time.Now().Add(-time.Minute),
		},
	}, nil
}

func (m *MockSignozClient) ExecuteClickHouseQuery(ctx context.Context, sql string, timeRange domain.TimeRange, params map[string]interface{}) (*domain.QueryResult, error) {
	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: [][]interface{}{
			{timeRange.Start.Unix(), "user-service", 125.5, 200},
			{timeRange.Start.Add(time.Minute).Unix(), "user-service", 134.2, 200},
			{timeRange.Start.Add(2 * time.Minute).Unix(), "user-service", 142.8, 500},
		},
		Meta: domain.QueryMeta{
			QueryID:       "clickhouse-query-789",
			ExecutionTime: time.Millisecond * 45,
			RowsAffected:  3,
			Statistics: domain.QueryStats{
				ReadRows:  3,
				ReadBytes: 1024,
			},
		},
	}, nil
}

func (m *MockSignozClient) ExecuteBuilderQuery(ctx context.Context, queryType, metricName, aggregation string, filters []domain.QueryFilter, groupBy []string, timeRange domain.TimeRange, step time.Duration, options map[string]interface{}) (*domain.QueryResult, error) {
	return &domain.QueryResult{
		Columns: []domain.ColumnInfo{
			{Name: "timestamp", Type: "UInt64"},
			{Name: metricName, Type: "Float64"},
		},
		Rows: [][]interface{}{
			{timeRange.Start.Unix(), 100.5},
			{timeRange.Start.Add(step).Unix(), 105.2},
			{timeRange.Start.Add(2 * step).Unix(), 98.7},
			{timeRange.End.Unix(), 103.1},
		},
		Meta: domain.QueryMeta{
			QueryID:       "builder-query-321",
			ExecutionTime: time.Millisecond * 32,
			RowsAffected:  4,
			Statistics: domain.QueryStats{
				ReadRows:  4,
				ReadBytes: 512,
			},
		},
	}, nil
}

func (m *MockSignozClient) GetTracesOrLogs(ctx context.Context, dataType string, timeRange domain.TimeRange, serviceName string, limit int) ([]domain.TraceLog, error) {
	return []domain.TraceLog{
		{
			ID:        "trace-abc123",
			Timestamp: timeRange.Start,
			Service:   serviceName,
			Operation: "GET /api/users",
			Duration:  time.Millisecond * 125,
			Status:    "success",
			Message:   "User retrieval successful",
			Attributes: map[string]interface{}{
				"user_id":       "user-123",
				"http_method":   "GET",
				"http_url":      "/api/users/123",
				"response_size": 1024,
			},
			SpanID:   "span-def456",
			TraceID:  "trace-abc123",
			ParentID: "",
		},
		{
			ID:        "trace-xyz789",
			Timestamp: timeRange.Start.Add(time.Minute),
			Service:   serviceName,
			Operation: "POST /api/orders",
			Duration:  time.Millisecond * 256,
			Status:    "error",
			Message:   "Order creation failed: insufficient inventory",
			Attributes: map[string]interface{}{
				"order_id":    "order-456",
				"http_method": "POST",
				"http_url":    "/api/orders",
				"error_code":  "INSUFFICIENT_INVENTORY",
				"product_id":  "product-789",
			},
			SpanID:   "span-ghi789",
			TraceID:  "trace-xyz789",
			ParentID: "",
		},
	}, nil
}

// Integration test helper functions
func createIntegrationTestServer() (*dependencies, error) {
	configRepo := &TestConfigRepository{}
	transportMode := TransportModeStdio

	// Create a simplified dependencies structure for testing
	deps := &dependencies{
		configRepo:    nil, // We'll handle config separately for tests
		transportMode: transportMode,
	}

	// Initialize stdio-aware logger
	deps.logger = NewStdioAwareLogger(configRepo.GetLogConfig().Level, transportMode)

	// Initialize no-op implementations
	deps.tracer = NoOpTracer{}
	deps.metrics = NoOpMetrics{}
	deps.cache = NoOpCache{}

	// Skip signoz client for tests - we'll use the mock directly in MCP service

	// Create test transport
	stdin := &bytes.Buffer{}
	stdout := &bytes.Buffer{}
	deps.transport = transport.NewStdioTransportWithStreams(stdin, stdout)

	return deps, nil
}

func sendMCPMessage(deps *dependencies, message *mcp.MCPMessage) (*mcp.MCPMessage, error) {
	// Get transport
	testTransport := deps.transport

	// Serialize and send message
	msgBytes, err := json.Marshal(message)
	if err != nil {
		return nil, err
	}

	// Write to stdin
	stdin := bytes.NewReader(append(msgBytes, '\n'))
	stdout := &bytes.Buffer{}

	// Create new transport for this message
	testTransport = transport.NewStdioTransportWithStreams(stdin, stdout)

	// Simulate server handling the message
	receivedMsg, err := testTransport.Receive()
	if err != nil {
		return nil, err
	}

	// Create MCP service and handle the message
	mcpService := &MockSignozClient{}

	// Handle message directly (simplified for testing)
	var response *mcp.MCPMessage
	switch receivedMsg.Method {
	case "initialize":
		response, err = handleInitializeIntegration(receivedMsg)
	case "tools/list":
		response, err = handleToolsListIntegration(receivedMsg)
	case "tools/call":
		response, err = handleToolsCallIntegration(receivedMsg, mcpService)
	default:
		return nil, fmt.Errorf("method not found: %s", receivedMsg.Method)
	}

	return response, err
}

func handleInitializeIntegration(message *mcp.MCPMessage) (*mcp.MCPMessage, error) {
	initResponse := mcp.InitializeResponse{
		ProtocolVersion: mcp.MCPVersion,
		ServerInfo: mcp.ServerInfo{
			Name:    "signoz-mcp-server",
			Version: "1.0.0",
		},
		Capabilities: mcp.ServerCapabilities{
			Tools: &mcp.ToolsCapability{
				ListChanged: false,
			},
		},
	}

	result, _ := json.Marshal(initResponse)

	return &mcp.MCPMessage{
		JSONRPC: "2.0",
		ID:      message.ID,
		Result:  result,
	}, nil
}

func handleToolsListIntegration(message *mcp.MCPMessage) (*mcp.MCPMessage, error) {
	tools := []mcp.Tool{
		{
			Name:        "signoz_test_connection",
			Description: "Verify connectivity to your Signoz instance and configuration",
			InputSchema: mcp.ToolInputSchema{
				Type:       "object",
				Properties: map[string]mcp.Property{},
				Required:   []string{},
			},
		},
		{
			Name:        "signoz_fetch_dashboards",
			Description: "List all available dashboards from Signoz",
			InputSchema: mcp.ToolInputSchema{
				Type:       "object",
				Properties: map[string]mcp.Property{},
				Required:   []string{},
			},
		},
		// Add other tools as needed for testing
	}

	toolsResponse := mcp.ToolsListResponse{
		Tools: tools,
	}

	result, _ := json.Marshal(toolsResponse)

	return &mcp.MCPMessage{
		JSONRPC: "2.0",
		ID:      message.ID,
		Result:  result,
	}, nil
}

func handleToolsCallIntegration(message *mcp.MCPMessage, mcpService *MockSignozClient) (*mcp.MCPMessage, error) {
	var request mcp.ToolsCallRequest
	if err := json.Unmarshal(message.Params, &request); err != nil {
		return nil, err
	}

	ctx := context.Background()
	var content []mcp.Content

	switch request.Name {
	case "signoz_test_connection":
		status, err := mcpService.TestConnection(ctx)
		if err != nil {
			return nil, err
		}
		statusJSON, _ := json.MarshalIndent(status, "", "  ")
		content = []mcp.Content{
			{
				Type: string(mcp.ContentTypeText),
				Text: string(statusJSON),
			},
		}

	case "signoz_fetch_dashboards":
		dashboards, err := mcpService.FetchDashboards(ctx)
		if err != nil {
			return nil, err
		}
		dashboardsJSON, _ := json.MarshalIndent(dashboards, "", "  ")
		content = []mcp.Content{
			{
				Type: string(mcp.ContentTypeText),
				Text: string(dashboardsJSON),
			},
		}

	default:
		return nil, fmt.Errorf("unknown tool: %s", request.Name)
	}

	response := mcp.ToolsCallResponse{
		Content: content,
		IsError: false,
	}

	result, _ := json.Marshal(response)

	return &mcp.MCPMessage{
		JSONRPC: "2.0",
		ID:      message.ID,
		Result:  result,
	}, nil
}

// Integration Tests
func TestIntegrationMCPWorkflow(t *testing.T) {
	deps, err := createIntegrationTestServer()
	if err != nil {
		t.Fatalf("Failed to create test server: %v", err)
	}

	t.Run("FullMCPWorkflow", func(t *testing.T) {
		// Step 1: Initialize
		initRequest := mcp.InitializeRequest{
			ProtocolVersion: mcp.MCPVersion,
			Capabilities:    mcp.Capabilities{},
			ClientInfo: mcp.ClientInfo{
				Name:    "test-client",
				Version: "1.0.0",
			},
		}

		params, _ := json.Marshal(initRequest)
		id := int64(1)

		initMessage := &mcp.MCPMessage{
			JSONRPC: "2.0",
			ID:      &id,
			Method:  "initialize",
			Params:  params,
		}

		initResponse, err := sendMCPMessage(deps, initMessage)
		if err != nil {
			t.Fatalf("Initialize failed: %v", err)
		}

		if initResponse.Error != nil {
			t.Fatalf("Initialize returned error: %v", initResponse.Error)
		}

		var initResp mcp.InitializeResponse
		if err := json.Unmarshal(initResponse.Result, &initResp); err != nil {
			t.Fatalf("Failed to parse initialize response: %v", err)
		}

		if initResp.ServerInfo.Name != "signoz-mcp-server" {
			t.Errorf("Expected server name 'signoz-mcp-server', got %s", initResp.ServerInfo.Name)
		}

		// Step 2: List Tools
		id = 2
		toolsListMessage := &mcp.MCPMessage{
			JSONRPC: "2.0",
			ID:      &id,
			Method:  "tools/list",
		}

		toolsResponse, err := sendMCPMessage(deps, toolsListMessage)
		if err != nil {
			t.Fatalf("Tools list failed: %v", err)
		}

		if toolsResponse.Error != nil {
			t.Fatalf("Tools list returned error: %v", toolsResponse.Error)
		}

		var toolsResp mcp.ToolsListResponse
		if err := json.Unmarshal(toolsResponse.Result, &toolsResp); err != nil {
			t.Fatalf("Failed to parse tools response: %v", err)
		}

		if len(toolsResp.Tools) < 2 {
			t.Errorf("Expected at least 2 tools, got %d", len(toolsResp.Tools))
		}

		// Step 3: Call a tool
		toolsCallRequest := mcp.ToolsCallRequest{
			Name:      "signoz_test_connection",
			Arguments: map[string]interface{}{},
		}

		params, _ = json.Marshal(toolsCallRequest)
		id = 3

		toolCallMessage := &mcp.MCPMessage{
			JSONRPC: "2.0",
			ID:      &id,
			Method:  "tools/call",
			Params:  params,
		}

		toolCallResponse, err := sendMCPMessage(deps, toolCallMessage)
		if err != nil {
			t.Fatalf("Tool call failed: %v", err)
		}

		if toolCallResponse.Error != nil {
			t.Fatalf("Tool call returned error: %v", toolCallResponse.Error)
		}

		var toolResp mcp.ToolsCallResponse
		if err := json.Unmarshal(toolCallResponse.Result, &toolResp); err != nil {
			t.Fatalf("Failed to parse tool call response: %v", err)
		}

		if toolResp.IsError {
			t.Errorf("Tool call returned error response")
		}

		if len(toolResp.Content) == 0 {
			t.Errorf("Expected content in tool response")
		}

		// Verify the content contains connection status
		content := toolResp.Content[0].Text
		if !strings.Contains(content, "connected") {
			t.Errorf("Expected connection status in response, got: %s", content)
		}
	})
}

func TestIntegrationAllTools(t *testing.T) {
	deps, err := createIntegrationTestServer()
	if err != nil {
		t.Fatalf("Failed to create test server: %v", err)
	}

	testCases := []struct {
		name     string
		toolName string
		args     map[string]interface{}
	}{
		{
			name:     "TestConnection",
			toolName: "signoz_test_connection",
			args:     map[string]interface{}{},
		},
		{
			name:     "FetchDashboards",
			toolName: "signoz_fetch_dashboards",
			args:     map[string]interface{}{},
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			toolsCallRequest := mcp.ToolsCallRequest{
				Name:      tc.toolName,
				Arguments: tc.args,
			}

			params, _ := json.Marshal(toolsCallRequest)
			id := int64(100)

			toolCallMessage := &mcp.MCPMessage{
				JSONRPC: "2.0",
				ID:      &id,
				Method:  "tools/call",
				Params:  params,
			}

			response, err := sendMCPMessage(deps, toolCallMessage)
			if err != nil {
				t.Fatalf("Tool call failed: %v", err)
			}

			if response.Error != nil {
				t.Fatalf("Tool call returned error: %v", response.Error)
			}

			var toolResp mcp.ToolsCallResponse
			if err := json.Unmarshal(response.Result, &toolResp); err != nil {
				t.Fatalf("Failed to parse tool response: %v", err)
			}

			if toolResp.IsError {
				t.Errorf("Tool %s returned error response", tc.toolName)
			}

			if len(toolResp.Content) == 0 {
				t.Errorf("Tool %s returned empty content", tc.toolName)
			}

			// Verify content is valid JSON for data tools
			if tc.toolName != "signoz_test_connection" {
				content := toolResp.Content[0].Text
				var jsonContent interface{}
				if err := json.Unmarshal([]byte(content), &jsonContent); err != nil {
					t.Errorf("Tool %s returned invalid JSON: %v", tc.toolName, err)
				}
			}
		})
	}
}

func TestIntegrationStdioModeDetection(t *testing.T) {
	// Test that the server properly detects stdio mode
	originalArgs := os.Args
	defer func() { os.Args = originalArgs }()

	testCases := []struct {
		name     string
		args     []string
		expected TransportMode
	}{
		{
			name:     "ExplicitStdio",
			args:     []string{"program", "stdio"},
			expected: TransportModeStdio,
		},
		{
			name:     "NoArgs",
			args:     []string{"program"},
			expected: TransportModeStdio,
		},
		{
			name:     "OtherArgs",
			args:     []string{"program", "other", "args"},
			expected: TransportModeStdio,
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			os.Args = tc.args
			detected := detectTransportMode()

			if detected != tc.expected {
				t.Errorf("Expected transport mode %s, got %s", tc.expected, detected)
			}

			isStdio := isStdioMode(detected)
			if !isStdio {
				t.Errorf("Expected stdio mode to be true for mode %s", detected)
			}
		})
	}
}

func TestIntegrationStdioAwareLogger(t *testing.T) {
	logger := NewStdioAwareLogger("debug", TransportModeStdio)

	if !logger.IsStdioMode() {
		t.Error("Expected logger to be in stdio mode")
	}

	if logger.GetTransportMode() != TransportModeStdio {
		t.Errorf("Expected transport mode %s, got %s", TransportModeStdio, logger.GetTransportMode())
	}

	// Test that logging doesn't interfere with stdout
	// (This is more of a behavioral test - logs should go to stderr)
	logger.Info("Test message for integration test")
	logger.Debug("Debug message for integration test")
	logger.Error("Error message for integration test")

	// If we reach here without issues, the logger is working correctly
}
