package config

import (
	"strings"
	"testing"
	"time"
)

func TestNewDefaults(t *testing.T) {
	t.Setenv("TENANT_ID", "test")
	t.Setenv("DB_DSN", "user:pass@tcp(localhost)/testdb")
	// Set optional vars to empty to verify defaults (t.Setenv restores on cleanup)
	t.Setenv("PORT", "")
	t.Setenv("BASE_URL", "")
	t.Setenv("CONSOLE_BASE_URL", "")
	t.Setenv("GIT_REPO_DIR", "")

	cfg, err := New()
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	if cfg.Port != "8080" {
		t.Errorf("expected default Port=8080, got %q", cfg.Port)
	}
	if len(cfg.Tenants) != 1 {
		t.Fatalf("expected one normalized tenant, got %#v", cfg.Tenants)
	}
	tenant := cfg.Tenants[0]
	if tenant.BaseURL != "http://localhost:8080" {
		t.Errorf("expected default BaseURL, got %q", tenant.BaseURL)
	}
	if tenant.Browser.ConsoleBaseURL != "http://localhost:5173" {
		t.Errorf("expected default ConsoleBaseURL, got %q", tenant.Browser.ConsoleBaseURL)
	}
	if tenant.Browser.OAuthDeviceVerificationURL != "" {
		t.Errorf("expected default OAuthDeviceVerificationURL empty, got %q", tenant.Browser.OAuthDeviceVerificationURL)
	}
	if tenant.GitRepoDir != "gitrepos" {
		t.Errorf("expected default GitRepoDir=gitrepos, got %q", tenant.GitRepoDir)
	}
	if tenant.DBdsn != "user:pass@tcp(localhost)/testdb" {
		t.Errorf("expected DBdsn from env, got %q", tenant.DBdsn)
	}
	if tenant.Workflow.Enabled {
		t.Error("expected workflow execution to be disabled by default")
	}
	if tenant.Workflow.Image != "bash:5.2" {
		t.Errorf("expected default WorkflowExecImage=bash:5.2, got %q", tenant.Workflow.Image)
	}
	if tenant.Workflow.Timeout != 2*time.Minute {
		t.Errorf("expected default WorkflowExecTimeout=2m, got %s", tenant.Workflow.Timeout)
	}
	if tenant.Workflow.CPUs != "1.0" {
		t.Errorf("expected default WorkflowExecCPUs=1.0, got %q", tenant.Workflow.CPUs)
	}
	if tenant.Workflow.Memory != "256m" {
		t.Errorf("expected default WorkflowExecMemory=256m, got %q", tenant.Workflow.Memory)
	}
	if tenant.Workflow.PidsLimit != 128 {
		t.Errorf("expected default WorkflowExecPidsLimit=128, got %d", tenant.Workflow.PidsLimit)
	}
	if tenant.Workflow.NoFile != 1024 {
		t.Errorf("expected default WorkflowExecNoFile=1024, got %d", tenant.Workflow.NoFile)
	}
	if tenant.Workflow.TmpfsSize != "64m" {
		t.Errorf("expected default WorkflowExecTmpfsSize=64m, got %q", tenant.Workflow.TmpfsSize)
	}
}

func TestNewOverrides(t *testing.T) {
	t.Setenv("TENANT_ID", "test")
	t.Setenv("DB_DSN", "custom-dsn")
	t.Setenv("PORT", "9090")
	t.Setenv("BASE_URL", "https://example.com")
	t.Setenv("TENANT_DOMAIN_ALIASES", " https://api.example.com/, https://uploads.example.com,https://api.example.com ")
	t.Setenv("CONSOLE_BASE_URL", "https://console.example.com")
	t.Setenv("OAUTH_DEVICE_VERIFICATION_URL", "https://console.example.com/device-login")
	t.Setenv("GIT_REPO_DIR", "/tmp/repos")
	t.Setenv("ENABLE_WORKFLOW_EXEC", "1")
	t.Setenv("WORKFLOW_EXEC_IMAGE", "custom/bash:latest")
	t.Setenv("WORKFLOW_EXEC_TIMEOUT", "45s")
	t.Setenv("WORKFLOW_EXEC_CPUS", "0.5")
	t.Setenv("WORKFLOW_EXEC_MEMORY", "128m")
	t.Setenv("WORKFLOW_EXEC_PIDS_LIMIT", "64")
	t.Setenv("WORKFLOW_EXEC_NOFILE", "256")
	t.Setenv("WORKFLOW_EXEC_TMPFS_SIZE", "16m")
	t.Setenv("OIDC_PROVIDER", "casdoor")
	t.Setenv("OIDC_ISSUER", "https://door.example.com")
	t.Setenv("OIDC_CLIENT_ID", "oidc-client")
	t.Setenv("OIDC_SCOPES", "openid profile email groups")

	cfg, err := New()
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	if cfg.Port != "9090" {
		t.Errorf("expected Port=9090, got %q", cfg.Port)
	}
	if len(cfg.Tenants) != 1 {
		t.Fatalf("expected one normalized tenant, got %#v", cfg.Tenants)
	}
	tenant := cfg.Tenants[0]
	if tenant.BaseURL != "https://example.com" {
		t.Errorf("expected BaseURL=https://example.com, got %q", tenant.BaseURL)
	}
	if got := tenant.DomainAliases; len(got) != 2 ||
		got[0] != "https://api.example.com" ||
		got[1] != "https://uploads.example.com" {
		t.Errorf("expected normalized tenant domain aliases, got %#v", got)
	}
	if tenant.Browser.ConsoleBaseURL != "https://console.example.com" {
		t.Errorf("expected ConsoleBaseURL=https://console.example.com, got %q", tenant.Browser.ConsoleBaseURL)
	}
	if tenant.Browser.OAuthDeviceVerificationURL != "https://console.example.com/device-login" {
		t.Errorf("expected OAuthDeviceVerificationURL override, got %q", tenant.Browser.OAuthDeviceVerificationURL)
	}
	if tenant.GitRepoDir != "/tmp/repos" {
		t.Errorf("expected GitRepoDir=/tmp/repos, got %q", tenant.GitRepoDir)
	}
	if tenant.DBdsn != "custom-dsn" {
		t.Errorf("expected DBdsn=custom-dsn, got %q", tenant.DBdsn)
	}
	if !tenant.Workflow.Enabled {
		t.Error("expected workflow execution to be enabled")
	}
	if tenant.Workflow.Image != "custom/bash:latest" {
		t.Errorf("expected WorkflowExecImage override, got %q", tenant.Workflow.Image)
	}
	if tenant.Workflow.Timeout != 45*time.Second {
		t.Errorf("expected WorkflowExecTimeout=45s, got %s", tenant.Workflow.Timeout)
	}
	if tenant.Workflow.CPUs != "0.5" {
		t.Errorf("expected WorkflowExecCPUs=0.5, got %q", tenant.Workflow.CPUs)
	}
	if tenant.Workflow.Memory != "128m" {
		t.Errorf("expected WorkflowExecMemory=128m, got %q", tenant.Workflow.Memory)
	}
	if tenant.Workflow.PidsLimit != 64 {
		t.Errorf("expected WorkflowExecPidsLimit=64, got %d", tenant.Workflow.PidsLimit)
	}
	if tenant.Workflow.NoFile != 256 {
		t.Errorf("expected WorkflowExecNoFile=256, got %d", tenant.Workflow.NoFile)
	}
	if tenant.Workflow.TmpfsSize != "16m" {
		t.Errorf("expected WorkflowExecTmpfsSize=16m, got %q", tenant.Workflow.TmpfsSize)
	}
	if tenant.OIDC.Provider != "casdoor" || tenant.OIDC.Issuer != "https://door.example.com" || tenant.OIDC.ClientID != "oidc-client" {
		t.Fatalf("expected explicit oidc config to be loaded, got %+v", tenant.OIDC)
	}
	if tenant.OIDC.Scopes != "openid profile email groups" {
		t.Fatalf("expected explicit oidc scopes, got %q", tenant.OIDC.Scopes)
	}
}

func TestNewLoadsConnectedLoginConfig(t *testing.T) {
	t.Setenv("TENANT_ID", "test")
	t.Setenv("DB_DSN", "user:pass@tcp(localhost)/testdb")
	t.Setenv("CONNECTED_LOGIN_PROVIDER", " provider ")
	t.Setenv("CONNECTED_LOGIN_ORIGIN", " https://app.provider.example ")
	t.Setenv("CONNECTED_LOGIN_API_ORIGIN", " https://api.provider.example ")
	t.Setenv("CONNECTED_LOGIN_CLIENT_ID", "connected-client")
	t.Setenv("CONNECTED_LOGIN_CLIENT_SECRET", "connected-secret")
	t.Setenv("CONNECTED_LOGIN_LOGIN_PATH", "/custom/login")
	t.Setenv("CONNECTED_LOGIN_SUBJECT_NAMESPACE_CLAIM", "workspace_id")

	cfg, err := New()
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	connected := cfg.Tenants[0].ConnectedLogin
	if !connected.Enabled {
		t.Fatal("expected connected login to be enabled")
	}
	if connected.Provider != "provider" {
		t.Fatalf("ConnectedLoginProvider: got %q", connected.Provider)
	}
	if connected.Origin != "https://app.provider.example" {
		t.Fatalf("ConnectedLoginOrigin: got %q", connected.Origin)
	}
	if connected.APIOrigin != "https://api.provider.example" {
		t.Fatalf("ConnectedLoginAPIOrigin: got %q", connected.APIOrigin)
	}
	if connected.ClientID != "connected-client" {
		t.Fatalf("ConnectedLoginClientID: got %q", connected.ClientID)
	}
	if connected.ClientSecret != "connected-secret" {
		t.Fatalf("ConnectedLoginClientSecret: got %q", connected.ClientSecret)
	}
	if connected.LoginPath != "/custom/login" {
		t.Fatalf("ConnectedLoginLoginPath: got %q", connected.LoginPath)
	}
	if connected.SubjectNamespaceClaim != "workspace_id" {
		t.Fatalf("ConnectedLoginSubjectNamespaceClaim: got %q", connected.SubjectNamespaceClaim)
	}
}

func TestOAuthDeviceVerificationURLValidation(t *testing.T) {
	t.Setenv("TENANT_ID", "test")
	t.Setenv("DB_DSN", "user:pass@tcp(localhost)/testdb")

	t.Run("trims valid url", func(t *testing.T) {
		t.Setenv("OAUTH_DEVICE_VERIFICATION_URL", " https://console.example.com/device?tenant=one ")
		cfg, err := New()
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if got := cfg.Tenants[0].Browser.OAuthDeviceVerificationURL; got != "https://console.example.com/device?tenant=one" {
			t.Fatalf("unexpected OAuthDeviceVerificationURL: %q", got)
		}
	})

	t.Run("rejects invalid urls", func(t *testing.T) {
		for _, raw := range []string{"console.example.com/device", "ftp://console.example.com/device", "https:///device"} {
			t.Run(raw, func(t *testing.T) {
				t.Setenv("OAUTH_DEVICE_VERIFICATION_URL", raw)
				_, err := New()
				if err == nil {
					t.Fatalf("expected OAUTH_DEVICE_VERIFICATION_URL=%q to fail", raw)
				}
				if !strings.Contains(err.Error(), "OAUTH_DEVICE_VERIFICATION_URL") {
					t.Fatalf("unexpected error: %v", err)
				}
			})
		}
	})
}

func TestNewRejectsPartialConnectedLoginConfig(t *testing.T) {
	t.Setenv("TENANT_ID", "test")
	t.Setenv("DB_DSN", "user:pass@tcp(localhost)/testdb")
	t.Setenv("CONNECTED_LOGIN_ORIGIN", "https://app.provider.example")
	t.Setenv("CONNECTED_LOGIN_API_ORIGIN", "")
	t.Setenv("CONNECTED_LOGIN_CLIENT_ID", "connected-client")
	t.Setenv("CONNECTED_LOGIN_CLIENT_SECRET", "")

	_, err := New()
	if err == nil {
		t.Fatal("expected partial connected login config to fail")
	}
	if !strings.Contains(err.Error(), "connected login: partial configuration") {
		t.Fatalf("unexpected error: %v", err)
	}
}

func TestOIDCProviderDefaultsToAuth0ForAuth0Issuer(t *testing.T) {
	t.Setenv("TENANT_ID", "test")
	t.Setenv("DB_DSN", "user:pass@tcp(localhost)/testdb")
	t.Setenv("OIDC_PROVIDER", "")
	t.Setenv("OIDC_ISSUER", "https://account.us.auth0.com/")
	t.Setenv("OIDC_CLIENT_ID", "oidc-client")

	cfg, err := New()
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if got := cfg.Tenants[0].OIDC.Provider; got != "auth0" {
		t.Fatalf("expected OIDCProvider=auth0 for Auth0 issuer, got %q", got)
	}
}

func TestOIDCProviderDefaultsToOIDCForNonAuth0Issuer(t *testing.T) {
	t.Setenv("TENANT_ID", "test")
	t.Setenv("DB_DSN", "user:pass@tcp(localhost)/testdb")
	t.Setenv("OIDC_PROVIDER", "")
	t.Setenv("OIDC_ISSUER", "https://issuer.example.com")
	t.Setenv("OIDC_CLIENT_ID", "oidc-client")

	cfg, err := New()
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if got := cfg.Tenants[0].OIDC.Provider; got != "oidc" {
		t.Fatalf("expected OIDCProvider=oidc for generic issuer, got %q", got)
	}
}

func TestNewErrorsWithoutDBDSN(t *testing.T) {
	t.Setenv("TENANT_ID", "test")
	t.Setenv("DB_DSN", "")

	_, err := New()
	if err == nil {
		t.Fatal("expected error when DB_DSN is not set")
	}
	expected := "required environment variable not set: DB_DSN"
	if err.Error() != expected {
		t.Errorf("unexpected error message: %q", err.Error())
	}
}

func TestNewErrorsWithoutTenantID(t *testing.T) {
	t.Setenv("TENANT_ID", "")
	t.Setenv("DB_DSN", "user:pass@tcp(localhost)/testdb")

	_, err := New()
	if err == nil {
		t.Fatal("expected error when TENANT_ID is not set")
	}
	expected := "required environment variable not set: TENANT_ID"
	if err.Error() != expected {
		t.Errorf("unexpected error message: %q", err.Error())
	}
}

func TestEnvironmentDefaultsToProduction(t *testing.T) {
	t.Setenv("TENANT_ID", "test")
	t.Setenv("DB_DSN", "user:pass@tcp(localhost)/testdb")
	t.Setenv("ENVIRONMENT", "")

	cfg, err := New()
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if cfg.Environment != "production" {
		t.Errorf("expected default Environment=production, got %q", cfg.Environment)
	}
}

func TestEnvironmentExplicitDevelopment(t *testing.T) {
	t.Setenv("TENANT_ID", "test")
	t.Setenv("DB_DSN", "user:pass@tcp(localhost)/testdb")
	t.Setenv("ENVIRONMENT", "development")

	cfg, err := New()
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if cfg.Environment != "development" {
		t.Errorf("expected Environment=development, got %q", cfg.Environment)
	}
}

func TestListenModeDefault(t *testing.T) {
	t.Setenv("TENANT_ID", "test")
	t.Setenv("DB_DSN", "user:pass@tcp(localhost)/testdb")
	t.Setenv("LISTEN_MODE", "")

	cfg, err := New()
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if cfg.ListenMode != "development" {
		t.Errorf("expected default ListenMode=development, got %q", cfg.ListenMode)
	}
}

func TestListenModeProduction(t *testing.T) {
	t.Setenv("TENANT_ID", "test")
	t.Setenv("DB_DSN", "user:pass@tcp(localhost)/testdb")
	t.Setenv("LISTEN_MODE", "production")

	cfg, err := New()
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if cfg.ListenMode != "production" {
		t.Errorf("expected ListenMode=production, got %q", cfg.ListenMode)
	}
}

func TestListenModeInvalid(t *testing.T) {
	t.Setenv("TENANT_ID", "test")
	t.Setenv("DB_DSN", "user:pass@tcp(localhost)/testdb")

	for _, mode := range []string{"Production", "PRODUCTION", "dev", "staging", "typo"} {
		t.Run(mode, func(t *testing.T) {
			t.Setenv("LISTEN_MODE", mode)
			_, err := New()
			if err == nil {
				t.Fatalf("expected error for LISTEN_MODE=%q, got nil", mode)
			}
		})
	}
}

func TestEnvironmentDefaultProduction(t *testing.T) {
	t.Setenv("TENANT_ID", "test")
	t.Setenv("DB_DSN", "user:pass@tcp(localhost)/testdb")
	t.Setenv("ENVIRONMENT", "") // unset — must default to production

	cfg, err := New()
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if cfg.Environment != "production" {
		t.Errorf("expected default Environment=production, got %q", cfg.Environment)
	}
}

func TestEnvironmentDevelopment(t *testing.T) {
	t.Setenv("TENANT_ID", "test")
	t.Setenv("DB_DSN", "user:pass@tcp(localhost)/testdb")
	t.Setenv("ENVIRONMENT", "development")

	cfg, err := New()
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if cfg.Environment != "development" {
		t.Errorf("expected Environment=development, got %q", cfg.Environment)
	}
}

func TestEnvironmentNormalization(t *testing.T) {
	t.Setenv("TENANT_ID", "test")
	t.Setenv("DB_DSN", "user:pass@tcp(localhost)/testdb")

	for _, val := range []string{"PRODUCTION", "Production", " production ", "DEVELOPMENT", "Development", " development "} {
		t.Run(val, func(t *testing.T) {
			t.Setenv("ENVIRONMENT", val)
			cfg, err := New()
			if err != nil {
				t.Fatalf("unexpected error for ENVIRONMENT=%q: %v", val, err)
			}
			norm := cfg.Environment
			if norm != "production" && norm != "development" {
				t.Errorf("expected normalized value, got %q for input %q", norm, val)
			}
		})
	}
}

func TestEnvironmentInvalid(t *testing.T) {
	t.Setenv("TENANT_ID", "test")
	t.Setenv("DB_DSN", "user:pass@tcp(localhost)/testdb")

	for _, val := range []string{"staging", "dev", "prod", "test", "typo"} {
		t.Run(val, func(t *testing.T) {
			t.Setenv("ENVIRONMENT", val)
			_, err := New()
			if err == nil {
				t.Fatalf("expected error for ENVIRONMENT=%q, got nil", val)
			}
		})
	}
}

func TestWorkflowExecTimeoutInvalid(t *testing.T) {
	t.Setenv("TENANT_ID", "test")
	t.Setenv("DB_DSN", "user:pass@tcp(localhost)/testdb")
	t.Setenv("WORKFLOW_EXEC_TIMEOUT", "nope")

	if _, err := New(); err == nil {
		t.Fatal("expected error for invalid WORKFLOW_EXEC_TIMEOUT")
	}
}

func TestWorkflowExecPidsLimitInvalid(t *testing.T) {
	t.Setenv("TENANT_ID", "test")
	t.Setenv("DB_DSN", "user:pass@tcp(localhost)/testdb")
	t.Setenv("WORKFLOW_EXEC_PIDS_LIMIT", "0")

	if _, err := New(); err == nil {
		t.Fatal("expected error for invalid WORKFLOW_EXEC_PIDS_LIMIT")
	}
}

func TestWorkflowExecNoFileInvalid(t *testing.T) {
	t.Setenv("TENANT_ID", "test")
	t.Setenv("DB_DSN", "user:pass@tcp(localhost)/testdb")
	t.Setenv("WORKFLOW_EXEC_NOFILE", "-1")

	if _, err := New(); err == nil {
		t.Fatal("expected error for invalid WORKFLOW_EXEC_NOFILE")
	}
}
