//go:build integration

package cmd

import (
	"encoding/json"
	"os"
	"os/exec"
	"path/filepath"
	"slices"
	"strings"
	"testing"

	"github.com/tidbcloud/controlplane9/internal/dedicatedapi"
)

func TestIntegration_DedicatedPiTRRuntimeContract(t *testing.T) {
	repoRoot := filepath.Clean(filepath.Join("..", ".."))
	overlay := filepath.Join(repoRoot, "personas", "app-delivery", "envs", "dev-full", "dedicated-dataflow-svc")
	rendered, err := exec.Command("kubectl", "kustomize", overlay).CombinedOutput()
	if err != nil {
		t.Fatalf("render Dedicated Dataflow overlay: %v\n%s", err, rendered)
	}
	text := string(rendered)

	for _, expected := range []string{
		"env: CP9_SESSION_NAME",
		"backupInstanceTypes:",
		"restoreInstanceTypes:",
		"enableConfigCenter: true",
		"secretKey: CP9_ACCOUNT_CONFIG_SECRET",
		"name: AWS_REGION\n",
		"name: AWS_DEFAULT_REGION\n",
	} {
		if !strings.Contains(text, expected) {
			t.Errorf("rendered Dedicated Dataflow manifest missing %q", expected)
		}
	}
	// Dataflow decrypts the cluster credential that Global Control encrypted, so it
	// must reach the session CMK on real KMS. Any endpoint override sends it to the
	// stub instead, whose ciphertext the other side cannot read.
	if strings.Contains(text, "AWS_ENDPOINT_URL_KMS") {
		t.Error("rendered Dedicated Dataflow manifest overrides the KMS endpoint; it must use real KMS")
	}

	outputsPath := filepath.Join(t.TempDir(), "outputs.json")
	outputs := map[string]any{
		"account_config_secret": map[string]any{"value": map[string]any{"name": "cp9/test/account-config"}},
	}
	outputsData, err := json.Marshal(outputs)
	if err != nil {
		t.Fatal(err)
	}
	if err := os.WriteFile(outputsPath, outputsData, 0o600); err != nil {
		t.Fatal(err)
	}
	contextPath := filepath.Join(t.TempDir(), "context.json")
	contextData, err := json.Marshal(map[string]any{
		"session_name": "cp9-pitr-test",
		"target":       map[string]any{"account": "123456789012", "region": "us-east-1"},
		"terraform": map[string]any{
			"outputs_path": outputsPath,
			"session_product_pca": map[string]any{
				"arn":    "arn:aws:acm-pca:us-east-1:123456789012:certificate-authority/00000000-0000-4000-8000-000000000002",
				"region": "us-east-1",
			},
			"session_cmk": map[string]any{
				"arn":    "arn:aws:kms:us-east-1:123456789012:key/00000000-0000-4000-8000-000000000001",
				"region": "us-east-1",
			},
		},
	})
	if err != nil {
		t.Fatal(err)
	}
	if err := os.WriteFile(contextPath, contextData, 0o600); err != nil {
		t.Fatal(err)
	}
	script := filepath.Join(repoRoot, "personas", "dev-full", "runtime", "scripts", "apply-kustomize-images.sh")
	command := exec.Command(
		"sh", script, overlay, "cp9-global", "/dev/null", "unused",
		"cp9.local/dataflow-svc:placeholder=example.invalid/dataflow:test",
	)
	command.Env = append(os.Environ(),
		"CP9_RENDER_ONLY=true",
		"CP9_SESSION_CONTEXT="+contextPath,
	)
	patched, err := command.CombinedOutput()
	if err != nil {
		t.Fatalf("render session-patched Dedicated Dataflow overlay: %v\n%s", err, patched)
	}
	patchedText := string(patched)
	for _, expected := range []string{
		"env: cp9-pitr-test",
		"value: us-east-1",
		"secretKey: cp9/test/account-config",
		"arn:aws:kms:us-east-1:123456789012:key/",
		"arn:aws:acm-pca:us-east-1:123456789012:certificate-authority/",
	} {
		if !strings.Contains(patchedText, expected) {
			t.Errorf("session-patched Dedicated Dataflow manifest missing %q", expected)
		}
	}
	for _, placeholder := range []string{"CP9_SESSION_NAME", "CP9_RUNTIME_REGION", "CP9_ACCOUNT_CONFIG_SECRET"} {
		if strings.Contains(patchedText, placeholder) {
			t.Errorf("session-patched Dedicated Dataflow manifest still contains %q", placeholder)
		}
	}

	for _, proxyOverlay := range []string{
		"dedicated-cluster-control-server",
		"dedicated-cluster-controller",
		"regional-server",
	} {
		path := filepath.Join(repoRoot, "personas", "app-delivery", "envs", "dev-full", proxyOverlay)
		proxyRendered, renderErr := exec.Command("kubectl", "kustomize", path).CombinedOutput()
		if renderErr != nil {
			t.Fatalf("render %s overlay: %v\n%s", proxyOverlay, renderErr, proxyRendered)
		}
		proxyText := string(proxyRendered)
		for _, expected := range []string{"tidb-dev.us.auth0.com", "dev-proxy-service"} {
			if !strings.Contains(proxyText, expected) {
				t.Errorf("rendered %s manifest missing %q", proxyOverlay, expected)
			}
		}
		if proxyOverlay != "regional-server" && !strings.Contains(proxyText, "name: dedicated-proxy-auth0-client") {
			t.Errorf("rendered %s manifest does not use the Dedicated proxy credential Secret", proxyOverlay)
		}
	}

	metaDBOverlay := filepath.Join(repoRoot, "personas", "app-delivery", "envs", "dev-full", "premium-metadb")
	metaDBRendered, err := exec.Command("kubectl", "kustomize", metaDBOverlay).CombinedOutput()
	if err != nil {
		t.Fatalf("render Premium MetaDB overlay: %v\n%s", err, metaDBRendered)
	}
	metaDBText := string(metaDBRendered)
	for _, expected := range []string{
		"backup_cycle` enum('','daily','weekly')",
		"3014387,'DEDICATED_TIDB','gitops-v6.5.10-backup'",
		"3014386,'DEDICATED_TIDB','gitops-v6.5.10-restore'",
		"11756,'DEDICATED_TIDB','backup','v8.5.2'",
		"11758,'DEDICATED_TIDB','restore','v8.5.2'",
	} {
		if !strings.Contains(metaDBText, expected) {
			t.Errorf("rendered Premium MetaDB missing %q", expected)
		}
	}

	rbac := string(dedicatedapi.RenderBootstrapManifest("cp9-pitr-test"))
	for _, resource := range []string{
		"backups",
		"backups/status",
		"backups/finalizers",
		"restores",
		"restores/status",
		"restores/finalizers",
		"compactbackups",
		"compactbackups/status",
		"compactbackups/finalizers",
		"replays",
		"replays/status",
		"replays/finalizers",
		"upgradeoperations",
		"upgradeoperations/status",
		"upgradeoperations/finalizers",
	} {
		if !strings.Contains(rbac, resource) {
			t.Errorf("Dedicated cluster-control-server RBAC missing %q", resource)
		}
	}
}

func TestIntegration_DedicatedProxyAuthSecretApply(t *testing.T) {
	repoRoot := filepath.Clean(filepath.Join("..", ".."))
	realKubectl, err := exec.LookPath("kubectl")
	if err != nil {
		t.Fatal(err)
	}

	stubDir := t.TempDir()
	logPath := filepath.Join(stubDir, "kubectl.log")
	kubectlStub := `#!/bin/sh
if [ "$1" = "kustomize" ]; then
  exec "$REAL_KUBECTL" "$@"
fi
case " $* " in
  *" get configmap,secret -o json "*) printf '{"items":[]}\n'; exit 0 ;;
  *" get "*) exit 1 ;;
esac
printf '%s\n' "$*" >>"$STUB_LOG"
previous=
for argument in "$@"; do
  if [ "$previous" = "-f" ] && [ "$argument" != "-" ] && [ -f "$argument" ]; then
    cat "$argument" >>"$STUB_LOG"
  fi
  previous=$argument
done
cat >>"$STUB_LOG"
`
	if err := os.WriteFile(filepath.Join(stubDir, "kubectl"), []byte(kubectlStub), 0o755); err != nil {
		t.Fatal(err)
	}
	awsStub := `#!/bin/sh
cat <<'YAML'
proxy:
  auth0ClientID: test-client
  auth0ClientSecret: test-secret
YAML
`
	if err := os.WriteFile(filepath.Join(stubDir, "aws"), []byte(awsStub), 0o755); err != nil {
		t.Fatal(err)
	}

	outputsPath := filepath.Join(t.TempDir(), "outputs.json")
	outputsData, err := json.Marshal(map[string]any{
		"account_config_secret": map[string]any{"value": map[string]any{"name": "cp9/test/account-config"}},
	})
	if err != nil {
		t.Fatal(err)
	}
	if err := os.WriteFile(outputsPath, outputsData, 0o600); err != nil {
		t.Fatal(err)
	}
	contextPath := filepath.Join(t.TempDir(), "context.json")
	contextData, err := json.Marshal(map[string]any{
		"session_name":   "cp9-pitr-test",
		"ttl_expires_at": "2026-07-25T00:00:00Z",
		"session_tags": map[string]any{
			"cp9-owner": "david",
		},
		"host": map[string]any{
			"dedicated_api": map[string]any{"admin_kubeconfig_path": "/tmp/cp9-dedicated.kubeconfig"},
		},
		"target": map[string]any{
			"account": "123456789012",
			"region":  "us-west-2",
		},
		"terraform": map[string]any{
			"outputs_path": outputsPath,
			"iam": map[string]any{
				"role_arn": "arn:aws:iam::123456789012:role/cp9-pitr-test",
			},
		},
	})
	if err != nil {
		t.Fatal(err)
	}
	if err := os.WriteFile(contextPath, contextData, 0o600); err != nil {
		t.Fatal(err)
	}

	overlay := filepath.Join(repoRoot, "personas", "app-delivery", "envs", "dev-full", "dedicated-cluster-control-server")
	script := filepath.Join(repoRoot, "personas", "dev-full", "runtime", "scripts", "apply-kustomize-images.sh")
	command := exec.Command(
		"sh", script, overlay, "cp9-dedicated", "/dev/null", "unused",
		"cp9.local/cluster-control-server:placeholder=example.invalid/cluster-control-server:test",
	)
	command.Env = append(os.Environ(),
		"PATH="+stubDir+string(os.PathListSeparator)+os.Getenv("PATH"),
		"REAL_KUBECTL="+realKubectl,
		"STUB_LOG="+logPath,
		"CP9_AWS_RUNTIME_IDENTITY=ec2-instance-profile",
		"CP9_SESSION_CONTEXT="+contextPath,
	)
	output, err := command.CombinedOutput()
	if err != nil {
		t.Fatalf("apply Dedicated proxy Auth0 secret with stubs: %v\n%s", err, output)
	}

	logData, err := os.ReadFile(logPath)
	if err != nil {
		t.Fatal(err)
	}
	log := string(logData)
	for _, expected := range []string{
		"apply -f -",
		"cp9.tidbcloud.com/runtime-config-sha256",
		`"Auth0ClientID": "dGVzdC1jbGllbnQ="`,
		`"Auth0ClientSecret": "dGVzdC1zZWNyZXQ="`,
	} {
		if !strings.Contains(log, expected) {
			t.Errorf("stubbed Dedicated proxy apply log missing %q", expected)
		}
	}
	if strings.Contains(log, "rollout restart") {
		t.Errorf("Dedicated proxy apply must not restart a workload after materialization")
	}
}

func TestIntegration_DedicatedProductOpenAPIRuntime(t *testing.T) {
	repoRoot := filepath.Clean(filepath.Join("..", ".."))

	gatewayOverlay := filepath.Join(repoRoot, "personas", "app-delivery", "envs", "dev-full", "api-gateway-local-premium")
	gatewayRendered, err := exec.Command("kubectl", "kustomize", gatewayOverlay).CombinedOutput()
	if err != nil {
		t.Fatalf("render Premium API gateway overlay: %v\n%s", err, gatewayRendered)
	}
	for _, expected := range []string{
		"name: account-openapi-dbuser",
		"path: /~/v1beta1/clusters/[0-9]+/dbuser$",
		"name: account-openapi-http",
		"name: dedicated-openapi-changefeeds",
		"path: /v1beta1/changefeeds",
		"path: /v1beta1/changefeedRCUs",
		"name: dedicated-openapi-dataflow",
		"path: /api/v1beta",
		"name: dataflow-http",
		"openapi-plugin-digest-auth",
	} {
		if !strings.Contains(string(gatewayRendered), expected) {
			t.Errorf("rendered Premium API gateway missing Dedicated product route %q", expected)
		}
	}

	accountOverlay := filepath.Join(repoRoot, "personas", "app-delivery", "envs", "dev-full", "account-openapi")
	accountRendered, err := exec.Command("kubectl", "kustomize", accountOverlay).CombinedOutput()
	if err != nil {
		t.Fatalf("render Account OpenAPI overlay: %v\n%s", err, accountRendered)
	}
	for _, expected := range []string{
		"name: account-openapi",
		"command:\n        - account-openapi",
		"name: AWS_SHARED_CREDENTIALS_FILE",
		"name: account-openapi-http",
	} {
		if !strings.Contains(string(accountRendered), expected) {
			t.Errorf("rendered Account OpenAPI manifest missing %q", expected)
		}
	}

	accountConfig, err := os.ReadFile(filepath.Join(repoRoot, "personas", "app-delivery", "base", "premium-account-svc", "account-config", "config.yaml"))
	if err != nil {
		t.Fatal(err)
	}
	if !strings.Contains(string(accountConfig), "enableGatewayAuth: true") {
		t.Error("Dev Full Account OpenAPI must exercise gateway authentication and permission checks")
	}

	tiltfile, err := os.ReadFile(filepath.Join(repoRoot, "personas", "dev-full", "runtime", "example.Tiltfile"))
	if err != nil {
		t.Fatal(err)
	}
	for _, expected := range []string{
		"_account_workload_resource(\n    \"account-openapi\"",
		"../../app-delivery/envs/dev-full/account-openapi",
		"account_openapi_image",
	} {
		if !strings.Contains(string(tiltfile), expected) {
			t.Errorf("Dev Full Tiltfile missing Account OpenAPI runtime wiring %q", expected)
		}
	}

	contracts, err := os.ReadFile(filepath.Join(repoRoot, "internal", "sourcebacked", "contracts.json"))
	if err != nil {
		t.Fatal(err)
	}
	if !strings.Contains(string(contracts), `"name": "account-openapi"`) {
		t.Error("source-backed service registry must include Account OpenAPI")
	}

	accountBuild, err := os.ReadFile(filepath.Join(repoRoot, "personas", "dev-full", "build", "build-local-image.sh"))
	if err != nil {
		t.Fatal(err)
	}
	if !strings.Contains(string(accountBuild), "account|account-provider|account-acl|account-central|account-openapi") {
		t.Error("source image builder must support the Account OpenAPI binary")
	}
}

func TestIntegration_DedicatedO11yUsesDedicatedAPIServer(t *testing.T) {
	repoRoot := filepath.Clean(filepath.Join("..", ".."))
	overlay := filepath.Join(
		repoRoot,
		"personas",
		"app-delivery",
		"envs",
		"dev-full",
		"dedicated-o11y-agent-control-server",
	)
	rendered, err := exec.Command("kubectl", "kustomize", overlay).CombinedOutput()
	if err != nil {
		t.Fatalf("render Dedicated O11Y Agent Control overlay: %v\n%s", err, rendered)
	}
	virtualAPIRBACOverlay := filepath.Join(
		repoRoot,
		"personas",
		"app-delivery",
		"envs",
		"dev-full",
		"dedicated-cluster-control-server-support",
	)
	virtualAPIRBAC, err := exec.Command("kubectl", "kustomize", virtualAPIRBACOverlay).CombinedOutput()
	if err != nil {
		t.Fatalf("render Dedicated virtual API support overlay: %v\n%s", err, virtualAPIRBAC)
	}
	bootstrapScript, err := os.ReadFile(filepath.Join(
		repoRoot,
		"personas",
		"dev-full",
		"runtime",
		"scripts",
		"bootstrap-dedicated-api.sh",
	))
	if err != nil {
		t.Fatal(err)
	}
	contract := string(rendered) + string(bootstrapScript)
	for _, expected := range []string{
		"name: KUBECONFIG",
		"value: /var/run/cp9/dedicated-api/o11y-agent-control-server.kubeconfig",
		"secretName: dedicated-api-kubeconfigs",
		"key: o11y-agent-control-server.kubeconfig",
		`"$ticdc_crds" "$o11y_agent_crds"; do`,
		`"${dedicated_kubectl[@]}" apply --server-side --force-conflicts --recursive -f "$crd_directory"`,
		`--from-file=o11y-agent-control-server.kubeconfig=`,
	} {
		if !strings.Contains(contract, expected) {
			t.Errorf("Dedicated O11Y virtual API contract missing %q", expected)
		}
	}
	for _, expected := range []string{
		"name: cp9-dedicated-o11y-agent-control-server",
		"name: cp9:dedicated:o11y-agent-control-server",
		"autoscaling.aws.crossplane.io",
		"aws.crossplane.io",
		"cloud.pingcap.com",
		"dedicated.regionalserver.tidbcloud.com",
		"ec2.aws.upbound.io",
		"eks.aws.crossplane.io",
		"eks.aws.upbound.io",
		"autoscalinggroups",
		"clusters",
		"nodegroups",
		"providerconfigs",
		"vpcs",
		"vpcendpoints",
	} {
		if !strings.Contains(string(virtualAPIRBAC), expected) {
			t.Errorf("Dedicated O11Y virtual API RBAC missing %q", expected)
		}
	}
	if !slices.Contains(dedicatedapi.ComponentNames, "o11y-agent-control-server") {
		t.Errorf("Dedicated API component identities missing o11y-agent-control-server")
	}
	if strings.Contains(string(rendered), "--watch-namespace") {
		t.Errorf("Dedicated O11Y must see product VPCs outside the Agent namespace")
	}
}
