// Package config provides generic YAML config loading with env override support.
package config

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

	"gopkg.in/yaml.v3"
)

// LoadYAML reads a YAML file at path and unmarshals it into T.
// If path is empty or the file does not exist and optional is true, the zero value of T is returned.
func LoadYAML[T any](path string, optional bool) (T, error) {
	var zero T
	if path == "" {
		if optional {
			return zero, nil
		}
		return zero, fmt.Errorf("config path is empty")
	}

	data, err := os.ReadFile(path)
	if err != nil {
		if optional && os.IsNotExist(err) {
			return zero, nil
		}
		return zero, fmt.Errorf("reading config file %q: %w", path, err)
	}

	var cfg T
	if err := yaml.Unmarshal(data, &cfg); err != nil {
		return zero, fmt.Errorf("parsing config file %q: %w", path, err)
	}

	return cfg, nil
}

// OverrideFromEnv applies environment variable overrides to a struct using struct tags.
// Tag format: env:"ENV_VAR_NAME"
// Supports field types: string, int, bool, []string (comma-separated).
// envPrefix is prepended to tag values if non-empty (e.g. envPrefix="APP" + tag="PORT" → "APP_PORT").
func OverrideFromEnv[T any](cfg *T, envPrefix string) error {
	return overrideStruct(reflect.ValueOf(cfg).Elem(), envPrefix)
}

func overrideStruct(v reflect.Value, envPrefix string) error {
	t := v.Type()
	for i := 0; i < t.NumField(); i++ {
		field := t.Field(i)
		fv := v.Field(i)

		if !fv.CanSet() {
			continue
		}

		// Recurse into embedded/nested structs
		if fv.Kind() == reflect.Struct {
			if err := overrideStruct(fv, envPrefix); err != nil {
				return err
			}
			continue
		}

		envTag := field.Tag.Get("env")
		if envTag == "" {
			continue
		}

		envKey := envTag
		if envPrefix != "" {
			envKey = envPrefix + "_" + envTag
		}

		val := os.Getenv(envKey)
		if val == "" {
			continue
		}

		switch fv.Kind() {
		case reflect.String:
			fv.SetString(val)
		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
			n, err := strconv.ParseInt(val, 10, 64)
			if err != nil {
				return fmt.Errorf("env %s: cannot parse %q as int: %w", envKey, val, err)
			}
			fv.SetInt(n)
		case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
			n, err := strconv.ParseUint(val, 10, 64)
			if err != nil {
				return fmt.Errorf("env %s: cannot parse %q as uint: %w", envKey, val, err)
			}
			fv.SetUint(n)
		case reflect.Bool:
			b, err := strconv.ParseBool(val)
			if err != nil {
				return fmt.Errorf("env %s: cannot parse %q as bool: %w", envKey, val, err)
			}
			fv.SetBool(b)
		case reflect.Slice:
			if fv.Type().Elem().Kind() == reflect.String {
				parts := strings.Split(val, ",")
				for i, p := range parts {
					parts[i] = strings.TrimSpace(p)
				}
				fv.Set(reflect.ValueOf(parts))
			}
		}
	}
	return nil
}
