package config

import (
	"sync"
	"testing"

	"github.com/spf13/viper"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

// resetConfigState resets the package-level singleton so each test starts fresh.
func resetConfigState() {
	cfg = nil
	cfgOnce = sync.Once{}
	viper.Reset()
}

func TestLoad_DefaultValues(t *testing.T) {
	resetConfigState()
	// Provide required SECRET_KEY so validation passes
	t.Setenv("SECRET_KEY", "a-strong-secret-key-for-tests")

	c, err := Load()
	require.NoError(t, err)

	// Server defaults
	assert.Equal(t, 8000, c.Server.Port)
	assert.False(t, c.Server.Debug)
	assert.False(t, c.Server.DebugFixedCodes)
	assert.Equal(t, "UTC", c.Server.Timezone)
	assert.Equal(t, "/app/static", c.Server.StaticDir)
	assert.Equal(t, "https://api.myhoneydue.com", c.Server.BaseURL)

	// Database defaults
	assert.Equal(t, "localhost", c.Database.Host)
	assert.Equal(t, 5432, c.Database.Port)
	assert.Equal(t, "postgres", c.Database.User)
	assert.Equal(t, "honeydue", c.Database.Database)
	assert.Equal(t, "disable", c.Database.SSLMode)
	assert.Equal(t, 25, c.Database.MaxOpenConns)
	assert.Equal(t, 10, c.Database.MaxIdleConns)

	// Redis defaults
	assert.Equal(t, "redis://localhost:6379/0", c.Redis.URL)
	assert.Equal(t, 0, c.Redis.DB)

	// Worker defaults
	assert.Equal(t, 14, c.Worker.TaskReminderHour)
	assert.Equal(t, 15, c.Worker.OverdueReminderHour)
	assert.Equal(t, 3, c.Worker.DailyNotifHour)

	// Token expiry defaults
	assert.Equal(t, 90, c.Security.TokenExpiryDays)
	assert.Equal(t, 60, c.Security.TokenRefreshDays)

	// Feature flags default to true
	assert.True(t, c.Features.PushEnabled)
	assert.True(t, c.Features.EmailEnabled)
	assert.True(t, c.Features.WebhooksEnabled)
	assert.True(t, c.Features.OnboardingEmailsEnabled)
	assert.True(t, c.Features.PDFReportsEnabled)
	assert.True(t, c.Features.WorkerEnabled)
}

func TestLoad_EnvOverrides(t *testing.T) {
	resetConfigState()
	t.Setenv("SECRET_KEY", "a-strong-secret-key-for-tests")
	t.Setenv("PORT", "9090")
	t.Setenv("DEBUG", "true")
	t.Setenv("DB_HOST", "db.example.com")
	t.Setenv("DB_PORT", "5433")
	t.Setenv("TOKEN_EXPIRY_DAYS", "180")
	t.Setenv("TOKEN_REFRESH_DAYS", "120")
	t.Setenv("FEATURE_PUSH_ENABLED", "false")

	c, err := Load()
	require.NoError(t, err)

	assert.Equal(t, 9090, c.Server.Port)
	assert.True(t, c.Server.Debug)
	assert.Equal(t, "db.example.com", c.Database.Host)
	assert.Equal(t, 5433, c.Database.Port)
	assert.Equal(t, 180, c.Security.TokenExpiryDays)
	assert.Equal(t, 120, c.Security.TokenRefreshDays)
	assert.False(t, c.Features.PushEnabled)
}

func TestLoad_Validation_MissingSecretKey_Production(t *testing.T) {
	// Test validate() directly to avoid the sync.Once mutex issue
	// that occurs when Load() resets cfgOnce inside cfgOnce.Do()
	cfg := &Config{
		Server:   ServerConfig{Debug: false},
		Security: SecurityConfig{SecretKey: ""},
	}

	err := validate(cfg)
	require.Error(t, err)
	assert.Contains(t, err.Error(), "SECRET_KEY")
}

func TestLoad_Validation_MissingSecretKey_DebugMode(t *testing.T) {
	resetConfigState()
	t.Setenv("SECRET_KEY", "")
	t.Setenv("DEBUG", "true")

	c, err := Load()
	require.NoError(t, err)
	// In debug mode, a default key is assigned
	assert.Equal(t, "change-me-in-production-secret-key-12345", c.Security.SecretKey)
}

func TestLoad_Validation_WeakSecretKey_Production(t *testing.T) {
	// Test validate() directly to avoid the sync.Once mutex issue
	cfg := &Config{
		Server:   ServerConfig{Debug: false},
		Security: SecurityConfig{SecretKey: "password"},
	}

	err := validate(cfg)
	require.Error(t, err)
	assert.Contains(t, err.Error(), "well-known weak value")
}

func TestLoad_Validation_WeakSecretKey_DebugMode(t *testing.T) {
	resetConfigState()
	t.Setenv("SECRET_KEY", "secret")
	t.Setenv("DEBUG", "true")

	// In debug mode, weak keys produce a warning but no error
	c, err := Load()
	require.NoError(t, err)
	assert.Equal(t, "secret", c.Security.SecretKey)
}

func TestLoad_Validation_EncryptionKey_Valid(t *testing.T) {
	resetConfigState()
	t.Setenv("SECRET_KEY", "a-strong-secret-key-for-tests")
	// Valid 64-char hex key (32 bytes)
	t.Setenv("STORAGE_ENCRYPTION_KEY", "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")

	c, err := Load()
	require.NoError(t, err)
	assert.Equal(t, "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", c.Storage.EncryptionKey)
}

func TestLoad_Validation_EncryptionKey_WrongLength(t *testing.T) {
	// Test validate() directly to avoid the sync.Once mutex issue
	cfg := &Config{
		Server:   ServerConfig{Debug: false},
		Security: SecurityConfig{SecretKey: "a-strong-secret-key-for-tests"},
		Storage:  StorageConfig{EncryptionKey: "tooshort"},
	}

	err := validate(cfg)
	require.Error(t, err)
	assert.Contains(t, err.Error(), "STORAGE_ENCRYPTION_KEY must be exactly 64 hex characters")
}

func TestLoad_Validation_EncryptionKey_InvalidHex(t *testing.T) {
	// Test validate() directly to avoid the sync.Once mutex issue
	cfg := &Config{
		Server:   ServerConfig{Debug: false},
		Security: SecurityConfig{SecretKey: "a-strong-secret-key-for-tests"},
		Storage:  StorageConfig{EncryptionKey: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"},
	}

	err := validate(cfg)
	require.Error(t, err)
	assert.Contains(t, err.Error(), "invalid hex")
}

func TestLoad_DatabaseURL_Override(t *testing.T) {
	resetConfigState()
	t.Setenv("SECRET_KEY", "a-strong-secret-key-for-tests")
	t.Setenv("DATABASE_URL", "postgres://myuser:mypass@dbhost:5433/mydb?sslmode=require")

	c, err := Load()
	require.NoError(t, err)

	assert.Equal(t, "dbhost", c.Database.Host)
	assert.Equal(t, 5433, c.Database.Port)
	assert.Equal(t, "myuser", c.Database.User)
	assert.Equal(t, "mypass", c.Database.Password)
	assert.Equal(t, "mydb", c.Database.Database)
	assert.Equal(t, "require", c.Database.SSLMode)
}

func TestDSN(t *testing.T) {
	d := DatabaseConfig{
		Host:     "localhost",
		Port:     5432,
		User:     "testuser",
		Password: "Password123",
		Database: "testdb",
		SSLMode:  "disable",
	}

	dsn := d.DSN()
	assert.Contains(t, dsn, "host=localhost")
	assert.Contains(t, dsn, "port=5432")
	assert.Contains(t, dsn, "user=testuser")
	assert.Contains(t, dsn, "password=Password123")
	assert.Contains(t, dsn, "dbname=testdb")
	assert.Contains(t, dsn, "sslmode=disable")
}

func TestMaskURLCredentials(t *testing.T) {
	tests := []struct {
		name     string
		input    string
		expected string
	}{
		{
			name:     "URL with password",
			input:    "postgres://user:secret@host:5432/db",
			expected: "postgres://user:xxxxx@host:5432/db",
		},
		{
			name:     "URL without password",
			input:    "postgres://user@host:5432/db",
			expected: "postgres://user@host:5432/db",
		},
		{
			name:     "URL without user info",
			input:    "postgres://host:5432/db",
			expected: "postgres://host:5432/db",
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			result := MaskURLCredentials(tc.input)
			assert.Equal(t, tc.expected, result)
		})
	}
}

func TestParseCorsOrigins(t *testing.T) {
	tests := []struct {
		name     string
		input    string
		expected []string
	}{
		{"empty string", "", nil},
		{"single origin", "https://example.com", []string{"https://example.com"}},
		{"multiple origins", "https://a.com, https://b.com", []string{"https://a.com", "https://b.com"}},
		{"whitespace trimmed", "  https://a.com , https://b.com  ", []string{"https://a.com", "https://b.com"}},
		{"empty parts skipped", "https://a.com,,https://b.com", []string{"https://a.com", "https://b.com"}},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			result := parseCorsOrigins(tc.input)
			assert.Equal(t, tc.expected, result)
		})
	}
}

func TestParseDatabaseURL(t *testing.T) {
	tests := []struct {
		name        string
		url         string
		wantHost    string
		wantPort    int
		wantUser    string
		wantPass    string
		wantDB      string
		wantSSL     string
		expectError bool
	}{
		{
			name:     "full URL",
			url:      "postgres://user:Password123@host:5433/mydb?sslmode=require",
			wantHost: "host",
			wantPort: 5433,
			wantUser: "user",
			wantPass: "Password123",
			wantDB:   "mydb",
			wantSSL:  "require",
		},
		{
			name:     "default port",
			url:      "postgres://user:pass@host/mydb",
			wantHost: "host",
			wantPort: 5432,
			wantUser: "user",
			wantPass: "pass",
			wantDB:   "mydb",
			wantSSL:  "",
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			result, err := parseDatabaseURL(tc.url)
			if tc.expectError {
				require.Error(t, err)
				return
			}
			require.NoError(t, err)
			assert.Equal(t, tc.wantHost, result.Host)
			assert.Equal(t, tc.wantPort, result.Port)
			assert.Equal(t, tc.wantUser, result.User)
			assert.Equal(t, tc.wantPass, result.Password)
			assert.Equal(t, tc.wantDB, result.Database)
			assert.Equal(t, tc.wantSSL, result.SSLMode)
		})
	}
}

func TestIsWeakSecretKey(t *testing.T) {
	assert.True(t, isWeakSecretKey("secret"))
	assert.True(t, isWeakSecretKey("Secret"))     // case-insensitive
	assert.True(t, isWeakSecretKey(" changeme ")) // whitespace trimmed
	assert.True(t, isWeakSecretKey("password"))
	assert.True(t, isWeakSecretKey("change-me"))
	assert.False(t, isWeakSecretKey("a-strong-unique-production-key"))
}

func TestGet_ReturnsNilBeforeLoad(t *testing.T) {
	resetConfigState()
	assert.Nil(t, Get())
}
