package config

import (
	"errors"
	"testing"
	"time"
)

func TestReadOptionalEnv(t *testing.T) {
	t.Run("present non-empty", func(t *testing.T) {
		lookup := func(k string) (string, bool) {
			if k == "MY_KEY" {
				return "  value  ", true
			}
			return "", false
		}
		if got := readOptionalEnv(lookup, "MY_KEY", "default"); got != "value" {
			t.Fatalf("got %q, want %q", got, "value")
		}
	})
	t.Run("present empty", func(t *testing.T) {
		lookup := func(k string) (string, bool) {
			return "  ", true
		}
		if got := readOptionalEnv(lookup, "MY_KEY", "default"); got != "default" {
			t.Fatalf("got %q, want %q", got, "default")
		}
	})
	t.Run("missing", func(t *testing.T) {
		lookup := func(k string) (string, bool) {
			return "", false
		}
		if got := readOptionalEnv(lookup, "MY_KEY", "default"); got != "default" {
			t.Fatalf("got %q, want %q", got, "default")
		}
	})
}

func TestReadRequiredEnv(t *testing.T) {
	t.Run("present", func(t *testing.T) {
		lookup := func(k string) (string, bool) {
			return "my-token", true
		}
		if got := readRequiredEnv(lookup, "TOKEN"); got != "my-token" {
			t.Fatalf("got %q, want %q", got, "my-token")
		}
	})
	t.Run("missing", func(t *testing.T) {
		lookup := func(k string) (string, bool) {
			return "", false
		}
		if got := readRequiredEnv(lookup, "TOKEN"); got != "" {
			t.Fatalf("got %q, want empty", got)
		}
	})
}

func TestLoadStartupFromLookupEnv(t *testing.T) {
	t.Run("custom values", func(t *testing.T) {
		lookup := func(k string) (string, bool) {
			switch k {
			case EnvListenAddr:
				return ":9090", true
			case EnvSQLiteDSN:
				return "/data/db.sqlite", true
			case EnvRepoRoot:
				return "/srv/sub2api-cn-relay-manager", true
			case EnvReconcileWorkerEnabled:
				return "true", true
			case EnvReconcilePollInterval:
				return "15m", true
			case EnvRouteRuntimeBackend:
				return "redis", true
			case EnvRedisAddr:
				return "127.0.0.1:16379", true
			case EnvRedisPassword:
				return " redis-pass ", true
			case EnvRedisDB:
				return "5", true
			default:
				return "", false
			}
		}
		cfg, err := loadStartupFromLookupEnv(lookup)
		if err != nil {
			t.Fatal(err)
		}
		if cfg.Server.ListenAddr != ":9090" {
			t.Fatalf("ListenAddr = %q, want %q", cfg.Server.ListenAddr, ":9090")
		}
		if cfg.Database.SQLiteDSN != "/data/db.sqlite" {
			t.Fatalf("SQLiteDSN = %q, want %q", cfg.Database.SQLiteDSN, "/data/db.sqlite")
		}
		if cfg.Repository.RepoRoot != "/srv/sub2api-cn-relay-manager" {
			t.Fatalf("RepoRoot = %q, want %q", cfg.Repository.RepoRoot, "/srv/sub2api-cn-relay-manager")
		}
		if !cfg.Reconcile.WorkerEnabled {
			t.Fatal("WorkerEnabled = false, want true")
		}
		if cfg.Reconcile.PollInterval != 15*time.Minute {
			t.Fatalf("PollInterval = %s, want 15m", cfg.Reconcile.PollInterval)
		}
		if cfg.RouteRuntime.Backend != "redis" {
			t.Fatalf("RouteRuntime.Backend = %q, want redis", cfg.RouteRuntime.Backend)
		}
		if cfg.RouteRuntime.Redis.Addr != "127.0.0.1:16379" {
			t.Fatalf("RouteRuntime.Redis.Addr = %q, want 127.0.0.1:16379", cfg.RouteRuntime.Redis.Addr)
		}
		if cfg.RouteRuntime.Redis.Password != "redis-pass" {
			t.Fatalf("RouteRuntime.Redis.Password = %q, want redis-pass", cfg.RouteRuntime.Redis.Password)
		}
		if cfg.RouteRuntime.Redis.DB != 5 {
			t.Fatalf("RouteRuntime.Redis.DB = %d, want 5", cfg.RouteRuntime.Redis.DB)
		}
	})
	t.Run("default values", func(t *testing.T) {
		lookup := func(k string) (string, bool) {
			return "", false
		}
		cfg, err := loadStartupFromLookupEnv(lookup)
		if err != nil {
			t.Fatal(err)
		}
		if cfg.Server.ListenAddr != DefaultListenAddr {
			t.Fatalf("ListenAddr = %q, want %q", cfg.Server.ListenAddr, DefaultListenAddr)
		}
		if cfg.Database.SQLiteDSN != DefaultSQLiteDSN {
			t.Fatalf("SQLiteDSN = %q, want %q", cfg.Database.SQLiteDSN, DefaultSQLiteDSN)
		}
		if cfg.Repository.RepoRoot != "" {
			t.Fatalf("RepoRoot = %q, want empty by default", cfg.Repository.RepoRoot)
		}
		if cfg.Reconcile.WorkerEnabled {
			t.Fatal("WorkerEnabled = true, want false by default")
		}
		if cfg.Reconcile.PollInterval != DefaultReconcilePollInterval {
			t.Fatalf("PollInterval = %s, want %s", cfg.Reconcile.PollInterval, DefaultReconcilePollInterval)
		}
		if cfg.RouteRuntime.Backend != DefaultRouteRuntimeBackend {
			t.Fatalf("RouteRuntime.Backend = %q, want %q", cfg.RouteRuntime.Backend, DefaultRouteRuntimeBackend)
		}
		if cfg.RouteRuntime.Redis.Addr != "" || cfg.RouteRuntime.Redis.Password != "" || cfg.RouteRuntime.Redis.DB != 0 {
			t.Fatalf("RouteRuntime.Redis = %+v, want zero value", cfg.RouteRuntime.Redis)
		}
	})
	t.Run("invalid reconcile interval", func(t *testing.T) {
		lookup := func(k string) (string, bool) {
			if k == EnvReconcilePollInterval {
				return "not-a-duration", true
			}
			return "", false
		}
		if _, err := loadStartupFromLookupEnv(lookup); err == nil {
			t.Fatal("loadStartupFromLookupEnv() error = nil, want invalid interval")
		}
	})
	t.Run("invalid redis db", func(t *testing.T) {
		lookup := func(k string) (string, bool) {
			if k == EnvRedisDB {
				return "-1", true
			}
			return "", false
		}
		if _, err := loadStartupFromLookupEnv(lookup); err == nil {
			t.Fatal("loadStartupFromLookupEnv() error = nil, want invalid redis db")
		}
	})
}

func TestLoadAdminTokenFromLookupEnv(t *testing.T) {
	t.Run("valid token", func(t *testing.T) {
		lookup := func(k string) (string, bool) {
			return "  admin-secret-123  ", true
		}
		token, err := loadAdminTokenFromLookupEnv(lookup)
		if err != nil {
			t.Fatal(err)
		}
		if token != "admin-secret-123" {
			t.Fatalf("token = %q, want %q", token, "admin-secret-123")
		}
	})
	t.Run("empty token", func(t *testing.T) {
		lookup := func(k string) (string, bool) {
			return "  ", true
		}
		_, err := loadAdminTokenFromLookupEnv(lookup)
		if err == nil {
			t.Fatal("expected error for empty token")
		}
	})
	t.Run("missing env", func(t *testing.T) {
		lookup := func(k string) (string, bool) {
			return "", false
		}
		_, err := loadAdminTokenFromLookupEnv(lookup)
		if err == nil {
			t.Fatal("expected error for missing env")
		}
	})
}

func TestLoadAdminSessionFromLookupEnv(t *testing.T) {
	t.Run("uses defaults", func(t *testing.T) {
		cfg, err := loadAdminSessionFromLookupEnv(func(string) (string, bool) {
			return "", false
		})
		if err != nil {
			t.Fatal(err)
		}
		if cfg.Username != DefaultAdminUsername {
			t.Fatalf("Username = %q, want %q", cfg.Username, DefaultAdminUsername)
		}
		if cfg.Password != "" {
			t.Fatalf("Password = %q, want empty", cfg.Password)
		}
		if cfg.SessionTTL != DefaultAdminSessionTTL {
			t.Fatalf("SessionTTL = %s, want %s", cfg.SessionTTL, DefaultAdminSessionTTL)
		}
	})

	t.Run("loads custom values", func(t *testing.T) {
		cfg, err := loadAdminSessionFromLookupEnv(func(key string) (string, bool) {
			switch key {
			case EnvAdminUsername:
				return " portal-admin ", true
			case EnvAdminPassword:
				return " super-secret ", true
			case EnvAdminSessionTTL:
				return "4h", true
			default:
				return "", false
			}
		})
		if err != nil {
			t.Fatal(err)
		}
		if cfg.Username != "portal-admin" {
			t.Fatalf("Username = %q, want portal-admin", cfg.Username)
		}
		if cfg.Password != "super-secret" {
			t.Fatalf("Password = %q, want super-secret", cfg.Password)
		}
		if cfg.SessionTTL != 4*time.Hour {
			t.Fatalf("SessionTTL = %s, want 4h", cfg.SessionTTL)
		}
	})

	t.Run("rejects invalid ttl", func(t *testing.T) {
		_, err := loadAdminSessionFromLookupEnv(func(key string) (string, bool) {
			if key == EnvAdminSessionTTL {
				return "bad", true
			}
			return "", false
		})
		if err == nil {
			t.Fatal("expected error for invalid session ttl")
		}
	})
}

// Verify exported wrappers call the lookup versions.
// We can't easily test LoadStartupFromEnv / LoadAdminTokenFromEnv
// since they depend on os.LookupEnv, but we verify they compile and don't panic.

func TestExportFunctionsExist(t *testing.T) {
	// Just verify the exported functions are reachable and return the right types
	_, err := LoadAdminTokenFromEnv()
	if err != nil && !errors.Is(err, err) {
		// any result is fine, just proving the function exists
	}
}
