package cli

import (
	"context"
	cryptorand "crypto/rand"
	"encoding/hex"
	"errors"
	"flag"
	"fmt"
	"io"
	"path/filepath"
	"sort"
	"strings"
	"time"

	"github.com/codex-k8s/kodex/cmd/codex-bootstrap/internal/envfile"
	"github.com/codex-k8s/kodex/libs/go/k8s/clientcfg"
	"github.com/codex-k8s/kodex/libs/go/servicescfg"
	corev1 "k8s.io/api/core/v1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
)

const defaultSyncSecretsTimeout = 3 * time.Minute

const (
	defaultTelegramInteractionAdapterBaseURL = "http://kodex-telegram-interaction-adapter:8080"
	defaultTelegramInteractionAdapterTimeout = "10s"
	telegramInteractionAdapterSecretBytes    = 32
)

var syncSecretsRequiredKeys = []string{
	"KODEX_GITHUB_REPO",
	"KODEX_GIT_BOT_TOKEN",
	"KODEX_BOOTSTRAP_OWNER_EMAIL",
	"KODEX_GITHUB_OAUTH_CLIENT_ID",
	"KODEX_GITHUB_OAUTH_CLIENT_SECRET",
	"KODEX_PRODUCTION_DOMAIN",
	"KODEX_AI_DOMAIN",
}

func runSyncSecrets(args []string, stdout io.Writer, stderr io.Writer) int {
	var vars kvList
	fs := flag.NewFlagSet("sync-secrets", flag.ContinueOnError)
	fs.SetOutput(stderr)

	envPath := fs.String("env-file", "bootstrap/host/config.env", "Path to bootstrap env file")
	configPath := fs.String("config", "services.yaml", "Path to services.yaml (optional, for secret override policy)")
	kubeconfigPath := fs.String("kubeconfig", "", "Optional kubeconfig path")
	namespace := fs.String("namespace", "", "Override target namespace for secret sync")
	timeout := fs.Duration("timeout", defaultSyncSecretsTimeout, "Timeout for kubernetes operations")
	dryRun := fs.Bool("dry-run", false, "Print planned updates without writing to env file or kubernetes")
	fs.Var(&vars, "var", "Template variable in KEY=VALUE format (repeatable)")

	if err := fs.Parse(args); err != nil {
		return 2
	}
	if *timeout <= 0 {
		writef(stderr, "sync-secrets failed: --timeout must be positive\n")
		return 2
	}

	absEnv, err := filepath.Abs(*envPath)
	if err != nil {
		writef(stderr, "sync-secrets failed: resolve env-file path: %v\n", err)
		return 1
	}
	values, err := envfile.Load(absEnv)
	if err != nil {
		writef(stderr, "sync-secrets failed: load env-file: %v\n", err)
		return 1
	}
	for key, value := range vars.Map() {
		values[key] = value
	}

	applyGitHubSyncDefaults(values)
	applySyncSecretsDefaults(values)
	secretResolver, err := loadSecretResolver(*configPath, values)
	if err != nil {
		writef(stderr, "sync-secrets failed: load services config: %v\n", err)
		return 1
	}
	missing := missingRequiredKeys(values, syncSecretsRequiredKeys)
	if len(missing) > 0 {
		writef(stderr, "sync-secrets failed: missing required env keys: %s\n", strings.Join(missing, ", "))
		return 1
	}

	targetNamespace := strings.TrimSpace(*namespace)
	if targetNamespace == "" {
		targetNamespace = strings.TrimSpace(values["KODEX_PRODUCTION_NAMESPACE"])
	}
	if targetNamespace == "" {
		targetNamespace = "kodex-prod"
	}

	ctx, cancel := context.WithTimeout(context.Background(), *timeout)
	defer cancel()

	restConfig, err := clientcfg.BuildRESTConfig(strings.TrimSpace(*kubeconfigPath))
	if err != nil {
		writef(stderr, "sync-secrets failed: build kubernetes rest config: %v\n", err)
		return 1
	}
	clientset, err := kubernetes.NewForConfig(restConfig)
	if err != nil {
		writef(stderr, "sync-secrets failed: build kubernetes clientset: %v\n", err)
		return 1
	}

	existingPostgres, err := getSecretData(ctx, clientset, targetNamespace, "kodex-postgres")
	if err != nil {
		writef(stderr, "sync-secrets failed: load kodex-postgres: %v\n", err)
		return 1
	}
	existingRuntime, err := getSecretData(ctx, clientset, targetNamespace, "kodex-runtime")
	if err != nil {
		writef(stderr, "sync-secrets failed: load kodex-runtime: %v\n", err)
		return 1
	}
	existingOAuth, err := getSecretData(ctx, clientset, targetNamespace, "kodex-oauth2-proxy")
	if err != nil {
		writef(stderr, "sync-secrets failed: load kodex-oauth2-proxy: %v\n", err)
		return 1
	}
	existingRuntimeAI, err := getSecretData(ctx, clientset, targetNamespace, "kodex-runtime-ai")
	if err != nil {
		writef(stderr, "sync-secrets failed: load kodex-runtime-ai: %v\n", err)
		return 1
	}
	existingOAuthAI, err := getSecretData(ctx, clientset, targetNamespace, "kodex-oauth2-proxy-ai")
	if err != nil {
		writef(stderr, "sync-secrets failed: load kodex-oauth2-proxy-ai: %v\n", err)
		return 1
	}

	if err := hydrateValuesFromExistingSecrets(values, existingPostgres, existingRuntime, existingOAuth); err != nil {
		writef(stderr, "sync-secrets failed: hydrate values from existing secrets: %v\n", err)
		return 1
	}

	postgresSecret := buildPostgresSecretValues(values)
	runtimeSecret := buildRuntimeSecretValues(values)
	oauthSecret := buildOAuthSecretValues(values)

	aiRuntimeSecret := buildEnvScopedSecretValues(values, existingRuntimeAI, runtimeSecret, runtimeEnvironmentAI, secretResolver)
	aiOAuthSecret := buildEnvScopedSecretValues(values, existingOAuthAI, oauthSecret, runtimeEnvironmentAI, secretResolver)
	hydrateEnvOverrideValuesFromSecret(values, existingRuntimeAI, runtimeSecret, runtimeEnvironmentAI, secretResolver)

	if *dryRun {
		writef(stdout, "sync-secrets env-file=%s namespace=%s dry-run=true\n", absEnv, targetNamespace)
		writef(stdout, "resolved postgres keys=%d runtime keys=%d oauth keys=%d\n", len(postgresSecret), len(runtimeSecret), len(oauthSecret))
		writef(stdout, "resolved runtime-ai keys=%d oauth-ai keys=%d\n", len(aiRuntimeSecret), len(aiOAuthSecret))
		printResolvedSecretKeys(stdout, postgresSecret, runtimeSecret, oauthSecret)
		writeln(stdout, "runtime-ai secret keys:")
		printSortedKeys(stdout, aiRuntimeSecret)
		writeln(stdout, "oauth2-proxy-ai secret keys:")
		printSortedKeys(stdout, aiOAuthSecret)
		return 0
	}

	if err := upsertSecretData(ctx, clientset, targetNamespace, "kodex-postgres", postgresSecret); err != nil {
		writef(stderr, "sync-secrets failed: upsert kodex-postgres: %v\n", err)
		return 1
	}
	if err := upsertSecretData(ctx, clientset, targetNamespace, "kodex-runtime", runtimeSecret); err != nil {
		writef(stderr, "sync-secrets failed: upsert kodex-runtime: %v\n", err)
		return 1
	}
	if len(oauthSecret) > 0 {
		if err := upsertSecretData(ctx, clientset, targetNamespace, "kodex-oauth2-proxy", oauthSecret); err != nil {
			writef(stderr, "sync-secrets failed: upsert kodex-oauth2-proxy: %v\n", err)
			return 1
		}
	}
	if len(aiRuntimeSecret) > 0 {
		if err := upsertSecretData(ctx, clientset, targetNamespace, "kodex-runtime-ai", aiRuntimeSecret); err != nil {
			writef(stderr, "sync-secrets failed: upsert kodex-runtime-ai: %v\n", err)
			return 1
		}
	}
	if len(aiOAuthSecret) > 0 {
		if err := upsertSecretData(ctx, clientset, targetNamespace, "kodex-oauth2-proxy-ai", aiOAuthSecret); err != nil {
			writef(stderr, "sync-secrets failed: upsert kodex-oauth2-proxy-ai: %v\n", err)
			return 1
		}
	}

	if err := envfile.Upsert(absEnv, values); err != nil {
		writef(stderr, "sync-secrets failed: persist env-file: %v\n", err)
		return 1
	}

	writef(stdout, "sync-secrets env-file=%s namespace=%s\n", absEnv, targetNamespace)
	writef(stdout, "upserted postgres keys=%d runtime keys=%d oauth keys=%d\n", len(postgresSecret), len(runtimeSecret), len(oauthSecret))
	return 0
}

func applySyncSecretsDefaults(values map[string]string) {
	setEnvDefault(values, "KODEX_POSTGRES_DB", "kodex")
	setEnvDefault(values, "KODEX_POSTGRES_USER", "kodex")
	setEnvDefault(values, "KODEX_LEARNING_MODE_DEFAULT", "true")
	setEnvDefault(values, "KODEX_JWT_TTL", "15m")
	setEnvDefault(values, "KODEX_MCP_TOKEN_TTL", "24h")
	setEnvDefault(values, "KODEX_RUN_HEAVY_FIELDS_RETENTION_DAYS", "7")
	setEnvDefault(values, "KODEX_RUN_AGENT_LOGS_RETENTION_DAYS", strings.TrimSpace(values["KODEX_RUN_HEAVY_FIELDS_RETENTION_DAYS"]))
	setEnvDefault(values, "KODEX_GIT_BOT_USERNAME", "codex-bot")
	setEnvDefault(values, "KODEX_GIT_BOT_MAIL", "codex-bot@kodex.local")
	setEnvDefault(values, "KODEX_PROJECT_DB_ADMIN_HOST", "postgres")
	setEnvDefault(values, "KODEX_PROJECT_DB_ADMIN_PORT", "5432")
	setEnvDefault(values, "KODEX_PROJECT_DB_ADMIN_SSLMODE", "disable")
	setEnvDefault(values, "KODEX_PROJECT_DB_ADMIN_DATABASE", "postgres")
	setEnvDefault(values, "KODEX_PROJECT_DB_LIFECYCLE_ALLOWED_ENVS", "dev,production,prod")
	setEnvDefault(values, "KODEX_GITHUB_WEBHOOK_URL", resolveWebhookURL(values))
	setEnvDefault(values, "KODEX_GITHUB_WEBHOOK_EVENTS", defaultGitHubWebhookEvents)
	setEnvDefault(values, "KODEX_INTERNAL_REGISTRY_STORAGE_SIZE", "20Gi")
	setEnvDefault(values, "KODEX_TELEGRAM_INTERACTION_ADAPTER_TIMEOUT", defaultTelegramInteractionAdapterTimeout)
}

func hydrateValuesFromExistingSecrets(values map[string]string, existingPostgres map[string][]byte, existingRuntime map[string][]byte, existingOAuth map[string][]byte) error {
	transferFromSecret(values, existingPostgres, []string{
		"KODEX_POSTGRES_DB",
		"KODEX_POSTGRES_USER",
		"KODEX_POSTGRES_PASSWORD",
	})
	transferFromSecret(values, existingRuntime, []string{
		"KODEX_GITHUB_PAT",
		"KODEX_GITHUB_REPO",
		"KODEX_FIRST_PROJECT_GITHUB_REPO",
		"KODEX_OPENAI_API_KEY",
		"KODEX_PROJECT_DB_ADMIN_HOST",
		"KODEX_PROJECT_DB_ADMIN_PORT",
		"KODEX_PROJECT_DB_ADMIN_USER",
		"KODEX_PROJECT_DB_ADMIN_PASSWORD",
		"KODEX_PROJECT_DB_ADMIN_SSLMODE",
		"KODEX_PROJECT_DB_ADMIN_DATABASE",
		"KODEX_PROJECT_DB_LIFECYCLE_ALLOWED_ENVS",
		"KODEX_GIT_BOT_TOKEN",
		"KODEX_GIT_BOT_USERNAME",
		"KODEX_GIT_BOT_MAIL",
		"KODEX_TELEGRAM_BOT_TOKEN",
		"KODEX_TELEGRAM_CHAT_ID",
		"KODEX_TELEGRAM_INTERACTION_ADAPTER_BASE_URL",
		"KODEX_TELEGRAM_INTERACTION_ADAPTER_BEARER_TOKEN",
		"KODEX_TELEGRAM_INTERACTION_ADAPTER_WEBHOOK_SECRET",
		"KODEX_TELEGRAM_INTERACTION_ADAPTER_TIMEOUT",
		"KODEX_TELEGRAM_INTERACTION_ADAPTER_RECIPIENT_BINDINGS_JSON",
		"KODEX_CONTEXT7_API_KEY",
		"KODEX_APP_SECRET_KEY",
		"KODEX_TOKEN_ENCRYPTION_KEY",
		"KODEX_MCP_TOKEN_SIGNING_KEY",
		"KODEX_MCP_TOKEN_TTL",
		"KODEX_RUN_HEAVY_FIELDS_RETENTION_DAYS",
		"KODEX_RUN_AGENT_LOGS_RETENTION_DAYS",
		"KODEX_LEARNING_MODE_DEFAULT",
		"KODEX_GITHUB_WEBHOOK_SECRET",
		"KODEX_GITHUB_WEBHOOK_URL",
		"KODEX_GITHUB_WEBHOOK_EVENTS",
		"KODEX_PRODUCTION_DOMAIN",
		"KODEX_AI_DOMAIN",
		"KODEX_PUBLIC_BASE_URL",
		"KODEX_INTERACTION_CALLBACK_BASE_URL",
		"KODEX_BOOTSTRAP_OWNER_EMAIL",
		"KODEX_BOOTSTRAP_ALLOWED_EMAILS",
		"KODEX_BOOTSTRAP_PLATFORM_ADMIN_EMAILS",
		"KODEX_GITHUB_OAUTH_CLIENT_ID",
		"KODEX_GITHUB_OAUTH_CLIENT_SECRET",
		"KODEX_JWT_SIGNING_KEY",
		"KODEX_JWT_TTL",
		"KODEX_INTERNAL_REGISTRY_SERVICE",
		"KODEX_INTERNAL_REGISTRY_PORT",
		"KODEX_INTERNAL_REGISTRY_HOST",
		"KODEX_INTERNAL_REGISTRY_STORAGE_SIZE",
		"KODEX_VITE_DEV_UPSTREAM",
	})
	transferFromSecret(values, existingOAuth, []string{
		"OAUTH2_PROXY_COOKIE_SECRET",
	})

	var err error
	values["KODEX_POSTGRES_PASSWORD"], err = valueOrRandomHex(values, "KODEX_POSTGRES_PASSWORD", 24)
	if err != nil {
		return err
	}
	values["KODEX_APP_SECRET_KEY"], err = valueOrRandomHex(values, "KODEX_APP_SECRET_KEY", 32)
	if err != nil {
		return err
	}
	values["KODEX_TOKEN_ENCRYPTION_KEY"], err = valueOrRandomHex(values, "KODEX_TOKEN_ENCRYPTION_KEY", 32)
	if err != nil {
		return err
	}
	values["KODEX_GITHUB_WEBHOOK_SECRET"], err = valueOrRandomHex(values, "KODEX_GITHUB_WEBHOOK_SECRET", 32)
	if err != nil {
		return err
	}
	values["KODEX_JWT_SIGNING_KEY"], err = valueOrRandomHex(values, "KODEX_JWT_SIGNING_KEY", 32)
	if err != nil {
		return err
	}
	values["KODEX_TELEGRAM_INTERACTION_ADAPTER_BEARER_TOKEN"], err = valueOrRandomHex(values, "KODEX_TELEGRAM_INTERACTION_ADAPTER_BEARER_TOKEN", telegramInteractionAdapterSecretBytes)
	if err != nil {
		return err
	}
	values["KODEX_TELEGRAM_INTERACTION_ADAPTER_WEBHOOK_SECRET"], err = valueOrRandomHex(values, "KODEX_TELEGRAM_INTERACTION_ADAPTER_WEBHOOK_SECRET", telegramInteractionAdapterSecretBytes)
	if err != nil {
		return err
	}
	values["OAUTH2_PROXY_COOKIE_SECRET"], err = valueOrRandomHex(values, "OAUTH2_PROXY_COOKIE_SECRET", 16)
	if err != nil {
		return err
	}

	if strings.TrimSpace(values["KODEX_MCP_TOKEN_SIGNING_KEY"]) == "" {
		values["KODEX_MCP_TOKEN_SIGNING_KEY"] = strings.TrimSpace(values["KODEX_TOKEN_ENCRYPTION_KEY"])
	}
	if strings.TrimSpace(values["KODEX_FIRST_PROJECT_GITHUB_REPO"]) == "" {
		values["KODEX_FIRST_PROJECT_GITHUB_REPO"] = strings.TrimSpace(values["KODEX_GITHUB_REPO"])
	}
	if strings.TrimSpace(values["KODEX_PUBLIC_BASE_URL"]) == "" {
		domain := strings.TrimSpace(values["KODEX_PRODUCTION_DOMAIN"])
		if domain != "" {
			values["KODEX_PUBLIC_BASE_URL"] = "https://" + domain
		}
	}
	if strings.TrimSpace(values["KODEX_PROJECT_DB_ADMIN_USER"]) == "" {
		values["KODEX_PROJECT_DB_ADMIN_USER"] = strings.TrimSpace(values["KODEX_POSTGRES_USER"])
	}
	if strings.TrimSpace(values["KODEX_PROJECT_DB_ADMIN_PASSWORD"]) == "" {
		values["KODEX_PROJECT_DB_ADMIN_PASSWORD"] = strings.TrimSpace(values["KODEX_POSTGRES_PASSWORD"])
	}
	applyTelegramInteractionDefaults(values)
	return nil
}

func buildPostgresSecretValues(values map[string]string) map[string]string {
	return compactStringMap(map[string]string{
		"KODEX_POSTGRES_DB":       strings.TrimSpace(values["KODEX_POSTGRES_DB"]),
		"KODEX_POSTGRES_USER":     strings.TrimSpace(values["KODEX_POSTGRES_USER"]),
		"KODEX_POSTGRES_PASSWORD": strings.TrimSpace(values["KODEX_POSTGRES_PASSWORD"]),
	})
}

func buildRuntimeSecretValues(values map[string]string) map[string]string {
	return compactStringMap(map[string]string{
		"KODEX_INTERNAL_REGISTRY_SERVICE":                            strings.TrimSpace(values["KODEX_INTERNAL_REGISTRY_SERVICE"]),
		"KODEX_INTERNAL_REGISTRY_PORT":                               strings.TrimSpace(values["KODEX_INTERNAL_REGISTRY_PORT"]),
		"KODEX_INTERNAL_REGISTRY_HOST":                               strings.TrimSpace(values["KODEX_INTERNAL_REGISTRY_HOST"]),
		"KODEX_INTERNAL_REGISTRY_STORAGE_SIZE":                       strings.TrimSpace(values["KODEX_INTERNAL_REGISTRY_STORAGE_SIZE"]),
		"KODEX_K8S_API_CIDR":                                         strings.TrimSpace(values["KODEX_K8S_API_CIDR"]),
		"KODEX_K8S_API_PORT":                                         strings.TrimSpace(values["KODEX_K8S_API_PORT"]),
		"KODEX_GITHUB_PAT":                                           strings.TrimSpace(values["KODEX_GITHUB_PAT"]),
		"KODEX_GITHUB_REPO":                                          strings.TrimSpace(values["KODEX_GITHUB_REPO"]),
		"KODEX_FIRST_PROJECT_GITHUB_REPO":                            strings.TrimSpace(values["KODEX_FIRST_PROJECT_GITHUB_REPO"]),
		"KODEX_OPENAI_API_KEY":                                       strings.TrimSpace(values["KODEX_OPENAI_API_KEY"]),
		"KODEX_PROJECT_DB_ADMIN_HOST":                                strings.TrimSpace(values["KODEX_PROJECT_DB_ADMIN_HOST"]),
		"KODEX_PROJECT_DB_ADMIN_PORT":                                strings.TrimSpace(values["KODEX_PROJECT_DB_ADMIN_PORT"]),
		"KODEX_PROJECT_DB_ADMIN_USER":                                strings.TrimSpace(values["KODEX_PROJECT_DB_ADMIN_USER"]),
		"KODEX_PROJECT_DB_ADMIN_PASSWORD":                            strings.TrimSpace(values["KODEX_PROJECT_DB_ADMIN_PASSWORD"]),
		"KODEX_PROJECT_DB_ADMIN_SSLMODE":                             strings.TrimSpace(values["KODEX_PROJECT_DB_ADMIN_SSLMODE"]),
		"KODEX_PROJECT_DB_ADMIN_DATABASE":                            strings.TrimSpace(values["KODEX_PROJECT_DB_ADMIN_DATABASE"]),
		"KODEX_PROJECT_DB_LIFECYCLE_ALLOWED_ENVS":                    strings.TrimSpace(values["KODEX_PROJECT_DB_LIFECYCLE_ALLOWED_ENVS"]),
		"KODEX_GIT_BOT_TOKEN":                                        strings.TrimSpace(values["KODEX_GIT_BOT_TOKEN"]),
		"KODEX_GIT_BOT_USERNAME":                                     strings.TrimSpace(values["KODEX_GIT_BOT_USERNAME"]),
		"KODEX_GIT_BOT_MAIL":                                         strings.TrimSpace(values["KODEX_GIT_BOT_MAIL"]),
		"KODEX_TELEGRAM_BOT_TOKEN":                                   strings.TrimSpace(values["KODEX_TELEGRAM_BOT_TOKEN"]),
		"KODEX_TELEGRAM_CHAT_ID":                                     strings.TrimSpace(values["KODEX_TELEGRAM_CHAT_ID"]),
		"KODEX_TELEGRAM_INTERACTION_ADAPTER_BASE_URL":                strings.TrimSpace(values["KODEX_TELEGRAM_INTERACTION_ADAPTER_BASE_URL"]),
		"KODEX_TELEGRAM_INTERACTION_ADAPTER_BEARER_TOKEN":            strings.TrimSpace(values["KODEX_TELEGRAM_INTERACTION_ADAPTER_BEARER_TOKEN"]),
		"KODEX_TELEGRAM_INTERACTION_ADAPTER_WEBHOOK_SECRET":          strings.TrimSpace(values["KODEX_TELEGRAM_INTERACTION_ADAPTER_WEBHOOK_SECRET"]),
		"KODEX_TELEGRAM_INTERACTION_ADAPTER_TIMEOUT":                 strings.TrimSpace(values["KODEX_TELEGRAM_INTERACTION_ADAPTER_TIMEOUT"]),
		"KODEX_TELEGRAM_INTERACTION_ADAPTER_RECIPIENT_BINDINGS_JSON": strings.TrimSpace(values["KODEX_TELEGRAM_INTERACTION_ADAPTER_RECIPIENT_BINDINGS_JSON"]),
		"KODEX_CONTEXT7_API_KEY":                                     strings.TrimSpace(values["KODEX_CONTEXT7_API_KEY"]),
		"KODEX_APP_SECRET_KEY":                                       strings.TrimSpace(values["KODEX_APP_SECRET_KEY"]),
		"KODEX_TOKEN_ENCRYPTION_KEY":                                 strings.TrimSpace(values["KODEX_TOKEN_ENCRYPTION_KEY"]),
		"KODEX_MCP_TOKEN_SIGNING_KEY":                                strings.TrimSpace(values["KODEX_MCP_TOKEN_SIGNING_KEY"]),
		"KODEX_MCP_TOKEN_TTL":                                        strings.TrimSpace(values["KODEX_MCP_TOKEN_TTL"]),
		"KODEX_RUN_HEAVY_FIELDS_RETENTION_DAYS":                      strings.TrimSpace(values["KODEX_RUN_HEAVY_FIELDS_RETENTION_DAYS"]),
		"KODEX_RUN_AGENT_LOGS_RETENTION_DAYS":                        strings.TrimSpace(values["KODEX_RUN_AGENT_LOGS_RETENTION_DAYS"]),
		"KODEX_LEARNING_MODE_DEFAULT":                                strings.TrimSpace(values["KODEX_LEARNING_MODE_DEFAULT"]),
		"KODEX_GITHUB_WEBHOOK_SECRET":                                strings.TrimSpace(values["KODEX_GITHUB_WEBHOOK_SECRET"]),
		"KODEX_GITHUB_WEBHOOK_URL":                                   strings.TrimSpace(values["KODEX_GITHUB_WEBHOOK_URL"]),
		"KODEX_GITHUB_WEBHOOK_EVENTS":                                strings.TrimSpace(values["KODEX_GITHUB_WEBHOOK_EVENTS"]),
		"KODEX_PRODUCTION_DOMAIN":                                    strings.TrimSpace(values["KODEX_PRODUCTION_DOMAIN"]),
		"KODEX_AI_DOMAIN":                                            strings.TrimSpace(values["KODEX_AI_DOMAIN"]),
		"KODEX_PUBLIC_BASE_URL":                                      strings.TrimSpace(values["KODEX_PUBLIC_BASE_URL"]),
		"KODEX_INTERACTION_CALLBACK_BASE_URL":                        strings.TrimSpace(values["KODEX_INTERACTION_CALLBACK_BASE_URL"]),
		"KODEX_BOOTSTRAP_OWNER_EMAIL":                                strings.TrimSpace(values["KODEX_BOOTSTRAP_OWNER_EMAIL"]),
		"KODEX_BOOTSTRAP_ALLOWED_EMAILS":                             strings.TrimSpace(values["KODEX_BOOTSTRAP_ALLOWED_EMAILS"]),
		"KODEX_BOOTSTRAP_PLATFORM_ADMIN_EMAILS":                      strings.TrimSpace(values["KODEX_BOOTSTRAP_PLATFORM_ADMIN_EMAILS"]),
		"KODEX_GITHUB_OAUTH_CLIENT_ID":                               strings.TrimSpace(values["KODEX_GITHUB_OAUTH_CLIENT_ID"]),
		"KODEX_GITHUB_OAUTH_CLIENT_SECRET":                           strings.TrimSpace(values["KODEX_GITHUB_OAUTH_CLIENT_SECRET"]),
		"KODEX_JWT_SIGNING_KEY":                                      strings.TrimSpace(values["KODEX_JWT_SIGNING_KEY"]),
		"KODEX_JWT_TTL":                                              strings.TrimSpace(values["KODEX_JWT_TTL"]),
		"KODEX_VITE_DEV_UPSTREAM":                                    strings.TrimSpace(values["KODEX_VITE_DEV_UPSTREAM"]),
	})
}

func applyTelegramInteractionDefaults(values map[string]string) {
	if strings.TrimSpace(values["KODEX_TELEGRAM_INTERACTION_ADAPTER_BASE_URL"]) == "" {
		if strings.TrimSpace(values["KODEX_TELEGRAM_BOT_TOKEN"]) != "" || strings.TrimSpace(values["KODEX_TELEGRAM_INTERACTION_ADAPTER_RECIPIENT_BINDINGS_JSON"]) != "" {
			values["KODEX_TELEGRAM_INTERACTION_ADAPTER_BASE_URL"] = defaultTelegramInteractionAdapterBaseURL
		}
	}
	if strings.TrimSpace(values["KODEX_TELEGRAM_INTERACTION_ADAPTER_TIMEOUT"]) == "" {
		values["KODEX_TELEGRAM_INTERACTION_ADAPTER_TIMEOUT"] = defaultTelegramInteractionAdapterTimeout
	}
}

func buildOAuthSecretValues(values map[string]string) map[string]string {
	return compactStringMap(map[string]string{
		"OAUTH2_PROXY_CLIENT_ID":     strings.TrimSpace(values["KODEX_GITHUB_OAUTH_CLIENT_ID"]),
		"OAUTH2_PROXY_CLIENT_SECRET": strings.TrimSpace(values["KODEX_GITHUB_OAUTH_CLIENT_SECRET"]),
		"OAUTH2_PROXY_COOKIE_SECRET": strings.TrimSpace(values["OAUTH2_PROXY_COOKIE_SECRET"]),
	})
}

func transferFromSecret(values map[string]string, secret map[string][]byte, keys []string) {
	if len(secret) == 0 || len(keys) == 0 {
		return
	}
	for _, key := range keys {
		key = strings.TrimSpace(key)
		if key == "" || strings.TrimSpace(values[key]) != "" {
			continue
		}
		raw, ok := secret[key]
		if !ok {
			continue
		}
		value := strings.TrimSpace(string(raw))
		if value == "" {
			continue
		}
		values[key] = value
	}
}

func compactStringMap(input map[string]string) map[string]string {
	if len(input) == 0 {
		return map[string]string{}
	}
	out := make(map[string]string, len(input))
	for key, value := range input {
		key = strings.TrimSpace(key)
		value = strings.TrimSpace(value)
		if key == "" || value == "" {
			continue
		}
		out[key] = value
	}
	return out
}

func printResolvedSecretKeys(out io.Writer, postgres map[string]string, runtime map[string]string, oauth map[string]string) {
	writeln(out, "postgres secret keys:")
	printSortedKeys(out, postgres)
	writeln(out, "runtime secret keys:")
	printSortedKeys(out, runtime)
	writeln(out, "oauth2-proxy secret keys:")
	printSortedKeys(out, oauth)
}

func printSortedKeys(out io.Writer, values map[string]string) {
	if len(values) == 0 {
		writeln(out, "  - <none>")
		return
	}
	keys := make([]string, 0, len(values))
	for key := range values {
		keys = append(keys, key)
	}
	sort.Strings(keys)
	for _, key := range keys {
		writef(out, "  - %s\n", key)
	}
}

func getSecretData(ctx context.Context, clientset kubernetes.Interface, namespace string, name string) (map[string][]byte, error) {
	secret, err := clientset.CoreV1().Secrets(namespace).Get(ctx, strings.TrimSpace(name), metav1.GetOptions{})
	if err != nil {
		if apierrors.IsNotFound(err) {
			return map[string][]byte{}, nil
		}
		return nil, err
	}
	out := make(map[string][]byte, len(secret.Data))
	for key, value := range secret.Data {
		out[key] = append([]byte(nil), value...)
	}
	return out, nil
}

func upsertSecretData(ctx context.Context, clientset kubernetes.Interface, namespace string, name string, data map[string]string) error {
	targetNamespace := strings.TrimSpace(namespace)
	targetName := strings.TrimSpace(name)
	if targetNamespace == "" || targetName == "" {
		return fmt.Errorf("namespace and secret name are required")
	}

	existing, err := clientset.CoreV1().Secrets(targetNamespace).Get(ctx, targetName, metav1.GetOptions{})
	if err != nil && !apierrors.IsNotFound(err) {
		return err
	}

	merged := make(map[string][]byte)
	if existing != nil && existing.Data != nil {
		for key, value := range existing.Data {
			merged[key] = append([]byte(nil), value...)
		}
	}
	for key, value := range data {
		trimmedKey := strings.TrimSpace(key)
		trimmedValue := strings.TrimSpace(value)
		if trimmedKey == "" || trimmedValue == "" {
			continue
		}
		merged[trimmedKey] = []byte(trimmedValue)
	}
	if len(merged) == 0 {
		return nil
	}

	if apierrors.IsNotFound(err) {
		_, createErr := clientset.CoreV1().Secrets(targetNamespace).Create(ctx, &corev1.Secret{
			ObjectMeta: metav1.ObjectMeta{Name: targetName},
			Type:       corev1.SecretTypeOpaque,
			Data:       merged,
		}, metav1.CreateOptions{})
		return createErr
	}

	existing.Data = merged
	if existing.Type == "" {
		existing.Type = corev1.SecretTypeOpaque
	}
	_, updateErr := clientset.CoreV1().Secrets(targetNamespace).Update(ctx, existing, metav1.UpdateOptions{})
	return updateErr
}

func valueOrRandomHex(values map[string]string, key string, numBytes int) (string, error) {
	key = strings.TrimSpace(key)
	if key == "" {
		return "", errors.New("key is required")
	}
	value := strings.TrimSpace(values[key])
	if value != "" {
		return value, nil
	}
	generated, err := randomHexString(numBytes)
	if err != nil {
		return "", err
	}
	values[key] = generated
	return generated, nil
}

func randomHexString(numBytes int) (string, error) {
	if numBytes <= 0 {
		numBytes = 16
	}
	raw := make([]byte, numBytes)
	if _, err := cryptorand.Read(raw); err != nil {
		return "", err
	}
	return hex.EncodeToString(raw), nil
}

func buildEnvScopedSecretValues(values map[string]string, existing map[string][]byte, production map[string]string, envName string, resolver servicescfg.SecretResolver) map[string]string {
	out := make(map[string]string, len(production))
	for key, raw := range existing {
		trimmedKey := strings.TrimSpace(key)
		trimmedValue := strings.TrimSpace(string(raw))
		if trimmedKey == "" || trimmedValue == "" {
			continue
		}
		out[trimmedKey] = trimmedValue
	}

	for key, value := range production {
		trimmedKey := strings.TrimSpace(key)
		trimmedValue := strings.TrimSpace(value)
		if trimmedKey == "" || trimmedValue == "" {
			continue
		}
		if strings.TrimSpace(out[trimmedKey]) != "" {
			continue
		}
		out[trimmedKey] = trimmedValue
	}

	for key := range production {
		trimmedKey := strings.TrimSpace(key)
		if trimmedKey == "" {
			continue
		}
		overrideKey, ok := resolver.ResolveOverrideKey(envName, trimmedKey)
		if !ok {
			continue
		}
		if overrideValue := strings.TrimSpace(values[overrideKey]); overrideValue != "" {
			out[trimmedKey] = overrideValue
		}
	}

	return compactStringMap(out)
}

func hydrateEnvOverrideValuesFromSecret(values map[string]string, existing map[string][]byte, production map[string]string, envName string, resolver servicescfg.SecretResolver) {
	if len(existing) == 0 || len(production) == 0 || len(values) == 0 {
		return
	}

	for key, prodValue := range production {
		trimmedKey := strings.TrimSpace(key)
		if trimmedKey == "" {
			continue
		}
		overrideKey, ok := resolver.ResolveOverrideKey(envName, trimmedKey)
		if !ok {
			continue
		}
		if strings.TrimSpace(values[overrideKey]) != "" {
			continue
		}
		raw, ok := existing[trimmedKey]
		if !ok {
			continue
		}
		secretValue := strings.TrimSpace(string(raw))
		if secretValue == "" {
			continue
		}
		if strings.TrimSpace(prodValue) == secretValue {
			continue
		}
		values[overrideKey] = secretValue
	}
}
