// Package config provides configuration management for AutoSSO.
// This file implements configuration loading from YAML files with
// environment variable override and default value support.
package config

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

	"gopkg.in/yaml.v3"
)

// Load loads configuration from a YAML file and applies environment variable
// overrides and default values.
//
// The loading process follows this order:
// 1. Apply default values from struct tags
// 2. Load values from YAML file (overrides defaults)
// 3. Apply environment variable overrides (highest priority)
//
// Parameters:
//   - configPath: Path to the YAML configuration file. If empty, uses
//     CONFIG_PATH environment variable, or defaults to "config/config.yaml"
//
// Returns:
//   - *Config: The loaded configuration
//   - error: Any error that occurred during loading
//
// Requirements: 2.1-2.8, 9.2, 9.4, 9.7
func Load(configPath string) (*Config, error) {
	// Determine config file path
	if configPath == "" {
		configPath = os.Getenv("CONFIG_PATH")
		if configPath == "" {
			configPath = "config/config.yaml"
		}
	}

	// Create config with default values
	cfg := &Config{}
	if err := applyDefaults(cfg); err != nil {
		return nil, fmt.Errorf("failed to apply default values: %w", err)
	}

	// Load from YAML file
	if err := loadFromYAML(configPath, cfg); err != nil {
		return nil, err
	}

	// Apply environment variable overrides
	if err := applyEnvOverrides(cfg); err != nil {
		return nil, fmt.Errorf("failed to apply environment overrides: %w", err)
	}

	return cfg, nil
}

// loadFromYAML loads configuration from a YAML file.
func loadFromYAML(path string, cfg *Config) error {
	data, err := os.ReadFile(path)
	if err != nil {
		if os.IsNotExist(err) {
			return fmt.Errorf("configuration file not found: %s", path)
		}
		return fmt.Errorf("failed to read configuration file: %w", err)
	}

	// Expand environment variables in YAML content
	expandedData := os.ExpandEnv(string(data))

	if err := yaml.Unmarshal([]byte(expandedData), cfg); err != nil {
		return fmt.Errorf("failed to parse configuration file: %w", err)
	}

	return nil
}

// applyDefaults applies default values from struct tags to the configuration.
// It recursively processes all struct fields looking for "default" tags.
func applyDefaults(v interface{}) error {
	return applyDefaultsToValue(reflect.ValueOf(v))
}

// applyDefaultsToValue recursively applies default values to a reflect.Value.
func applyDefaultsToValue(v reflect.Value) error {
	// Handle pointers
	if v.Kind() == reflect.Ptr {
		if v.IsNil() {
			return nil
		}
		return applyDefaultsToValue(v.Elem())
	}

	// Only process structs
	if v.Kind() != reflect.Struct {
		return nil
	}

	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 default tag value
		defaultVal := fieldType.Tag.Get("default")

		// Recursively process nested structs
		if field.Kind() == reflect.Struct {
			if err := applyDefaultsToValue(field); err != nil {
				return err
			}
			continue
		}

		// Apply default value if tag exists and field is zero value
		if defaultVal != "" && isZeroValue(field) {
			if err := setFieldValue(field, defaultVal); err != nil {
				return fmt.Errorf("failed to set default for field %s: %w", fieldType.Name, err)
			}
		}

		// Process slices of structs
		if field.Kind() == reflect.Slice && field.Type().Elem().Kind() == reflect.Struct {
			for j := 0; j < field.Len(); j++ {
				if err := applyDefaultsToValue(field.Index(j)); err != nil {
					return err
				}
			}
		}
	}

	return nil
}

// applyEnvOverrides applies environment variable overrides to the configuration.
// It looks for "env" tags on struct fields and overrides values with environment
// variables if they are set.
func applyEnvOverrides(v interface{}) error {
	return applyEnvOverridesToValue(reflect.ValueOf(v))
}

// applyEnvOverridesToValue recursively applies environment variable overrides.
func applyEnvOverridesToValue(v reflect.Value) error {
	// Handle pointers
	if v.Kind() == reflect.Ptr {
		if v.IsNil() {
			return nil
		}
		return applyEnvOverridesToValue(v.Elem())
	}

	// Only process structs
	if v.Kind() != reflect.Struct {
		return nil
	}

	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
		}

		// Recursively process nested structs
		if field.Kind() == reflect.Struct {
			if err := applyEnvOverridesToValue(field); err != nil {
				return err
			}
			continue
		}

		// Get env tag value
		envVar := fieldType.Tag.Get("env")
		if envVar == "" {
			continue
		}

		// Check if environment variable is set
		envValue := os.Getenv(envVar)
		if envValue == "" {
			continue
		}

		// Apply environment variable value
		if err := setFieldValue(field, envValue); err != nil {
			return fmt.Errorf("failed to set env override for field %s from %s: %w",
				fieldType.Name, envVar, err)
		}

		// Process slices of structs
		if field.Kind() == reflect.Slice && field.Type().Elem().Kind() == reflect.Struct {
			for j := 0; j < field.Len(); j++ {
				if err := applyEnvOverridesToValue(field.Index(j)); err != nil {
					return err
				}
			}
		}
	}

	return nil
}

// setFieldValue sets a field value from a string representation.
// It supports string, int, bool, time.Duration, and slice types.
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:
		// Special handling for time.Duration
		if field.Type() == reflect.TypeOf(time.Duration(0)) {
			d, err := time.ParseDuration(value)
			if err != nil {
				return fmt.Errorf("invalid duration value %q: %w", value, err)
			}
			field.SetInt(int64(d))
		} else {
			i, err := strconv.ParseInt(value, 10, 64)
			if err != nil {
				return fmt.Errorf("invalid integer value %q: %w", value, err)
			}
			field.SetInt(i)
		}

	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		u, err := strconv.ParseUint(value, 10, 64)
		if err != nil {
			return fmt.Errorf("invalid unsigned integer value %q: %w", value, err)
		}
		field.SetUint(u)

	case reflect.Float32, reflect.Float64:
		f, err := strconv.ParseFloat(value, 64)
		if err != nil {
			return fmt.Errorf("invalid float value %q: %w", value, err)
		}
		field.SetFloat(f)

	case reflect.Bool:
		b, err := strconv.ParseBool(value)
		if err != nil {
			return fmt.Errorf("invalid boolean value %q: %w", value, err)
		}
		field.SetBool(b)

	case reflect.Slice:
		// Handle string slices (comma-separated)
		if field.Type().Elem().Kind() == reflect.String {
			parts := strings.Split(value, ",")
			for i := range parts {
				parts[i] = strings.TrimSpace(parts[i])
			}
			field.Set(reflect.ValueOf(parts))
		} else {
			return fmt.Errorf("unsupported slice element type: %v", field.Type().Elem().Kind())
		}

	default:
		return fmt.Errorf("unsupported field type: %v", field.Kind())
	}

	return nil
}

// isZeroValue checks if a reflect.Value is the zero value for its type.
func isZeroValue(v reflect.Value) bool {
	switch v.Kind() {
	case reflect.String:
		return v.String() == ""
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		return v.Int() == 0
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		return v.Uint() == 0
	case reflect.Float32, reflect.Float64:
		return v.Float() == 0
	case reflect.Bool:
		return !v.Bool()
	case reflect.Slice, reflect.Map:
		return v.IsNil() || v.Len() == 0
	case reflect.Ptr, reflect.Interface:
		return v.IsNil()
	default:
		return false
	}
}
