package config

import (
	"fmt"
	"os"
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestDetectEnvironment(t *testing.T) {
	tests := []struct {
		name      string
		setupFunc func()
		expected  string
	}{
		{
			name: "APP_ENV takes priority",
			setupFunc: func() {
				os.Setenv("APP_ENV", "production")
				os.Setenv("NODE_ENV", "development")
				os.Setenv("GO_ENV", "staging")
			},
			expected: EnvProduction,
		},
		{
			name: "NODE_ENV fallback",
			setupFunc: func() {
				os.Unsetenv("APP_ENV")
				os.Setenv("NODE_ENV", "staging")
				os.Unsetenv("GO_ENV")
			},
			expected: EnvStaging,
		},
		{
			name: "GO_ENV fallback",
			setupFunc: func() {
				os.Unsetenv("APP_ENV")
				os.Unsetenv("NODE_ENV")
				os.Setenv("GO_ENV", "test")
			},
			expected: EnvTest,
		},
		{
			name: "default to development",
			setupFunc: func() {
				os.Unsetenv("APP_ENV")
				os.Unsetenv("NODE_ENV")
				os.Unsetenv("GO_ENV")
			},
			expected: EnvDevelopment,
		},
		{
			name: "invalid APP_ENV falls back to NODE_ENV",
			setupFunc: func() {
				os.Setenv("APP_ENV", "invalid")
				os.Setenv("NODE_ENV", "production")
				os.Unsetenv("GO_ENV")
			},
			expected: EnvProduction,
		},
		{
			name: "case insensitive",
			setupFunc: func() {
				os.Setenv("APP_ENV", "PRODUCTION")
				os.Unsetenv("NODE_ENV")
				os.Unsetenv("GO_ENV")
			},
			expected: EnvProduction,
		},
		{
			name: "whitespace trimmed",
			setupFunc: func() {
				os.Setenv("APP_ENV", "  production  ")
				os.Unsetenv("NODE_ENV")
				os.Unsetenv("GO_ENV")
			},
			expected: EnvProduction,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			tt.setupFunc()
			defer func() {
				os.Unsetenv("APP_ENV")
				os.Unsetenv("NODE_ENV")
				os.Unsetenv("GO_ENV")
			}()

			result := DetectEnvironment()
			fmt.Println("TestDetectEnvironment/whitespace_trimmed - Detected Environment:", result)
			assert.Equal(t, tt.expected, result)
		})
	}
}

func TestNormalizeEnvironment(t *testing.T) {
	tests := []struct {
		input    string
		expected string
	}{
		{"dev", EnvDevelopment},
		{"prod", EnvProduction},
		{"stage", EnvStaging},
		{"stg", EnvStaging},
		{"test", EnvTest},
		{"local", EnvDevelopment},
		{"development", EnvDevelopment},
		{"production", EnvProduction},
		{"staging", EnvStaging},
		{"invalid", EnvDevelopment},
		{"", EnvDevelopment},
		{"  dev  ", EnvDevelopment},
		{"PROD", EnvProduction},
		{"STAGE", EnvStaging},
	}

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

func TestIsValidEnvironment(t *testing.T) {
	tests := []struct {
		name     string
		env      string
		expected bool
	}{
		{"valid development", EnvDevelopment, true},
		{"valid staging", EnvStaging, true},
		{"valid production", EnvProduction, true},
		{"valid test", EnvTest, true},
		{"invalid", "invalid", false},
		{"empty", "", false},
		{"case insensitive", "PRODUCTION", true},
		{"with whitespace", "  production  ", true},
		{"dev alias", "dev", false},   // Dev n'est pas valide directement, doit être normalisé
		{"prod alias", "prod", false}, // Prod n'est pas valide directement, doit être normalisé
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := isValidEnvironment(tt.env)
			assert.Equal(t, tt.expected, result, "Environment %s should be valid: %v", tt.env, tt.expected)
		})
	}
}

func TestDetectEnvironment_Priority(t *testing.T) {
	// Test que APP_ENV a la plus haute priorité
	os.Setenv("APP_ENV", "production")
	os.Setenv("NODE_ENV", "staging")
	os.Setenv("GO_ENV", "development")
	defer func() {
		os.Unsetenv("APP_ENV")
		os.Unsetenv("NODE_ENV")
		os.Unsetenv("GO_ENV")
	}()

	result := DetectEnvironment()
	assert.Equal(t, EnvProduction, result, "APP_ENV should have highest priority")
}

func TestDetectEnvironment_AllEnvironments(t *testing.T) {
	environments := []string{EnvDevelopment, EnvStaging, EnvProduction, EnvTest}

	for _, env := range environments {
		t.Run(env, func(t *testing.T) {
			os.Setenv("APP_ENV", env)
			defer os.Unsetenv("APP_ENV")

			result := DetectEnvironment()
			assert.Equal(t, env, result)
		})
	}
}

func TestNormalizeEnvironment_CanonicalNames(t *testing.T) {
	// Les noms canoniques doivent rester inchangés
	canonicalNames := []string{
		EnvDevelopment,
		EnvStaging,
		EnvProduction,
		EnvTest,
	}

	for _, name := range canonicalNames {
		t.Run(name, func(t *testing.T) {
			result := NormalizeEnvironment(name)
			assert.Equal(t, name, result, "Canonical name should remain unchanged")
		})
	}
}

func TestNormalizeEnvironment_Aliases(t *testing.T) {
	aliasTests := []struct {
		alias    string
		expected string
	}{
		{"dev", EnvDevelopment},
		{"local", EnvDevelopment},
		{"prod", EnvProduction},
		{"stage", EnvStaging},
		{"stg", EnvStaging},
		{"test", EnvTest},
	}

	for _, tt := range aliasTests {
		t.Run(tt.alias, func(t *testing.T) {
			result := NormalizeEnvironment(tt.alias)
			assert.Equal(t, tt.expected, result, "Alias %s should normalize to %s", tt.alias, tt.expected)
		})
	}
}

func TestConstants(t *testing.T) {
	// Vérifier que les constantes sont définies correctement
	assert.Equal(t, "development", EnvDevelopment)
	assert.Equal(t, "staging", EnvStaging)
	assert.Equal(t, "production", EnvProduction)
	assert.Equal(t, "test", EnvTest)
}

func TestDetectEnvironment_InvalidEnvFallback(t *testing.T) {
	// Test que les environnements invalides ne sont pas utilisés
	os.Setenv("APP_ENV", "invalid_env")
	os.Setenv("NODE_ENV", "also_invalid")
	os.Setenv("GO_ENV", "still_invalid")
	defer func() {
		os.Unsetenv("APP_ENV")
		os.Unsetenv("NODE_ENV")
		os.Unsetenv("GO_ENV")
	}()

	result := DetectEnvironment()
	// Devrait fallback sur hostname ou development
	assert.Contains(t, []string{EnvDevelopment, EnvStaging, EnvProduction}, result)
}
