package config

import (
	"errors"
	"fmt"
	"os"
	"strings"

	"github.com/go-viper/mapstructure/v2"
	"github.com/spf13/viper"
)

// Config is the root configuration for vecna.
type Config struct {
	Server    ServerConfig              `mapstructure:"server"     json:"server"     yaml:"server"     xml:"server"`
	Metrics   MetricsConfig             `mapstructure:"metrics"    json:"metrics"    yaml:"metrics"    xml:"metrics"`
	Forward   ForwardConfig             `mapstructure:"forward"    json:"forward"    yaml:"forward"    xml:"forward"`
	Adapter   AdapterConfig             `mapstructure:"adapter"    json:"adapter"    yaml:"adapter"    xml:"adapter"`
	ExtraMaps map[string]ExtraMapConfig `mapstructure:"extra_maps" json:"extra_maps" yaml:"extra_maps" xml:"extra_maps"`
}

// ServerConfig controls the HTTP listener and inbound auth.
type ServerConfig struct {
	Port    int      `mapstructure:"port"     json:"port"     yaml:"port"     xml:"port"`
	Host    string   `mapstructure:"host"     json:"host"     yaml:"host"     xml:"host"`
	APIKeys []string `mapstructure:"api_keys" json:"api_keys" yaml:"api_keys" xml:"api_keys"`
}

// MetricsConfig controls Prometheus metrics exposure.
type MetricsConfig struct {
	Enabled bool   `mapstructure:"enabled" json:"enabled" yaml:"enabled" xml:"enabled"`
	Path    string `mapstructure:"path"    json:"path"    yaml:"path"    xml:"path"`
	APIKey  string `mapstructure:"api_key" json:"api_key" yaml:"api_key" xml:"api_key"`
}

// ForwardConfig holds all named forwarding targets.
type ForwardConfig struct {
	Default string                   `mapstructure:"default" json:"default" yaml:"default" xml:"default"`
	Targets map[string]ForwardTarget `mapstructure:"targets" json:"targets" yaml:"targets" xml:"targets"`
}

// ForwardTarget is a named backing embedding model with one or more endpoints.
type ForwardTarget struct {
	Endpoints        []EndpointConfig `mapstructure:"endpoints"         json:"endpoints"         yaml:"endpoints"         xml:"endpoints"`
	Model            string           `mapstructure:"model"             json:"model"             yaml:"model"             xml:"model"`
	APIKey           string           `mapstructure:"api_key"           json:"api_key"           yaml:"api_key"           xml:"api_key"`
	APIType          string           `mapstructure:"api_type"          json:"api_type"          yaml:"api_type"          xml:"api_type"`
	TimeoutSecs      int              `mapstructure:"timeout_secs"      json:"timeout_secs"      yaml:"timeout_secs"      xml:"timeout_secs"`
	CooldownSecs     int              `mapstructure:"cooldown_secs"     json:"cooldown_secs"     yaml:"cooldown_secs"     xml:"cooldown_secs"`
	PriorityDecay    int              `mapstructure:"priority_decay"    json:"priority_decay"    yaml:"priority_decay"    xml:"priority_decay"`
	PriorityRecovery int              `mapstructure:"priority_recovery" json:"priority_recovery" yaml:"priority_recovery" xml:"priority_recovery"`
}

// EndpointConfig is a single URL within a ForwardTarget.
type EndpointConfig struct {
	URL      string `mapstructure:"url"      json:"url"      yaml:"url"      xml:"url"`
	Priority int    `mapstructure:"priority" json:"priority" yaml:"priority" xml:"priority"`
	APIKey   string `mapstructure:"api_key"  json:"api_key"  yaml:"api_key"  xml:"api_key"`
}

// ExtraMapConfig is a per-mapping override: all adapter fields fall back to the
// global adapter when unset; ForwardTarget selects a named target from
// forward.targets (empty = global forward.default).
type ExtraMapConfig struct {
	ForwardTarget string `mapstructure:"forward_target" json:"forward_target,omitempty" yaml:"forward_target,omitempty" xml:"forward_target,omitempty"`
	Type          string `mapstructure:"type"           json:"type,omitempty"          yaml:"type,omitempty"          xml:"type,omitempty"`
	SourceDim     int    `mapstructure:"source_dim"     json:"source_dim,omitempty"    yaml:"source_dim,omitempty"    xml:"source_dim,omitempty"`
	TargetDim     int    `mapstructure:"target_dim"     json:"target_dim,omitempty"    yaml:"target_dim,omitempty"    xml:"target_dim,omitempty"`
	TruncateMode  string `mapstructure:"truncate_mode"  json:"truncate_mode,omitempty" yaml:"truncate_mode,omitempty" xml:"truncate_mode,omitempty"`
	PadMode       string `mapstructure:"pad_mode"       json:"pad_mode,omitempty"      yaml:"pad_mode,omitempty"      xml:"pad_mode,omitempty"`
	Seed          int64  `mapstructure:"seed"           json:"seed,omitempty"          yaml:"seed,omitempty"          xml:"seed,omitempty"`
	MatrixFile    string `mapstructure:"matrix_file"    json:"matrix_file,omitempty"   yaml:"matrix_file,omitempty"   xml:"matrix_file,omitempty"`
}

// AdapterConfig selects and tunes the dimension adapter.
type AdapterConfig struct {
	Type         string `mapstructure:"type"          json:"type"          yaml:"type"          xml:"type"`
	SourceDim    int    `mapstructure:"source_dim"    json:"source_dim"    yaml:"source_dim"    xml:"source_dim"`
	TargetDim    int    `mapstructure:"target_dim"    json:"target_dim"    yaml:"target_dim"    xml:"target_dim"`
	TruncateMode string `mapstructure:"truncate_mode" json:"truncate_mode" yaml:"truncate_mode" xml:"truncate_mode"`
	PadMode      string `mapstructure:"pad_mode"      json:"pad_mode"      yaml:"pad_mode"      xml:"pad_mode"`
	Seed         int64  `mapstructure:"seed"          json:"seed"          yaml:"seed"          xml:"seed"`
	MatrixFile   string `mapstructure:"matrix_file"   json:"matrix_file"   yaml:"matrix_file"   xml:"matrix_file"`
}

// extensions viper will detect automatically.
var extensions = []string{"json", "yaml", "toml"}

// ResolveFile returns the config file path that would be used by Load.
// If cfgFile is non-empty it is returned as-is.
// Otherwise the default search paths are checked; if no existing file is found,
// the preferred default (~/.vecna.json) is returned so callers can create it.
func ResolveFile(cfgFile string) string {
	if cfgFile != "" {
		return cfgFile
	}
	home, _ := os.UserHomeDir()
	dirs := []string{"/config", ".", home, home + "/.config/vecna"}
	for _, dir := range dirs {
		for _, ext := range extensions {
			path := dir + "/vecna." + ext
			if _, err := os.Stat(path); err == nil {
				return path
			}
		}
	}
	return home + "/vecna.json"
}

// Load reads configuration from the given file path (empty = search defaults),
// environment variables (prefix VECNA_), and applies built-in defaults.
func Load(cfgFile string) (*Config, error) {
	v := viper.New()

	// Defaults
	v.SetDefault("server.port", 8080)
	v.SetDefault("server.host", "0.0.0.0")
	v.SetDefault("metrics.enabled", false)
	v.SetDefault("metrics.path", "/metrics")
	v.SetDefault("adapter.type", "truncate")
	v.SetDefault("adapter.truncate_mode", "from_end")
	v.SetDefault("adapter.pad_mode", "at_end")

	// Environment
	v.SetEnvPrefix("VECNA")
	v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
	v.AutomaticEnv()

	// Config file
	if cfgFile != "" {
		v.SetConfigFile(cfgFile)
	} else {
		home, _ := os.UserHomeDir()
		v.SetConfigName("vecna")
		// No SetConfigType — viper detects format from file extension (json, yaml, toml, etc.)
		v.AddConfigPath("/config")
		v.AddConfigPath(".")
		v.AddConfigPath(home)
		v.AddConfigPath(home + "/.config/vecna")
	}

	if err := v.ReadInConfig(); err != nil {
		// Missing config file is acceptable when all required values come from flags/env
		var notFound viper.ConfigFileNotFoundError
		if !errors.As(err, &notFound) {
			return nil, fmt.Errorf("load config: %w", err)
		}
	}

	var cfg Config
	if err := v.Unmarshal(&cfg, func(dc *mapstructure.DecoderConfig) {
		// Viper lowercases all keys (e.g. "SourceDim" → "sourcedim"), but
		// mapstructure tags use snake_case ("source_dim"). Strip underscores
		// before comparing so both forms resolve correctly.
		dc.MatchName = func(mapKey, fieldName string) bool {
			norm := func(s string) string {
				return strings.ToLower(strings.ReplaceAll(s, "_", ""))
			}
			return norm(mapKey) == norm(fieldName)
		}
	}); err != nil {
		return nil, fmt.Errorf("unmarshal config: %w", err)
	}

	applyForwardDefaults(&cfg)

	return &cfg, nil
}

// applyForwardDefaults fills in zero-value fields on ForwardTarget entries.
func applyForwardDefaults(cfg *Config) {
	for name, t := range cfg.Forward.Targets {
		if t.TimeoutSecs == 0 {
			t.TimeoutSecs = 30
		}
		if t.CooldownSecs == 0 {
			t.CooldownSecs = 60
		}
		if t.PriorityDecay == 0 {
			t.PriorityDecay = 2
		}
		if t.PriorityRecovery == 0 {
			t.PriorityRecovery = 5
		}
		for i, ep := range t.Endpoints {
			if ep.Priority == 0 {
				t.Endpoints[i].Priority = 10
			}
			if ep.APIKey == "" && t.APIKey != "" {
				t.Endpoints[i].APIKey = t.APIKey
			}
		}
		cfg.Forward.Targets[name] = t
	}
}
