package examples

import (
	"context"
	"crypto/ed25519"
	"crypto/rand"
	"fmt"
	"testing"
	"time"

	"github.com/chorus-services/backbeat/pkg/sdk"
)

var testCounter int

// generateUniqueAgentID generates unique agent IDs for tests to avoid expvar conflicts
func generateUniqueAgentID(prefix string) string {
	testCounter++
	return fmt.Sprintf("%s-%d", prefix, testCounter)
}

// Test helper interface for both *testing.T and *testing.B
type testHelper interface {
	Fatalf(format string, args ...interface{})
}

// Test helper to create a test client configuration
func createTestConfig(t testHelper, agentIDPrefix string) *sdk.Config {
	_, signingKey, err := ed25519.GenerateKey(rand.Reader)
	if err != nil {
		t.Fatalf("Failed to generate signing key: %v", err)
	}
	
	config := sdk.DefaultConfig()
	config.ClusterID = "test-cluster"
	config.AgentID = generateUniqueAgentID(agentIDPrefix)
	config.NATSUrl = "nats://localhost:4222" // Assumes NATS is running for tests
	config.SigningKey = signingKey
	
	return config
}

// TestSimpleAgentPattern tests the simple agent usage pattern
func TestSimpleAgentPattern(t *testing.T) {
	config := createTestConfig(t, "test-simple-agent")
	client := sdk.NewClient(config)
	
	// Context for timeout control (used in full integration tests)
	_ = context.Background()
	
	// Track callback invocations  
	var beatCount, downbeatCount int
	
	// Register callbacks
	err := client.OnBeat(func(beat sdk.BeatFrame) {
		beatCount++
		t.Logf("Beat received: %d (downbeat: %v)", beat.BeatIndex, beat.Downbeat)
	})
	if err != nil {
		t.Fatalf("Failed to register beat callback: %v", err)
	}
	
	err = client.OnDownbeat(func(beat sdk.BeatFrame) {
		downbeatCount++
		t.Logf("Downbeat received: %d", beat.BeatIndex)
	})
	if err != nil {
		t.Fatalf("Failed to register downbeat callback: %v", err)
	}
	
	// Use variables to prevent unused warnings
	_ = beatCount
	_ = downbeatCount
	
	// This test only checks if the client can be configured and started
	// without errors. Full integration tests would require running services.
	
	// Test health status before starting
	health := client.Health()
	if health.Connected {
		t.Error("Client should not be connected before Start()")
	}
	
	// Test that we can create status claims
	err = client.EmitStatusClaim(sdk.StatusClaim{
		State:     "planning",
		BeatsLeft: 10,
		Progress:  0.0,
		Notes:     "Test status claim",
	})
	// This should fail because client isn't started
	if err == nil {
		t.Error("EmitStatusClaim should fail when client not started")
	}
}

// TestBeatBudgetPattern tests the beat budget usage pattern
func TestBeatBudgetPattern(t *testing.T) {
	config := createTestConfig(t, "test-budget-agent")
	client := sdk.NewClient(config)
	
	// Test beat budget without starting client (should work for timeout logic)
	err := client.WithBeatBudget(2, func() error {
		time.Sleep(100 * time.Millisecond) // Quick task
		return nil
	})
	
	// This may fail due to no beat timing available, but shouldn't panic
	if err != nil {
		t.Logf("Beat budget failed as expected (no timing): %v", err)
	}
	
	// Test invalid budget
	err = client.WithBeatBudget(0, func() error {
		return nil
	})
	if err == nil {
		t.Error("WithBeatBudget should fail with zero budget")
	}
	
	err = client.WithBeatBudget(-1, func() error {
		return nil
	})
	if err == nil {
		t.Error("WithBeatBudget should fail with negative budget")
	}
}

// TestClientConfiguration tests various client configuration scenarios
func TestClientConfiguration(t *testing.T) {
	// Test with minimal config
	config := &sdk.Config{
		ClusterID: "test",
		AgentID:   "test-agent",
		NATSUrl:   "nats://localhost:4222",
	}
	
	client := sdk.NewClient(config)
	if client == nil {
		t.Fatal("NewClient should not return nil")
	}
	
	// Test health before start
	health := client.Health()
	if health.Connected {
		t.Error("New client should not be connected")
	}
	
	// Test utilities with no beat data
	beat := client.GetCurrentBeat()
	if beat != 0 {
		t.Errorf("GetCurrentBeat should return 0 initially, got %d", beat)
	}
	
	window := client.GetCurrentWindow()
	if window != "" {
		t.Errorf("GetCurrentWindow should return empty string initially, got %s", window)
	}
	
	// Test IsInWindow
	if client.IsInWindow("any-window") {
		t.Error("IsInWindow should return false with no current window")
	}
}

// TestStatusClaimValidation tests status claim validation
func TestStatusClaimValidation(t *testing.T) {
	config := createTestConfig(t, "test-validation")
	client := sdk.NewClient(config)
	
	// Test various invalid status claims
	testCases := []struct {
		name    string
		claim   sdk.StatusClaim
		wantErr bool
	}{
		{
			name: "valid claim",
			claim: sdk.StatusClaim{
				State:     "executing",
				BeatsLeft: 5,
				Progress:  0.5,
				Notes:     "Test note",
			},
			wantErr: false, // Will still error due to no connection, but validation should pass
		},
		{
			name: "invalid state",
			claim: sdk.StatusClaim{
				State:     "invalid",
				BeatsLeft: 5,
				Progress:  0.5,
				Notes:     "Test note",
			},
			wantErr: true,
		},
		{
			name: "negative progress",
			claim: sdk.StatusClaim{
				State:     "executing",
				BeatsLeft: 5,
				Progress:  -0.1,
				Notes:     "Test note",
			},
			wantErr: true,
		},
		{
			name: "progress too high",
			claim: sdk.StatusClaim{
				State:     "executing",
				BeatsLeft: 5,
				Progress:  1.1,
				Notes:     "Test note",
			},
			wantErr: true,
		},
		{
			name: "negative beats left",
			claim: sdk.StatusClaim{
				State:     "executing",
				BeatsLeft: -1,
				Progress:  0.5,
				Notes:     "Test note",
			},
			wantErr: true,
		},
	}
	
	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			err := client.EmitStatusClaim(tc.claim)
			
			if tc.wantErr && err == nil {
				t.Error("Expected error but got none")
			}
			
			// Note: All will error due to no connection, but we're testing validation
			if err != nil {
				t.Logf("Error (expected): %v", err)
			}
		})
	}
}

// BenchmarkStatusClaimEmission benchmarks status claim creation and validation
func BenchmarkStatusClaimEmission(b *testing.B) {
	config := createTestConfig(b, "benchmark-agent")
	client := sdk.NewClient(config)
	
	claim := sdk.StatusClaim{
		State:     "executing",
		BeatsLeft: 10,
		Progress:  0.75,
		Notes:     "Benchmark test claim",
	}
	
	b.ResetTimer()
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			// This will fail due to no connection, but measures validation overhead
			client.EmitStatusClaim(claim)
		}
	})
}

// BenchmarkBeatCallbacks benchmarks callback execution
func BenchmarkBeatCallbacks(b *testing.B) {
	config := createTestConfig(b, "callback-benchmark")
	client := sdk.NewClient(config)
	
	// Register a simple callback
	client.OnBeat(func(beat sdk.BeatFrame) {
		// Minimal processing
		_ = beat.BeatIndex
	})
	
	// Create a mock beat frame
	beatFrame := sdk.BeatFrame{
		Type:       "backbeat.beatframe.v1",
		ClusterID:  "test",
		BeatIndex:  1,
		Downbeat:   false,
		Phase:      "test",
		HLC:        "123-0",
		WindowID:   "test-window",
		TempoBPM:   2,  // 30-second beats - much more reasonable for testing
	}
	
	b.ResetTimer()
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			// Simulate callback execution
			// Note: This doesn't actually invoke callbacks since client isn't started
			_ = beatFrame
		}
	})
}

// TestDetermineState tests the state determination logic from simple_agent.go
func TestDetermineState(t *testing.T) {
	tests := []struct {
		total    int64
		completed int64
		expected string
	}{
		{0, 0, "waiting"},
		{5, 5, "done"},
		{5, 3, "executing"},
		{5, 0, "planning"},
		{10, 8, "executing"},
		{1, 1, "done"},
	}

	for _, test := range tests {
		result := determineState(test.total, test.completed)
		if result != test.expected {
			t.Errorf("determineState(%d, %d) = %s; expected %s", 
				test.total, test.completed, result, test.expected)
		}
	}
}

// TestCalculateBeatsLeft tests the beats remaining calculation from simple_agent.go
func TestCalculateBeatsLeft(t *testing.T) {
	tests := []struct {
		total    int64
		completed int64
		expected int
	}{
		{0, 0, 0},
		{5, 5, 0},
		{5, 3, 10}, // (5-3) * 5 = 10
		{10, 0, 50}, // 10 * 5 = 50
		{1, 0, 5},   // 1 * 5 = 5
	}

	for _, test := range tests {
		result := calculateBeatsLeft(test.total, test.completed)
		if result != test.expected {
			t.Errorf("calculateBeatsLeft(%d, %d) = %d; expected %d", 
				test.total, test.completed, result, test.expected)
		}
	}
}

// TestTaskStructure tests Task struct from task_processor.go
func TestTaskStructure(t *testing.T) {
	task := &Task{
		ID:          "test-task-123",
		Description: "Test processing task",
		BeatBudget:  8,
		WorkTime:    3 * time.Second,
		Created:     time.Now(),
	}

	if task.ID == "" {
		t.Error("Expected task ID to be set")
	}

	if task.Description == "" {
		t.Error("Expected task description to be set")
	}

	if task.BeatBudget <= 0 {
		t.Error("Expected positive beat budget")
	}

	if task.WorkTime <= 0 {
		t.Error("Expected positive work time")
	}

	if task.Created.IsZero() {
		t.Error("Expected creation time to be set")
	}
}

// TestServiceHealthStructure tests ServiceHealth struct from service_monitor.go
func TestServiceHealthStructure(t *testing.T) {
	health := &ServiceHealth{
		ServiceName:   "test-service",
		Status:        "healthy",
		LastCheck:     time.Now(),
		ResponseTime:  150 * time.Millisecond,
		ErrorCount:    0,
		Uptime:        5 * time.Minute,
	}

	if health.ServiceName == "" {
		t.Error("Expected service name to be set")
	}

	validStatuses := []string{"healthy", "degraded", "unhealthy", "unknown"}
	validStatus := false
	for _, status := range validStatuses {
		if health.Status == status {
			validStatus = true
			break
		}
	}
	if !validStatus {
		t.Errorf("Expected valid status, got: %s", health.Status)
	}

	if health.ResponseTime < 0 {
		t.Error("Expected non-negative response time")
	}

	if health.ErrorCount < 0 {
		t.Error("Expected non-negative error count")
	}
}

// TestSystemMetricsStructure tests SystemMetrics struct from service_monitor.go
func TestSystemMetricsStructure(t *testing.T) {
	metrics := &SystemMetrics{
		CPUPercent:     25.5,
		MemoryPercent:  67.8,
		GoroutineCount: 42,
		HeapSizeMB:     128.5,
	}

	if metrics.CPUPercent < 0 || metrics.CPUPercent > 100 {
		t.Error("Expected CPU percentage between 0 and 100")
	}

	if metrics.MemoryPercent < 0 || metrics.MemoryPercent > 100 {
		t.Error("Expected memory percentage between 0 and 100")
	}

	if metrics.GoroutineCount < 0 {
		t.Error("Expected non-negative goroutine count")
	}

	if metrics.HeapSizeMB < 0 {
		t.Error("Expected non-negative heap size")
	}
}

// TestHealthScoreCalculation tests calculateHealthScore from service_monitor.go
func TestHealthScoreCalculation(t *testing.T) {
	tests := []struct {
		summary  map[string]int
		expected float64
	}{
		{map[string]int{"healthy": 0, "degraded": 0, "unhealthy": 0, "unknown": 0}, 0.0},
		{map[string]int{"healthy": 4, "degraded": 0, "unhealthy": 0, "unknown": 0}, 1.0},
		{map[string]int{"healthy": 0, "degraded": 0, "unhealthy": 4, "unknown": 0}, 0.0},
		{map[string]int{"healthy": 2, "degraded": 2, "unhealthy": 0, "unknown": 0}, 0.75},
		{map[string]int{"healthy": 1, "degraded": 1, "unhealthy": 1, "unknown": 1}, 0.4375},
	}

	for i, test := range tests {
		result := calculateHealthScore(test.summary)
		if result != test.expected {
			t.Errorf("Test %d: calculateHealthScore(%v) = %.4f; expected %.4f", 
				i, test.summary, result, test.expected)
		}
	}
}

// TestDetermineOverallState tests determineOverallState from service_monitor.go
func TestDetermineOverallState(t *testing.T) {
	tests := []struct {
		summary  map[string]int
		expected string
	}{
		{map[string]int{"healthy": 3, "degraded": 0, "unhealthy": 0, "unknown": 0}, "done"},
		{map[string]int{"healthy": 2, "degraded": 1, "unhealthy": 0, "unknown": 0}, "executing"},
		{map[string]int{"healthy": 1, "degraded": 1, "unhealthy": 1, "unknown": 0}, "failed"},
		{map[string]int{"healthy": 0, "degraded": 0, "unhealthy": 0, "unknown": 3}, "waiting"},
		{map[string]int{"healthy": 0, "degraded": 0, "unhealthy": 1, "unknown": 0}, "failed"},
	}

	for i, test := range tests {
		result := determineOverallState(test.summary)
		if result != test.expected {
			t.Errorf("Test %d: determineOverallState(%v) = %s; expected %s", 
				i, test.summary, result, test.expected)
		}
	}
}

// TestFormatHealthSummary tests formatHealthSummary from service_monitor.go
func TestFormatHealthSummary(t *testing.T) {
	summary := map[string]int{
		"healthy":   3,
		"degraded":  2,
		"unhealthy": 1,
		"unknown":   0,
	}

	result := formatHealthSummary(summary)
	expected := "H:3 D:2 U:1 ?:0"

	if result != expected {
		t.Errorf("formatHealthSummary() = %s; expected %s", result, expected)
	}
}

// TestCollectSystemMetrics tests collectSystemMetrics from service_monitor.go
func TestCollectSystemMetrics(t *testing.T) {
	metrics := collectSystemMetrics()

	if metrics.GoroutineCount <= 0 {
		t.Error("Expected positive goroutine count")
	}

	if metrics.HeapSizeMB < 0 {
		t.Error("Expected non-negative heap size")
	}

	// Note: CPU and Memory percentages are simplified in the example implementation
	if metrics.CPUPercent < 0 {
		t.Error("Expected non-negative CPU percentage")
	}

	if metrics.MemoryPercent < 0 {
		t.Error("Expected non-negative memory percentage")
	}
}