// Package config loads the optional layered user configuration and resolves
// settings per AGENTS.md §3.
package config

import (
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"strings"

	"gopkg.in/yaml.v3"
)

// SystemPath is the system-wide configuration file (§3).
const SystemPath = "/etc/nest/nest.yaml"

// Config holds the merged user configuration. Empty fields mean "unset".
type Config struct {
	Runtime        string `yaml:"runtime"`
	DefaultMode    string `yaml:"default_mode"`
	PersistenceDir string `yaml:"persistence_dir"`
	CatalogURL     string `yaml:"catalog_url"`
}

// DefaultPaths returns the system and per-user config file locations, honoring
// XDG_CONFIG_HOME.
func DefaultPaths(getenv func(string) string) (system, user string) {
	configHome := getenv("XDG_CONFIG_HOME")
	if configHome == "" {
		configHome = filepath.Join(getenv("HOME"), ".config")
	}
	return SystemPath, filepath.Join(configHome, "nest", "nest.yaml")
}

// Load reads the system and user config files, user overriding system (§3).
// Missing files are fine; a malformed file is a fatal error naming the file.
func Load(system, user string) (*Config, error) {
	merged := &Config{}
	for _, path := range []string{system, user} {
		file, err := loadFile(path)
		if err != nil {
			if errors.Is(err, os.ErrNotExist) {
				continue
			}
			return nil, err
		}
		merged.merge(file)
	}
	return merged, nil
}

func loadFile(path string) (*Config, error) {
	data, err := os.ReadFile(path)
	if err != nil {
		return nil, err
	}
	var c Config
	if err := yaml.Unmarshal(data, &c); err != nil {
		return nil, fmt.Errorf("malformed config file %s: %w", path, err)
	}
	if err := c.validate(); err != nil {
		return nil, fmt.Errorf("invalid config file %s: %w", path, err)
	}
	return &c, nil
}

func (c *Config) validate() error {
	switch c.Runtime {
	case "", "auto", "podman", "docker":
	default:
		return fmt.Errorf("runtime must be auto, podman or docker (got %q)", c.Runtime)
	}
	switch c.DefaultMode {
	case "", "local", "container":
	default:
		return fmt.Errorf("default_mode must be local or container (got %q)", c.DefaultMode)
	}
	return nil
}

// merge applies non-empty fields of other on top of c.
func (c *Config) merge(other *Config) {
	if other.Runtime != "" {
		c.Runtime = other.Runtime
	}
	if other.DefaultMode != "" {
		c.DefaultMode = other.DefaultMode
	}
	if other.PersistenceDir != "" {
		c.PersistenceDir = other.PersistenceDir
	}
	if other.CatalogURL != "" {
		c.CatalogURL = other.CatalogURL
	}
}

// ResolveRuntime returns flag if set, else the configured runtime, else "auto"
// (§3.1).
func (c *Config) ResolveRuntime(flag string) string {
	if flag != "" {
		return flag
	}
	if c.Runtime != "" {
		return c.Runtime
	}
	return "auto"
}

// ResolveMode returns the run mode: explicit flags, else default_mode, else
// "local" (§3.1). Conflicting flags are a usage error.
func (c *Config) ResolveMode(local, container bool) (string, error) {
	if local && container {
		return "", fmt.Errorf("--local and --container are mutually exclusive")
	}
	if local {
		return "local", nil
	}
	if container {
		return "container", nil
	}
	if c.DefaultMode != "" {
		return c.DefaultMode, nil
	}
	return "local", nil
}

// ResolvePersistenceDir returns NEST_PERSISTENCE_DIR, else the configured
// directory, else ~/nest/persistence, with ~ expanded (§3.1).
func (c *Config) ResolvePersistenceDir(getenv func(string) string) string {
	dir := getenv("NEST_PERSISTENCE_DIR")
	if dir == "" {
		dir = c.PersistenceDir
	}
	if dir == "" {
		dir = "~/nest/persistence"
	}
	return expandTilde(dir, getenv("HOME"))
}

// ResolveCatalogURL returns NEST_CATALOG_URL, else the configured URL, else
// the pinned default (§3.1).
func (c *Config) ResolveCatalogURL(getenv func(string) string, pinned string) string {
	if url := getenv("NEST_CATALOG_URL"); url != "" {
		return url
	}
	if c.CatalogURL != "" {
		return c.CatalogURL
	}
	return pinned
}

func expandTilde(path, home string) string {
	if path == "~" {
		return home
	}
	if strings.HasPrefix(path, "~/") {
		return filepath.Join(home, path[2:])
	}
	return path
}
