package aibrix

import (
	"context"
	"fmt"
	"os"
	"os/exec"
	"time"

	"github.com/vllm-project/semantic-router/e2e/pkg/framework"
	"github.com/vllm-project/semantic-router/e2e/pkg/helm"
	"github.com/vllm-project/semantic-router/e2e/pkg/helpers"
	"github.com/vllm-project/semantic-router/e2e/pkg/testmatrix"

	// Import testcases package to register all test cases via their init() functions
	_ "github.com/vllm-project/semantic-router/e2e/testcases"
)

const (
	// Version Configuration
	// AIBrix version - can be overridden via AIBRIX_VERSION environment variable
	defaultAIBrixVersion = "v0.5.0"

	// Kubernetes Namespaces - used frequently throughout
	namespaceSemanticRouter = "vllm-semantic-router-system"
	namespaceEnvoyGateway   = "envoy-gateway-system"
	namespaceAIBrix         = "aibrix-system"

	// Deployment Names - used in multiple verification steps
	deploymentSemanticRouter          = "semantic-router"
	deploymentEnvoyGateway            = "envoy-gateway"
	deploymentAIBrixGatewayPlugins    = "aibrix-gateway-plugins"
	deploymentAIBrixMetadataService   = "aibrix-metadata-service"
	deploymentAIBrixControllerManager = "aibrix-controller-manager"
	deploymentDemoLLM                 = "vllm-llama3-8b-instruct"

	// Label Selectors - complex values
	labelSelectorAIBrixGateway = "gateway.envoyproxy.io/owning-gateway-namespace=aibrix-system,gateway.envoyproxy.io/owning-gateway-name=aibrix-eg"

	// Timeouts - configuration values for tuning
	timeoutSemanticRouterDeploy = 20 * time.Minute
	timeoutComponentDeploy      = 2 * time.Minute // For Envoy Gateway, AIBrix plugins/metadata, Demo LLM
	timeoutWebhookDeploy        = 5 * time.Minute // For webhook-enabled components (controller-manager)
	timeoutEnvoyServiceReady    = 10 * time.Minute
	timeoutStabilization        = 60 * time.Second // Increased for CI environments
	retryInterval               = 5 * time.Second
)

// Profile implements the AIBrix test profile
type Profile struct {
	verbose       bool
	aibrixVersion string
}

// NewProfile creates a new AIBrix profile
func NewProfile() *Profile {
	// Allow version override via environment variable
	version := os.Getenv("AIBRIX_VERSION")
	if version == "" {
		version = defaultAIBrixVersion
	}
	return &Profile{
		aibrixVersion: version,
	}
}

// Name returns the profile name
func (p *Profile) Name() string {
	return "aibrix"
}

// Description returns the profile description
func (p *Profile) Description() string {
	return "Tests Semantic Router with vLLM AIBrix integration"
}

// Setup deploys all required components for AIBrix testing
func (p *Profile) Setup(ctx context.Context, opts *framework.SetupOptions) error {
	p.verbose = opts.Verbose
	p.log("Setting up AIBrix test environment")

	deployer := helm.NewDeployer(opts.KubeConfig, opts.Verbose)

	// Track what we've deployed for cleanup on error
	var (
		semanticRouterDeployed   bool
		aibrixDepsDeployed       bool
		aibrixCoreDeployed       bool
		gatewayResourcesDeployed bool
	)

	// Ensure cleanup on error
	defer func() {
		if r := recover(); r != nil {
			p.log("Panic during setup, cleaning up...")
			p.cleanupPartialDeployment(ctx, opts, semanticRouterDeployed, aibrixDepsDeployed, aibrixCoreDeployed, gatewayResourcesDeployed)
			panic(r) // Re-panic after cleanup
		}
	}()

	// Step 1: Deploy Semantic Router
	p.log("Step 1/5: Deploying Semantic Router")
	if err := p.deploySemanticRouter(ctx, deployer, opts); err != nil {
		return fmt.Errorf("failed to deploy semantic router: %w", err)
	}
	semanticRouterDeployed = true

	// Step 2: Deploy AIBrix Dependencies
	p.log("Step 2/5: Deploying AIBrix Dependencies")
	if err := p.deployAIBrixDependencies(ctx, opts); err != nil {
		p.cleanupPartialDeployment(ctx, opts, semanticRouterDeployed, false, false, false)
		return fmt.Errorf("failed to deploy AIBrix dependencies: %w", err)
	}
	aibrixDepsDeployed = true

	// Step 3: Deploy AIBrix Core
	p.log("Step 3/5: Deploying AIBrix Core")
	if err := p.deployAIBrixCore(ctx, opts); err != nil {
		p.cleanupPartialDeployment(ctx, opts, semanticRouterDeployed, aibrixDepsDeployed, false, false)
		return fmt.Errorf("failed to deploy AIBrix core: %w", err)
	}
	aibrixCoreDeployed = true

	// Step 4: Deploy Demo LLM and Gateway API Resources
	p.log("Step 4/5: Deploying Demo LLM and Gateway API Resources")
	if err := p.deployGatewayResources(ctx, opts); err != nil {
		p.cleanupPartialDeployment(ctx, opts, semanticRouterDeployed, aibrixDepsDeployed, aibrixCoreDeployed, false)
		return fmt.Errorf("failed to deploy gateway resources: %w", err)
	}
	gatewayResourcesDeployed = true

	// Step 5: Verify all components are ready
	p.log("Step 5/5: Verifying all components are ready")
	if err := p.verifyEnvironment(ctx, opts); err != nil {
		p.log("ERROR: Environment verification failed: %v", err)
		p.cleanupPartialDeployment(ctx, opts, semanticRouterDeployed, aibrixDepsDeployed, aibrixCoreDeployed, gatewayResourcesDeployed)
		return fmt.Errorf("failed to verify environment: %w", err)
	}

	p.log("AIBrix test environment setup complete")
	return nil
}

// Teardown cleans up all deployed resources
func (p *Profile) Teardown(ctx context.Context, opts *framework.TeardownOptions) error {
	p.verbose = opts.Verbose
	p.log("Tearing down AIBrix test environment")

	deployer := helm.NewDeployer(opts.KubeConfig, opts.Verbose)

	// Clean up in reverse order
	p.log("Cleaning up Gateway API resources")
	_ = p.cleanupGatewayResources(ctx, opts)

	p.log("Cleaning up AIBrix components")
	_ = p.cleanupAIBrix(ctx, opts)

	p.log("Uninstalling Semantic Router")
	_ = deployer.Uninstall(ctx, deploymentSemanticRouter, namespaceSemanticRouter)

	p.log("AIBrix test environment teardown complete")
	return nil
}

// GetTestCases returns the list of test cases for this profile
func (p *Profile) GetTestCases() []string {
	return testmatrix.Combine(
		testmatrix.RouterSmoke,
		[]string{
			"aibrix-control-plane-health",
		},
	)
}

// GetServiceConfig returns the service configuration for accessing the deployed service
func (p *Profile) GetServiceConfig() framework.ServiceConfig {
	return framework.ServiceConfig{
		LabelSelector: labelSelectorAIBrixGateway,
		Namespace:     namespaceEnvoyGateway,
		PortMapping:   "8080:80",
	}
}

func (p *Profile) deploySemanticRouter(ctx context.Context, deployer *helm.Deployer, opts *framework.SetupOptions) error {
	// Use local Helm chart instead of remote OCI registry
	installOpts := helm.InstallOptions{
		ReleaseName: deploymentSemanticRouter,
		Chart:       "deploy/helm/semantic-router",
		Namespace:   namespaceSemanticRouter,
		ValuesFiles: []string{"e2e/profiles/aibrix/values.yaml"},
		Set: map[string]string{
			"image.repository": "ghcr.io/vllm-project/semantic-router/extproc",
			"image.tag":        opts.ImageTag,
			"image.pullPolicy": "Never", // Use local image, don't pull from registry
		},
		Wait:    true,
		Timeout: "20m", // Increased timeout for model downloads
	}

	if err := deployer.Install(ctx, installOpts); err != nil {
		return err
	}

	return deployer.WaitForDeployment(ctx, namespaceSemanticRouter, deploymentSemanticRouter, timeoutSemanticRouterDeploy)
}

func (p *Profile) deployAIBrixDependencies(ctx context.Context, opts *framework.SetupOptions) error {
	// Apply AIBrix dependency components from GitHub release
	dependencyURL := fmt.Sprintf("https://github.com/vllm-project/aibrix/releases/download/%s/aibrix-dependency-%s.yaml",
		p.aibrixVersion, p.aibrixVersion)

	p.log("Deploying AIBrix dependencies (version: %s)", p.aibrixVersion)
	if err := p.kubectlApply(ctx, opts.KubeConfig, dependencyURL); err != nil {
		return fmt.Errorf("failed to apply AIBrix dependencies: %w", err)
	}

	// Wait for Envoy Gateway to be ready
	return p.waitForDeployment(ctx, opts, namespaceEnvoyGateway, deploymentEnvoyGateway, timeoutComponentDeploy)
}

func (p *Profile) deployAIBrixCore(ctx context.Context, opts *framework.SetupOptions) error {
	// Apply AIBrix core components from GitHub release
	coreURL := fmt.Sprintf("https://github.com/vllm-project/aibrix/releases/download/%s/aibrix-core-%s.yaml",
		p.aibrixVersion, p.aibrixVersion)

	p.log("Deploying AIBrix core (version: %s)", p.aibrixVersion)
	if err := p.kubectlApply(ctx, opts.KubeConfig, coreURL); err != nil {
		return fmt.Errorf("failed to apply AIBrix core: %w", err)
	}

	// Patch aibrix-gateway-plugins to reduce resource requests for CI environments
	// The default requests (2 CPU, 8Gi memory) are too high for GitHub Actions runners
	p.log("Patching aibrix-gateway-plugins resource requests for CI compatibility...")
	patchCmd := exec.CommandContext(ctx, "kubectl", "--kubeconfig", opts.KubeConfig,
		"patch", "deployment", deploymentAIBrixGatewayPlugins,
		"-n", namespaceAIBrix,
		"--type", "json",
		"-p", `[
			{"op": "replace", "path": "/spec/template/spec/containers/0/resources/requests/cpu", "value": "500m"},
			{"op": "replace", "path": "/spec/template/spec/containers/0/resources/requests/memory", "value": "1Gi"},
			{"op": "replace", "path": "/spec/template/spec/containers/0/resources/limits/cpu", "value": "1"},
			{"op": "replace", "path": "/spec/template/spec/containers/0/resources/limits/memory", "value": "2Gi"}
		]`)
	if p.verbose {
		patchCmd.Stdout = os.Stdout
		patchCmd.Stderr = os.Stderr
	}
	if err := patchCmd.Run(); err != nil {
		p.log("Warning: Failed to patch resource requests (proceeding anyway): %v", err)
	}

	// Wait for AIBrix core components to be ready
	deployments := []struct {
		namespace string
		name      string
		timeout   time.Duration
	}{
		{namespaceAIBrix, deploymentAIBrixGatewayPlugins, timeoutComponentDeploy},
		{namespaceAIBrix, deploymentAIBrixMetadataService, timeoutComponentDeploy},
		{namespaceAIBrix, deploymentAIBrixControllerManager, timeoutWebhookDeploy}, // Longer timeout for webhook setup
	}

	for _, dep := range deployments {
		p.log("Waiting for %s/%s to be ready (timeout: %v)...", dep.namespace, dep.name, dep.timeout)
		if err := p.waitForDeployment(ctx, opts, dep.namespace, dep.name, dep.timeout); err != nil {
			return fmt.Errorf("deployment %s/%s not ready: %w", dep.namespace, dep.name, err)
		}
	}

	return nil
}

func (p *Profile) deployGatewayResources(ctx context.Context, opts *framework.SetupOptions) error {
	// Apply base model (Demo LLM)
	if err := p.kubectlApply(ctx, opts.KubeConfig, "deploy/kubernetes/aibrix/aigw-resources/base-model.yaml"); err != nil {
		return fmt.Errorf("failed to apply base model: %w", err)
	}

	// Wait for Demo LLM deployment
	if err := p.waitForDeployment(ctx, opts, "default", deploymentDemoLLM, timeoutComponentDeploy); err != nil {
		return fmt.Errorf("demo LLM deployment not ready: %w", err)
	}

	// Apply gateway API resources
	if err := p.kubectlApply(ctx, opts.KubeConfig, "deploy/kubernetes/aibrix/aigw-resources/gwapi-resources.yaml"); err != nil {
		return fmt.Errorf("failed to apply gateway API resources: %w", err)
	}

	return nil
}

func (p *Profile) verifyEnvironment(ctx context.Context, opts *framework.SetupOptions) error {
	client, err := helpers.NewKubeClient(opts.KubeConfig)
	if err != nil {
		return err
	}

	// Give deployments extra time to stabilize after initial readiness
	p.log("Waiting for deployments to stabilize...")
	time.Sleep(timeoutStabilization)

	p.log("Waiting for Envoy Gateway service to be ready...")
	envoyService, err := helpers.WaitForServiceByLabelWithReadyPods(
		ctx,
		client,
		namespaceEnvoyGateway,
		labelSelectorAIBrixGateway,
		timeoutEnvoyServiceReady,
		retryInterval,
		p.verbose,
		p.log,
	)
	if err != nil {
		return err
	}
	p.log("Envoy Gateway service is ready: %s", envoyService)

	p.log("Verifying all deployments are healthy...")
	if err := helpers.VerifyDeployments(ctx, client, []helpers.DeploymentRef{
		{Namespace: namespaceSemanticRouter, Name: deploymentSemanticRouter},
		{Namespace: namespaceAIBrix, Name: deploymentAIBrixGatewayPlugins},
		{Namespace: namespaceAIBrix, Name: deploymentAIBrixMetadataService},
		{Namespace: namespaceAIBrix, Name: deploymentAIBrixControllerManager},
		{Namespace: namespaceEnvoyGateway, Name: deploymentEnvoyGateway},
		{Namespace: "default", Name: deploymentDemoLLM},
	}, p.verbose); err != nil {
		return fmt.Errorf("required deployment not healthy: %w", err)
	}

	p.log("All deployments are healthy")

	return nil
}

func (p *Profile) cleanupGatewayResources(ctx context.Context, opts *framework.TeardownOptions) error {
	// Delete in reverse order
	_ = p.kubectlDelete(ctx, opts.KubeConfig, "deploy/kubernetes/aibrix/aigw-resources/gwapi-resources.yaml")
	_ = p.kubectlDelete(ctx, opts.KubeConfig, "deploy/kubernetes/aibrix/aigw-resources/base-model.yaml")
	return nil
}

func (p *Profile) cleanupPartialDeployment(ctx context.Context, opts *framework.SetupOptions, semanticRouter, aibrixDeps, aibrixCore, gatewayResources bool) {
	p.log("Cleaning up partial deployment (semanticRouter=%v, aibrixDeps=%v, aibrixCore=%v, gatewayResources=%v)",
		semanticRouter, aibrixDeps, aibrixCore, gatewayResources)

	// Create TeardownOptions from SetupOptions
	teardownOpts := &framework.TeardownOptions{
		KubeClient:  opts.KubeClient,
		KubeConfig:  opts.KubeConfig,
		ClusterName: opts.ClusterName,
		Verbose:     opts.Verbose,
	}

	// Clean up in reverse order
	if gatewayResources {
		p.log("Cleaning up Gateway API resources...")
		_ = p.cleanupGatewayResources(ctx, teardownOpts)
	}

	if aibrixCore || aibrixDeps {
		p.log("Cleaning up AIBrix components...")
		_ = p.cleanupAIBrix(ctx, teardownOpts)
	}

	if semanticRouter {
		p.log("Uninstalling Semantic Router...")
		deployer := helm.NewDeployer(opts.KubeConfig, opts.Verbose)
		_ = deployer.Uninstall(ctx, deploymentSemanticRouter, namespaceSemanticRouter)
	}

	p.log("Partial deployment cleanup complete")
}

func (p *Profile) cleanupAIBrix(ctx context.Context, opts *framework.TeardownOptions) error {
	// Delete AIBrix core and dependencies
	coreURL := fmt.Sprintf("https://github.com/vllm-project/aibrix/releases/download/%s/aibrix-core-%s.yaml",
		p.aibrixVersion, p.aibrixVersion)
	dependencyURL := fmt.Sprintf("https://github.com/vllm-project/aibrix/releases/download/%s/aibrix-dependency-%s.yaml",
		p.aibrixVersion, p.aibrixVersion)

	_ = p.kubectlDelete(ctx, opts.KubeConfig, coreURL)
	_ = p.kubectlDelete(ctx, opts.KubeConfig, dependencyURL)

	return nil
}

func (p *Profile) waitForDeployment(ctx context.Context, opts *framework.SetupOptions, namespace, name string, timeout time.Duration) error {
	deployer := helm.NewDeployer(opts.KubeConfig, opts.Verbose)
	return deployer.WaitForDeployment(ctx, namespace, name, timeout)
}

func (p *Profile) kubectlApply(ctx context.Context, kubeConfig, manifest string) error {
	return p.runKubectl(ctx, kubeConfig, "apply", "--server-side", "-f", manifest)
}

func (p *Profile) kubectlDelete(ctx context.Context, kubeConfig, manifest string) error {
	return p.runKubectl(ctx, kubeConfig, "delete", "--ignore-not-found", "-f", manifest)
}

func (p *Profile) runKubectl(ctx context.Context, kubeConfig string, args ...string) error {
	args = append([]string{"--kubeconfig", kubeConfig}, args...)
	cmd := exec.CommandContext(ctx, "kubectl", args...)
	if p.verbose {
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
	}
	return cmd.Run()
}

func (p *Profile) log(format string, args ...interface{}) {
	if p.verbose {
		fmt.Printf("[AIBrix] "+format+"\n", args...)
	}
}
