package main

import (
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"testing"
)

func TestLoadConfigDatabaseDefaultsToLocal(t *testing.T) {
	t.Setenv("IOP_DATABASE_URL", "")
	t.Setenv("IOP_REDIS_URL", "")
	t.Setenv("IOP_REDIS_KEY_PREFIX", "")
	cfg, err := loadConfig("")
	if err != nil {
		t.Fatalf("load config: %v", err)
	}
	const wantDatabase = ""
	if cfg.Database.URL != wantDatabase {
		t.Fatalf("database url: got %q want %q", cfg.Database.URL, wantDatabase)
	}
	const wantRedis = ""
	if cfg.Redis.URL != wantRedis {
		t.Fatalf("redis url: got %q want %q", cfg.Redis.URL, wantRedis)
	}
	const wantRedisKeyPrefix = "iop:control-plane:"
	if cfg.Redis.KeyPrefix != wantRedisKeyPrefix {
		t.Fatalf("redis key prefix: got %q want %q", cfg.Redis.KeyPrefix, wantRedisKeyPrefix)
	}
}

func TestLoadConfigDatabaseFromYAML(t *testing.T) {
	t.Setenv("IOP_DATABASE_URL", "")
	t.Setenv("IOP_REDIS_URL", "")
	t.Setenv("IOP_REDIS_KEY_PREFIX", "")
	path := writeConfig(t, `
server:
  listen: "127.0.0.1:9080"
database:
  url: "postgres://user:pass@db:5432/iop-control-plane-local?sslmode=disable"
redis:
  url: "redis://cache:6379/1"
  key_prefix: "iop:test:"
`)

	cfg, err := loadConfig(path)
	if err != nil {
		t.Fatalf("load config: %v", err)
	}
	const want = "postgres://user:pass@db:5432/iop-control-plane-local?sslmode=disable"
	if cfg.Database.URL != want {
		t.Fatalf("database url: got %q want %q", cfg.Database.URL, want)
	}
	if cfg.Redis.URL != "redis://cache:6379/1" {
		t.Fatalf("redis url: got %q", cfg.Redis.URL)
	}
	if cfg.Redis.KeyPrefix != "iop:test:" {
		t.Fatalf("redis key prefix: got %q", cfg.Redis.KeyPrefix)
	}
}

func TestLoadConfigRepositoryLocalConfig(t *testing.T) {
	t.Setenv("IOP_DATABASE_URL", "")
	t.Setenv("IOP_REDIS_URL", "")
	t.Setenv("IOP_REDIS_KEY_PREFIX", "")
	t.Setenv("IOP_WIRE_LISTEN", "")
	t.Setenv("IOP_EDGE_WIRE_LISTEN", "")
	t.Setenv("IOP_METRICS_PORT", "")
	cfg, err := loadConfig("../../../../configs/control-plane.yaml")
	if err != nil {
		t.Fatalf("load config: %v", err)
	}
	if cfg.Redis.URL != "" {
		t.Fatalf("redis url: got %q", cfg.Redis.URL)
	}
	if cfg.Redis.KeyPrefix != "iop:control-plane:" {
		t.Fatalf("redis key prefix: got %q", cfg.Redis.KeyPrefix)
	}
	if cfg.Server.WireListen != "0.0.0.0:19080" {
		t.Fatalf("wire_listen: got %q", cfg.Server.WireListen)
	}
	if cfg.Server.EdgeWireListen != "0.0.0.0:19081" {
		t.Fatalf("edge_wire_listen: got %q", cfg.Server.EdgeWireListen)
	}
	if cfg.Server.Listen != "0.0.0.0:18000" {
		t.Fatalf("listen: got %q want %q", cfg.Server.Listen, "0.0.0.0:18000")
	}
	if cfg.Metrics.Port != 9093 {
		t.Fatalf("metrics port: got %d want 9093", cfg.Metrics.Port)
	}
}

func TestLoadConfigCredentialEncryptionDefaultsEmpty(t *testing.T) {
	cfg, err := loadConfig("")
	if err != nil {
		t.Fatalf("load config: %v", err)
	}
	if cfg.CredentialEncryption.KeyFile != "" {
		t.Fatalf("key_file default: got %q want empty", cfg.CredentialEncryption.KeyFile)
	}
	if cfg.CredentialEncryption.ActiveKeyID != "" {
		t.Fatalf("active_key_id default: got %q want empty", cfg.CredentialEncryption.ActiveKeyID)
	}
	if cfg.CredentialEncryption.ActiveKeyVersion != 0 {
		t.Fatalf("active_key_version default: got %d want 0", cfg.CredentialEncryption.ActiveKeyVersion)
	}
}

func TestLoadConfigCredentialEncryptionFromYAML(t *testing.T) {
	path := writeConfig(t, `
credential_encryption:
  key_file: "/run/secrets/iop/keyring.yaml"
  active_key_id: "primary"
  active_key_version: 3
`)
	cfg, err := loadConfig(path)
	if err != nil {
		t.Fatalf("load config: %v", err)
	}
	if cfg.CredentialEncryption.KeyFile != "/run/secrets/iop/keyring.yaml" {
		t.Fatalf("key_file: got %q", cfg.CredentialEncryption.KeyFile)
	}
	if cfg.CredentialEncryption.ActiveKeyID != "primary" {
		t.Fatalf("active_key_id: got %q", cfg.CredentialEncryption.ActiveKeyID)
	}
	if cfg.CredentialEncryption.ActiveKeyVersion != 3 {
		t.Fatalf("active_key_version: got %d want 3", cfg.CredentialEncryption.ActiveKeyVersion)
	}
}

func TestLoadConfigRepositoryCredentialEncryptionIsSecretFree(t *testing.T) {
	cfg, err := loadConfig("../../../../configs/control-plane.yaml")
	if err != nil {
		t.Fatalf("load config: %v", err)
	}
	// The tracked repository config carries only empty placeholders: encryption
	// is disabled and no key id/version/material is committed.
	if cfg.CredentialEncryption.KeyFile != "" ||
		cfg.CredentialEncryption.ActiveKeyID != "" ||
		cfg.CredentialEncryption.ActiveKeyVersion != 0 {
		t.Fatalf("repository config must ship disabled encryption placeholders: got %+v", cfg.CredentialEncryption)
	}
}

func TestLoadConfigEnvOverrides(t *testing.T) {
	path := writeConfig(t, `
database:
  url: "postgres://user:pass@db:5432/iop-control-plane-local?sslmode=disable"
redis:
  url: "redis://cache:6379/1"
  key_prefix: "iop:control-plane:local:"
`)
	const wantDatabase = "postgres://user:pass@db:5432/iop-control-plane-dev?sslmode=disable"
	const wantRedis = "redis://redis:6379/2"
	const wantRedisKeyPrefix = "iop:control-plane:dev:"
	const wantMetricsPort = 19103
	t.Setenv("IOP_DATABASE_URL", wantDatabase)
	t.Setenv("IOP_REDIS_URL", wantRedis)
	t.Setenv("IOP_REDIS_KEY_PREFIX", wantRedisKeyPrefix)
	t.Setenv("IOP_METRICS_PORT", fmt.Sprint(wantMetricsPort))

	cfg, err := loadConfig(path)
	if err != nil {
		t.Fatalf("load config: %v", err)
	}
	if cfg.Database.URL != wantDatabase {
		t.Fatalf("database url: got %q want %q", cfg.Database.URL, wantDatabase)
	}
	if cfg.Redis.URL != wantRedis {
		t.Fatalf("redis url: got %q want %q", cfg.Redis.URL, wantRedis)
	}
	if cfg.Redis.KeyPrefix != wantRedisKeyPrefix {
		t.Fatalf("redis key prefix: got %q want %q", cfg.Redis.KeyPrefix, wantRedisKeyPrefix)
	}
	if cfg.Metrics.Port != wantMetricsPort {
		t.Fatalf("metrics port: got %d want %d", cfg.Metrics.Port, wantMetricsPort)
	}
}

func TestDatabaseLogFieldsDoNotExposeCredentials(t *testing.T) {
	fields := databaseLogFields("postgres://user:secret@db.example:5432/iop-control-plane-local?sslmode=disable")
	var host, database string
	for _, field := range fields {
		switch field.Key {
		case "host":
			host = field.String
		case "database":
			database = field.String
		}
	}
	if host != "db.example:5432" {
		t.Fatalf("host field: got %q", host)
	}
	if database != "iop-control-plane-local" {
		t.Fatalf("database field: got %q", database)
	}
	if strings.Contains(fmt.Sprint(fields), "secret") {
		t.Fatal("secret leaked into log fields")
	}
}

func TestRedisLogFields(t *testing.T) {
	fields := redisLogFields("redis://:secret@cache.example:6379/2", "iop:control-plane:dev:")
	var host, database, keyPrefix string
	for _, field := range fields {
		switch field.Key {
		case "host":
			host = field.String
		case "database":
			database = field.String
		case "key_prefix":
			keyPrefix = field.String
		}
	}
	if host != "cache.example:6379" {
		t.Fatalf("host field: got %q", host)
	}
	if database != "2" {
		t.Fatalf("database field: got %q", database)
	}
	if keyPrefix != "iop:control-plane:dev:" {
		t.Fatalf("key_prefix field: got %q", keyPrefix)
	}
	if strings.Contains(fmt.Sprint(fields), "secret") {
		t.Fatal("secret leaked into log fields")
	}
}

func writeConfig(t *testing.T, body string) string {
	t.Helper()
	path := filepath.Join(t.TempDir(), "control-plane.yaml")
	if err := os.WriteFile(path, []byte(body), 0o600); err != nil {
		t.Fatalf("write config: %v", err)
	}
	return path
}

func TestLoadConfigWireListenEnvOverrides(t *testing.T) {
	path := writeConfig(t, `
server:
  listen: "0.0.0.0:9080"
  wire_listen: "0.0.0.0:19080"
`)
	const wantListen = "127.0.0.1:9081"
	const wantWireListen = "127.0.0.1:19081"
	t.Setenv("IOP_LISTEN", wantListen)
	t.Setenv("IOP_WIRE_LISTEN", wantWireListen)

	cfg, err := loadConfig(path)
	if err != nil {
		t.Fatalf("load config: %v", err)
	}
	if cfg.Server.Listen != wantListen {
		t.Fatalf("listen: got %q want %q", cfg.Server.Listen, wantListen)
	}
	if cfg.Server.WireListen != wantWireListen {
		t.Fatalf("wire_listen: got %q want %q", cfg.Server.WireListen, wantWireListen)
	}
}

func TestLoadConfigEdgeWireListenDefault(t *testing.T) {
	t.Setenv("IOP_EDGE_WIRE_LISTEN", "")
	cfg, err := loadConfig("")
	if err != nil {
		t.Fatalf("load config: %v", err)
	}
	if cfg.Server.EdgeWireListen != "0.0.0.0:19081" {
		t.Fatalf("edge_wire_listen default: got %q want %q", cfg.Server.EdgeWireListen, "0.0.0.0:19081")
	}
	if cfg.Server.EdgeWireListen == cfg.Server.WireListen {
		t.Fatalf("edge_wire_listen must not share the client wire_listen address %q", cfg.Server.WireListen)
	}
}

func TestLoadConfigEdgeWireListenEnvOverrides(t *testing.T) {
	path := writeConfig(t, `
server:
  listen: "0.0.0.0:9080"
  wire_listen: "0.0.0.0:19080"
  edge_wire_listen: "0.0.0.0:19081"
`)
	const wantEdgeWireListen = "127.0.0.1:29081"
	t.Setenv("IOP_EDGE_WIRE_LISTEN", wantEdgeWireListen)

	cfg, err := loadConfig(path)
	if err != nil {
		t.Fatalf("load config: %v", err)
	}
	if cfg.Server.EdgeWireListen != wantEdgeWireListen {
		t.Fatalf("edge_wire_listen: got %q want %q", cfg.Server.EdgeWireListen, wantEdgeWireListen)
	}
	if cfg.Server.WireListen != "0.0.0.0:19080" {
		t.Fatalf("wire_listen should stay from YAML: got %q", cfg.Server.WireListen)
	}
}
