package app

import (
	"testing"
	"time"

	outboxlib "github.com/codex-k8s/kodex/libs/go/outbox"
)

func TestLoadConfigRequiresDatabaseDSN(t *testing.T) {
	t.Setenv("KODEX_RUNTIME_MANAGER_DATABASE_DSN", "")

	if _, err := LoadConfig(); err == nil {
		t.Fatal("LoadConfig() err = nil, want required database DSN error")
	}
}

func TestLoadConfigAllowsMissingGRPCAuthTokenWhenAuthDisabled(t *testing.T) {
	t.Setenv("KODEX_RUNTIME_MANAGER_DATABASE_DSN", "postgres://postgres:5432/kodex_runtime_manager?sslmode=disable")
	t.Setenv("KODEX_RUNTIME_MANAGER_GRPC_AUTH_REQUIRED", "false")
	t.Setenv("KODEX_RUNTIME_MANAGER_GRPC_AUTH_TOKEN", "")
	t.Setenv("KODEX_RUNTIME_MANAGER_OUTBOX_DISPATCH_ENABLED", "false")
	t.Setenv("KODEX_RUNTIME_MANAGER_ACCESS_CHECK_ENABLED", "false")
	t.Setenv("KODEX_RUNTIME_MANAGER_FLEET_MANAGER_GRPC_AUTH_TOKEN", "fleet-token")

	cfg, err := LoadConfig()
	if err != nil {
		t.Fatalf("LoadConfig(): %v", err)
	}
	if cfg.GRPC.AuthRequired {
		t.Fatal("GRPC.AuthRequired = true, want false")
	}
}

func TestDatabasePoolSettingsIncludesRetryConfig(t *testing.T) {
	t.Parallel()

	cfg := validConfig()

	settings := cfg.DatabasePoolSettings()
	if settings.ConnectRetryMaxAttempts != cfg.Database.Retry.MaxAttempts {
		t.Fatalf("ConnectRetryMaxAttempts = %d, want %d", settings.ConnectRetryMaxAttempts, cfg.Database.Retry.MaxAttempts)
	}
	if settings.ConnectRetryInitialDelay != cfg.Database.Retry.Initial {
		t.Fatalf("ConnectRetryInitialDelay = %s, want %s", settings.ConnectRetryInitialDelay, cfg.Database.Retry.Initial)
	}
	if settings.ConnectRetryMaxDelay != cfg.Database.Retry.Max {
		t.Fatalf("ConnectRetryMaxDelay = %s, want %s", settings.ConnectRetryMaxDelay, cfg.Database.Retry.Max)
	}
	if settings.ConnectRetryJitterRatio != cfg.Database.Retry.JitterRatio {
		t.Fatalf("ConnectRetryJitterRatio = %f, want %f", settings.ConnectRetryJitterRatio, cfg.Database.Retry.JitterRatio)
	}
}

func TestValidateRequiresEventLogDSNForPostgresPublisher(t *testing.T) {
	t.Parallel()

	cfg := validConfig()
	cfg.EventLogDatabase.DSN = ""

	if err := cfg.Validate(); err == nil {
		t.Fatal("Validate() err = nil, want missing event-log DSN error")
	}
}

func TestValidateRequiresAccessTokenWhenChecksEnabled(t *testing.T) {
	t.Parallel()

	cfg := validConfig()
	cfg.Access.AccessManagerAuthToken = ""

	if err := cfg.Validate(); err == nil {
		t.Fatal("Validate() err = nil, want missing access-manager token error")
	}
}

func TestValidateRequiresFleetToken(t *testing.T) {
	t.Parallel()

	cfg := validConfig()
	cfg.Fleet.FleetManagerAuthToken = ""

	if err := cfg.Validate(); err == nil {
		t.Fatal("Validate() err = nil, want missing fleet-manager token error")
	}
}

func TestValidateRequiresKubernetesExecutorDefaultsWhenEnabled(t *testing.T) {
	t.Parallel()

	cfg := validConfig()
	cfg.KubernetesWorker.Enabled = true

	if err := cfg.Validate(); err == nil {
		t.Fatal("Validate() err = nil, want missing Kubernetes executor defaults error")
	}

	cfg.KubernetesWorker.DefaultNamespace = "runtime-jobs"
	cfg.KubernetesWorker.DefaultImage = "busybox:1.36"
	cfg.KubernetesWorker.DeployServiceAccount = "runtime-deployer"
	if err := cfg.Validate(); err != nil {
		t.Fatalf("Validate() with executor defaults: %v", err)
	}
}

func TestValidateRequiresKubernetesExecutorAgentManagerReporterWhenEnabled(t *testing.T) {
	t.Parallel()

	cfg := validConfig()
	cfg.KubernetesWorker.Enabled = true
	cfg.KubernetesWorker.DefaultNamespace = "runtime-jobs"
	cfg.KubernetesWorker.DefaultImage = "busybox:1.36"
	cfg.KubernetesWorker.AgentManagerSecretKey = ""

	if err := cfg.Validate(); err == nil {
		t.Fatal("Validate() err = nil, want missing agent-manager reporter secret ref error")
	}
}

func TestSlotServiceConfigIncludesSlotDefaults(t *testing.T) {
	t.Parallel()

	cfg := validConfig()

	slotConfig, err := cfg.SlotServiceConfig()
	if err != nil {
		t.Fatalf("SlotServiceConfig(): %v", err)
	}
	if slotConfig.DefaultFleetScopeID.String() != cfg.Slot.DefaultFleetScopeID {
		t.Fatalf("DefaultFleetScopeID = %s, want %s", slotConfig.DefaultFleetScopeID, cfg.Slot.DefaultFleetScopeID)
	}
	if slotConfig.DefaultClusterID.String() != cfg.Slot.DefaultClusterID {
		t.Fatalf("DefaultClusterID = %s, want %s", slotConfig.DefaultClusterID, cfg.Slot.DefaultClusterID)
	}
	if slotConfig.NamespacePrefix != cfg.Slot.NamespacePrefix {
		t.Fatalf("NamespacePrefix = %s, want %s", slotConfig.NamespacePrefix, cfg.Slot.NamespacePrefix)
	}
}

func validConfig() Config {
	return Config{
		HTTPAddr: ":8080",
		GRPCAddr: ":9090",
		GRPC: RuntimeGRPCConfig{
			AuthRequired:         true,
			AuthToken:            "test-token",
			MaxInFlight:          128,
			MaxConcurrentStreams: 128,
			UnaryTimeout:         30 * time.Second,
			KeepaliveTime:        2 * time.Minute,
			KeepaliveTimeout:     20 * time.Second,
			KeepaliveMinTime:     30 * time.Second,
			MaxRecvMessageBytes:  4 * 1024 * 1024,
			MaxSendMessageBytes:  4 * 1024 * 1024,
		},
		Database: RuntimeDatabaseConfig{
			DSN: "postgres://postgres:5432/kodex_runtime_manager?sslmode=disable",
			Pool: RuntimeDatabasePoolConfig{
				MaxConns:          8,
				MinConns:          1,
				MaxConnLifetime:   time.Hour,
				MaxConnIdleTime:   15 * time.Minute,
				HealthCheckPeriod: 30 * time.Second,
				PingTimeout:       5 * time.Second,
			},
			Retry: RuntimeDatabaseRetryConfig{
				MaxAttempts: 6,
				Initial:     500 * time.Millisecond,
				Max:         5 * time.Second,
				JitterRatio: 0.2,
			},
		},
		EventLogDatabase: RuntimeEventLogDBConfig{
			DSN:      "postgres://postgres:5432/kodex_platform_event_log?sslmode=disable",
			MaxConns: 4,
			MinConns: 0,
		},
		Outbox: RuntimeOutboxConfig{
			DispatchEnabled:     true,
			PublisherKind:       outboxlib.PublisherKindPostgresEventLog,
			EventLogSource:      "runtime-manager",
			BatchSize:           100,
			PollInterval:        time.Second,
			LockTTL:             30 * time.Second,
			PublishTimeout:      10 * time.Second,
			LeaseSafetyMargin:   5 * time.Second,
			RetryInitialDelay:   time.Second,
			RetryMaxDelay:       time.Minute,
			FailureMessageLimit: 512,
		},
		Slot: RuntimeSlotConfig{
			DefaultFleetScopeID: "00000000-0000-0000-0000-000000000001",
			DefaultClusterID:    "00000000-0000-0000-0000-000000000002",
			NamespacePrefix:     "kodex-rt",
			DefaultLeaseTTL:     30 * time.Minute,
		},
		Access: RuntimeAccessConfig{
			CheckEnabled:           true,
			AccessManagerGRPCAddr:  "access-manager:9090",
			AccessManagerAuthToken: "access-token",
			CheckTimeout:           3 * time.Second,
		},
		Fleet: RuntimeFleetConfig{
			FleetManagerGRPCAddr:  "fleet-manager:9090",
			FleetManagerAuthToken: "fleet-token",
			ResolveTimeout:        5 * time.Second,
		},
		SecretResolver: RuntimeSecretConfig{
			EnvEnabled:                true,
			MountedKubernetesMaxBytes: 1024 * 1024,
		},
		KubernetesWorker: RuntimeKubernetesWorkerConfig{
			Enabled:                 false,
			WorkerID:                "runtime-manager-kubernetes-executor",
			PollInterval:            5 * time.Second,
			ClaimLeaseTTL:           5 * time.Minute,
			JobTimeout:              2 * time.Minute,
			KubernetesPollInterval:  2 * time.Second,
			ImagePullPolicy:         "IfNotPresent",
			BackoffLimit:            0,
			TTLSecondsAfterFinished: 300,
			LogTailBytes:            16 * 1024,
			AgentManagerGRPCAddr:    "agent-manager:9090",
			AgentManagerSecretName:  "kodex-platform-runtime",
			AgentManagerSecretKey:   "KODEX_AGENT_MANAGER_GRPC_AUTH_TOKEN",
			AgentManagerTimeout:     3 * time.Second,
		},
	}
}
