package config

import (
	"fmt"
	"os"
	"reflect"
	"strconv"
	"time"

	"github.com/subosito/gotenv"
)

type ServerConfig struct {
	Host         string        `env:"SERVER_HOST"`
	Port         int           `env:"SERVER_PORT"`
	Timeout      time.Duration `env:"SERVER_TIMEOUT"`
	ReadTimeout  time.Duration `env:"SERVER_READ_TIMEOUT"`
	WriteTimeout time.Duration `env:"SERVER_WRITE_TIMEOUT"`
}

type DatabaseConfig struct {
	MongoDB MongoConfig
}

type MongoConfig struct {
	URI         string        `env:"MONGODB_URI"`
	Name        string        `env:"MONGODB_NAME"`
	Timeout     time.Duration `env:"MONGODB_TIMEOUT"`
	MaxPoolSize int           `env:"MONGODB_MAX_POOL_SIZE"`
}

type CacheConfig struct {
	Redis RedisConfig
}

type RedisConfig struct {
	Host     string `env:"REDIS_HOST"`
	Port     int    `env:"REDIS_PORT"`
	Password string `env:"REDIS_PASSWORD"`
	DB       int    `env:"REDIS_DB"`
	PoolSize int    `env:"REDIS_POOL_SIZE"`
}

type LoggingConfig struct {
	Level  string `env:"LOG_LEVEL"`
	Format string `env:"LOG_FORMAT"`
	Output string `env:"LOG_OUTPUT"`
	File   LogFileConfig
}

type LogFileConfig struct {
	Path       string `env:"LOG_FILE_PATH"`
	MaxSize    int    `env:"LOG_FILE_MAX_SIZE"`
	MaxBackups int    `env:"LOG_FILE_MAX_BACKUPS"`
	MaxAge     int    `env:"LOG_FILE_MAX_AGE"`
	Compress   bool   `env:"LOG_FILE_COMPRESS"`
}

type AssetsConfig struct {
	FlagsPath string `env:"ASSETS_FLAGS_PATH"`
}

type RateLimitConfig struct {
	RequestsPerMinute int `env:"RATE_LIMIT_REQUESTS_PER_MINUTE"`
	Burst             int `env:"RATE_LIMIT_BURST"`
}

type NotificationConfig struct {
	BaseURL       string        `env:"NOTIFICATION_BASE_URL"`
	Timeout       time.Duration `env:"NOTIFICATION_TIMEOUT"`
	RetryAttempts int           `env:"NOTIFICATION_RETRY_ATTEMPTS"`
	RetryDelay    time.Duration `env:"NOTIFICATION_RETRY_DELAY"`
	EnableLogging bool          `env:"NOTIFICATION_ENABLE_LOGGING"`
	UserAgent     string        `env:"NOTIFICATION_USER_AGENT"`
}

// LoadConfig loads configuration with priority: OS environment variables > .env file
// OS environment variables take precedence over .env file values
func LoadConfig[T any](path string, config T) (T, error) {
	// First, try to load .env file (this will not override existing OS env vars)
	envPath := fmt.Sprintf("%s/.env", path)

	// Check if .env file exists before trying to load it
	if _, err := os.Stat(envPath); err == nil {
		// Load .env file - gotenv.Load respects existing environment variables
		// and will not override them with values from the .env file
		if err := gotenv.Load(envPath); err != nil {
			return config, fmt.Errorf("failed to load .env file from %s: %w", envPath, err)
		}
	}
	// If .env file doesn't exist, we continue with OS environment variables only

	// Parse configuration using reflection
	// This will use OS env vars first, then fall back to values loaded from .env
	if err := parseConfig(config); err != nil {
		return config, fmt.Errorf("failed to parse configuration: %w", err)
	}

	return config, nil
}

// parseConfig uses reflection to parse environment variables into the config struct
// Environment variables are read with the following priority:
// 1. OS environment variables (highest priority)
// 2. Values from .env file (loaded by gotenv.Load, lower priority)
func parseConfig(config interface{}) error {
	v := reflect.ValueOf(config)
	if v.Kind() != reflect.Ptr {
		return fmt.Errorf("config must be a pointer")
	}

	v = v.Elem()
	if v.Kind() != reflect.Struct {
		return fmt.Errorf("config must be a struct")
	}

	return parseStruct(v)
}

// parseStruct recursively parses struct fields
func parseStruct(v reflect.Value) error {
	t := v.Type()

	for i := 0; i < v.NumField(); i++ {
		field := v.Field(i)
		fieldType := t.Field(i)

		// Skip unexported fields
		if !field.CanSet() {
			continue
		}

		// Get env tag
		envTag := fieldType.Tag.Get("env")
		if envTag == "" {
			// If no env tag, try to parse nested struct
			if field.Kind() == reflect.Struct {
				if err := parseStruct(field); err != nil {
					return err
				}
			}
			continue
		}

		// Get environment variable value
		// os.Getenv will return OS env vars first, then values loaded from .env
		envValue := os.Getenv(envTag)
		if envValue == "" {
			continue
		}

		// Parse and set the field value
		if err := setFieldValue(field, envValue); err != nil {
			return fmt.Errorf("failed to set field %s (env: %s): %w", fieldType.Name, envTag, err)
		}
	}

	return nil
}

// setFieldValue sets the field value from environment variable string
func setFieldValue(field reflect.Value, value string) error {
	switch field.Kind() {
	case reflect.String:
		field.SetString(value)
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		if field.Type() == reflect.TypeOf(time.Duration(0)) {
			// Handle time.Duration
			duration, err := time.ParseDuration(value)
			if err != nil {
				return fmt.Errorf("invalid duration format: %s", value)
			}
			field.Set(reflect.ValueOf(duration))
		} else {
			// Handle regular integers
			intValue, err := strconv.ParseInt(value, 10, 64)
			if err != nil {
				return fmt.Errorf("invalid integer format: %s", value)
			}
			field.SetInt(intValue)
		}
	case reflect.Bool:
		boolValue, err := strconv.ParseBool(value)
		if err != nil {
			return fmt.Errorf("invalid boolean format: %s", value)
		}
		field.SetBool(boolValue)
	case reflect.Float32, reflect.Float64:
		floatValue, err := strconv.ParseFloat(value, 64)
		if err != nil {
			return fmt.Errorf("invalid float format: %s", value)
		}
		field.SetFloat(floatValue)
	default:
		return fmt.Errorf("unsupported field type: %s", field.Kind())
	}

	return nil
}

// GetEnvWithPriority gets environment variable with explicit priority handling
// Returns the value and a boolean indicating the source (true = OS env, false = .env file)
func GetEnvWithPriority(key string) (string, bool) {
	// Check if the value exists in OS environment
	if value, exists := os.LookupEnv(key); exists {
		return value, true // From OS environment
	}

	// If not in OS environment, it might be from .env file
	if value := os.Getenv(key); value != "" {
		return value, false // From .env file
	}

	return "", false
}

// Helper function to get environment variable with default value
func getEnv(key, defaultValue string) string {
	if value := os.Getenv(key); value != "" {
		return value
	}
	return defaultValue
}

// Helper function to get environment variable as int with default value
func getEnvAsInt(key string, defaultValue int) int {
	if value := os.Getenv(key); value != "" {
		if intValue, err := strconv.Atoi(value); err == nil {
			return intValue
		}
	}
	return defaultValue
}

// Helper function to get environment variable as duration with default value
func getEnvAsDuration(key string, defaultValue time.Duration) time.Duration {
	if value := os.Getenv(key); value != "" {
		if duration, err := time.ParseDuration(value); err == nil {
			return duration
		}
	}
	return defaultValue
}

// Helper function to get environment variable as bool with default value
func getEnvAsBool(key string, defaultValue bool) bool {
	if value := os.Getenv(key); value != "" {
		if boolValue, err := strconv.ParseBool(value); err == nil {
			return boolValue
		}
	}
	return defaultValue
}
