package config

import (
	"errors"
	"fmt"
	"os"
	"reflect"
	"strconv"
	"strings"
)

type AppConfig struct {
	Port        string `env:"GUARD_PORT" default:"3001"`
	Host        string `env:"GUARD_HOST" default:"127.0.0.1"`
	Uri         string `env:"GUARD_URI" default:"http://127.0.0.1:3001"`
	DatabaseURL string `env:"GUARD_DB_URL" required:"true"`
	RedisURL    string `env:"GUARD_REDIS_URL" default:"redis://localhost:6379/0"`
	Admin       AdminConfig
	Jwt         JwtConfig
	Minio       MinioConfig
}

func LoadEnv(target any) error {
	v := reflect.ValueOf(target)
	if v.Kind() != reflect.Pointer || v.Elem().Kind() != reflect.Struct {
		return &InvalidTargetError{}
	}
	return loadStruct(v.Elem(), "")
}

type InvalidTargetError struct{}

func (e *InvalidTargetError) Error() string {
	return "target must be a pointer to a struct"
}

var ErrMissingRequiredEnv = errors.New("missing required environment variable")

func loadStruct(v reflect.Value, prefix string) error {
	t := v.Type()

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

		if !valueField.CanSet() {
			continue
		}

		envKey := field.Tag.Get("env")
		if envKey == "" {
			envKey = strings.ToUpper(prefix + "_" + field.Name)
			envKey = strings.TrimPrefix(envKey, "_")
		}

		required := field.Tag.Get("required") == "true"
		defaultVal := field.Tag.Get("default")

		if field.Type.Kind() == reflect.Struct {
			err := loadStruct(valueField, envKey)
			if err != nil {
				return err
			}
			continue
		}

		envVal := os.Getenv(envKey)

		if envVal == "" {
			if defaultVal != "" {
				envVal = defaultVal
			} else if required {
				return fmt.Errorf("%w: %s", ErrMissingRequiredEnv, envKey)
			} else {
				continue
			}
		}

		switch field.Type.Kind() {
		case reflect.String:
			valueField.SetString(envVal)
		case reflect.Int, reflect.Int64:
			i, err := strconv.ParseInt(envVal, 10, 64)
			if err != nil {
				return fmt.Errorf("invalid int for %s: %w", envKey, err)
			}
			valueField.SetInt(i)
		case reflect.Bool:
			b, err := strconv.ParseBool(envVal)
			if err != nil {
				return fmt.Errorf("invalid bool for %s: %w", envKey, err)
			}
			valueField.SetBool(b)
		default:
			return fmt.Errorf("unsupported type for field %s", field.Name)
		}
	}

	return nil
}
