package tests

import (
	"context"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"testing"
	"time"

	tests "github.com/jfraeys/fetch_ml/tests/fixtures"
)

const (
	manageScriptPath = "../../tools/manage.sh"
)

// TestHomelabSetupE2E tests the complete homelab setup workflow end-to-end
func TestHomelabSetupE2E(t *testing.T) {
	// Skip if essential tools not available
	repoRoot := e2eRepoRoot(t)
	manageScript := filepath.Join(repoRoot, "tools/manage.sh")
	if _, err := os.Stat(manageScript); os.IsNotExist(err) {
		t.Skip("manage.sh not found")
	}

	cliPath := e2eCLIPath(t)
	if _, err := os.Stat(cliPath); os.IsNotExist(err) {
		t.Skip("CLI not built - run 'make build' first")
	}

	// Use fixtures for manage script operations with correct working directory
	ms := tests.NewManageScriptWithDir(manageScript, repoRoot)
	defer ms.StopAndCleanup()

	testDir := t.TempDir()

	// Phase 1: Fresh Setup Simulation
	t.Run("FreshSetup", func(t *testing.T) {
		// Stop any existing services
		_ = ms.Stop()

		// Test initial status
		output, err := ms.Status()
		if err != nil {
			t.Fatalf("Failed to get status: %v", err)
		}
		t.Logf("Initial status: %s", output)

		// Start services
		if err := ms.Start(); err != nil {
			t.Skipf("Failed to start services: %v", err)
		}

		// Wait for health instead of fixed sleep
		for range 10 {
			healthOutput, err := ms.Health()
			if err == nil &&
				(strings.Contains(healthOutput, "API is healthy") ||
					strings.Contains(healthOutput, "Port 9101 is open")) {
				break
			}
			time.Sleep(300 * time.Millisecond)
		}

		// Verify with health check
		healthOutput, err := ms.Health()
		if err != nil {
			t.Logf("Health check failed (services may not be fully started)")
		} else {
			if !strings.Contains(healthOutput, "API is healthy") && !strings.Contains(healthOutput, "Port 9101 is open") {
				t.Errorf("Unexpected health check output: %s", healthOutput)
			}
			t.Log("Health check passed")
		}
	})

	// Phase 2: Service Management Workflow
	t.Run("ServiceManagement", func(t *testing.T) {
		// Check initial status
		output, err := ms.Status()
		if err != nil {
			t.Errorf("Status check failed: %v", err)
		}
		t.Logf("Initial status: %s", output)

		// Start services
		if err := ms.Start(); err != nil {
			t.Skipf("Failed to start services: %v", err)
		}

		// Wait for health instead of fixed sleep
		for range 15 {
			healthOutput, err := ms.Health()
			if err == nil &&
				(strings.Contains(healthOutput, "API is healthy") ||
					strings.Contains(healthOutput, "Port 9101 is open")) {
				break
			}
			time.Sleep(300 * time.Millisecond)
		}

		// Verify with health check
		healthOutput, err := ms.Health()
		t.Logf("Health check output: %s", healthOutput)
		if err != nil {
			t.Logf("Health check failed (expected if services not fully started): %v", err)
		}

		// Check final status
		statusOutput, err := ms.Status()
		if err != nil {
			t.Errorf("Final status check failed: %v", err)
		}
		t.Logf("Final status: %s", statusOutput)
	})

	// Phase 3: CLI Configuration Workflow
	t.Run("CLIConfiguration", func(t *testing.T) {
		// Create CLI config directory
		cliConfigDir := filepath.Join(testDir, "cli_config")
		if err := os.MkdirAll(cliConfigDir, 0750); err != nil {
			t.Fatalf("Failed to create CLI config dir: %v", err)
		}

		// Create minimal config
		configPath := filepath.Join(cliConfigDir, "config.yaml")
		configContent := `
redis_addr: localhost:6379
redis_db: 13
`
		if err := os.WriteFile(configPath, []byte(configContent), 0600); err != nil {
			t.Fatalf("Failed to create CLI config: %v", err)
		}

		// Test CLI init
		initCmd := exec.CommandContext(context.Background(), cliPath, "init")
		initCmd.Dir = cliConfigDir
		initOutput, err := initCmd.CombinedOutput()
		if err != nil {
			t.Logf("CLI init failed (may be expected): %v", err)
		}
		t.Logf("CLI init output: %s", string(initOutput))

		// Test CLI status
		statusCmd := exec.CommandContext(context.Background(), cliPath, "status")
		statusCmd.Dir = cliConfigDir
		statusOutput, err := statusCmd.CombinedOutput()
		if err != nil {
			t.Logf("CLI status failed (may be expected): %v", err)
		}
		t.Logf("CLI status output: %s", string(statusOutput))
	})
}

// TestDockerDeploymentE2E tests Docker deployment workflow
func TestDockerDeploymentE2E(t *testing.T) {
	t.Parallel() // Enable parallel execution

	if os.Getenv("FETCH_ML_E2E_DOCKER") != "1" {
		t.Skip("Skipping DockerDeploymentE2E (set FETCH_ML_E2E_DOCKER=1 to enable)")
	}

	// Skip if Docker not available
	dockerCompose := "../../docker-compose.yml"
	if _, err := os.Stat(dockerCompose); os.IsNotExist(err) {
		t.Skip("docker-compose.yml not found")
	}

	t.Run("DockerDeployment", func(t *testing.T) {
		// Stop any existing containers
		downCmd := exec.CommandContext(context.Background(),
			"docker-compose", "-f", dockerCompose, "down", "--remove-orphans")
		if err := downCmd.Run(); err != nil {
			t.Logf("Warning: Failed to stop existing containers: %v", err)
		}

		// Start Docker containers
		upCmd := exec.CommandContext(context.Background(), "docker-compose", "-f", dockerCompose, "up", "-d")
		if err := upCmd.Run(); err != nil {
			t.Fatalf("Failed to start Docker containers: %v", err)
		}

		// Wait for containers to be healthy using health checks instead of fixed sleep
		maxWait := 15 * time.Second // Reduced from 30 seconds
		start := time.Now()
		apiHealthy := false
		redisHealthy := false

		for time.Since(start) < maxWait && (!apiHealthy || !redisHealthy) {
			// Check if API container is healthy
			if !apiHealthy {
				healthCmd := exec.CommandContext(context.Background(),
					"docker", "ps", "--filter", "name=ml-experiments-api", "--format", "{{.Status}}")
				healthOutput, err := healthCmd.CombinedOutput()
				if err == nil && strings.Contains(string(healthOutput), "healthy") {
					t.Logf("API container became healthy in %v", time.Since(start))
					apiHealthy = true
				} else if err == nil && strings.Contains(string(healthOutput), "Up") {
					// Accept "Up" status as good enough for testing
					t.Logf("API container is up in %v (not necessarily healthy)", time.Since(start))
					apiHealthy = true
				}
			}

			// Check if Redis is healthy
			if !redisHealthy {
				redisCmd := exec.CommandContext(context.Background(),
					"docker", "ps", "--filter", "name=ml-experiments-redis", "--format", "{{.Status}}")
				redisOutput, err := redisCmd.CombinedOutput()
				if err == nil && strings.Contains(string(redisOutput), "healthy") {
					t.Logf("Redis container became healthy in %v", time.Since(start))
					redisHealthy = true
				}
			}

			// Break if both are healthy/up
			if apiHealthy && redisHealthy {
				t.Logf("All containers ready in %v", time.Since(start))
				break
			}

			time.Sleep(500 * time.Millisecond) // Check more frequently
		}

		// Check container status
		psCmd := exec.CommandContext(context.Background(),
			"docker-compose", "-f", dockerCompose, "ps", "--format", "table {{.Name}}\t{{.Status}}")
		psOutput, err := psCmd.CombinedOutput()
		if err != nil {
			t.Errorf("Docker ps failed: %v", err)
		}
		t.Logf("Docker containers status: %s", string(psOutput))

		// Test API endpoint in Docker (quick check)
		testDockerAPI(t)

		// Cleanup Docker synchronously to ensure proper cleanup
		t.Cleanup(func() {
			downCmd := exec.CommandContext(context.Background(),
				"docker-compose", "-f", dockerCompose, "down", "--remove-orphans", "--volumes")
			if err := downCmd.Run(); err != nil {
				t.Logf("Warning: Failed to stop Docker containers: %v", err)
			}
		})
	})
}

// testDockerAPI tests the Docker API endpoint
func testDockerAPI(t *testing.T) {
	// This would test the API endpoint - simplified for now
	t.Log("Testing Docker API functionality...")
	// In a real test, you would make HTTP requests to the API
}

// TestPerformanceE2E tests performance characteristics end-to-end
func TestPerformanceE2E(t *testing.T) {
	t.Parallel() // Enable parallel execution

	if os.Getenv("FETCH_ML_E2E_PERF") != "1" {
		t.Skip("Skipping PerformanceE2E (set FETCH_ML_E2E_PERF=1 to enable)")
	}

	manageScript := manageScriptPath
	if _, err := os.Stat(manageScript); os.IsNotExist(err) {
		t.Skip("manage.sh not found")
	}

	// Use fixtures for manage script operations with correct working directory
	repoRoot := e2eRepoRoot(t)
	manageScript = filepath.Join(repoRoot, "tools/manage.sh")
	ms := tests.NewManageScriptWithDir(manageScript, repoRoot)

	t.Run("PerformanceMetrics", func(t *testing.T) {
		// Test health check performance
		start := time.Now()
		_, err := ms.Health()
		duration := time.Since(start)

		t.Logf("Health check took %v", duration)

		if duration > 10*time.Second {
			t.Errorf("Health check took too long: %v", duration)
		}

		if err != nil {
			t.Logf("Health check failed (expected if services not running)")
		} else {
			t.Log("Health check passed")
		}

		// Test status check performance
		start = time.Now()
		output, err := ms.Status()
		duration = time.Since(start)

		t.Logf("Status check took %v", duration)
		t.Logf("Status output length: %d characters", len(output))

		if duration > 5*time.Second {
			t.Errorf("Status check took too long: %v", duration)
		}

		_ = err // Suppress unused variable warning
	})
}

// TestConfigurationScenariosE2E tests various configuration scenarios end-to-end
func TestConfigurationScenariosE2E(t *testing.T) {
	t.Parallel() // Enable parallel execution

	manageScript := "../../tools/manage.sh"
	if _, err := os.Stat(manageScript); os.IsNotExist(err) {
		t.Skip("manage.sh not found")
	}

	// Use fixtures for manage script operations with correct working directory
	repoRoot := e2eRepoRoot(t)
	manageScript = filepath.Join(repoRoot, "tools/manage.sh")
	ms := tests.NewManageScriptWithDir(manageScript, repoRoot)

	t.Run("ConfigurationHandling", func(t *testing.T) {
		testDir := t.TempDir()
		// Test status with different configuration states
		originalConfigDir := "../../configs"
		tempConfigDir := filepath.Join(testDir, "configs_backup")

		// Backup original configs if they exist
		if _, err := os.Stat(originalConfigDir); err == nil {
			if err := tests.CopyDir(originalConfigDir, tempConfigDir); err != nil {
				t.Fatalf("Failed to backup configs: %v", err)
			}
			defer func() {
				_ = tests.CopyDir(tempConfigDir, originalConfigDir)
			}()
		}

		// Test status without configs
		output, err := ms.Status()
		if err != nil {
			t.Errorf("Status check failed: %v", err)
		}
		t.Logf("Status without configs: %s", output)

		// Test health without configs
		_, err = ms.Health()
		if err != nil {
			t.Logf("Health check failed without configs (expected)")
		} else {
			t.Log("Health check passed without configs")
		}
	})
}
