// Package config loads and validates YAML configuration for odidere.
package config

import (
	"fmt"
	"os"
	"regexp"
	"time"

	"code.chimeric.al/chimerical/odidere/internal/job"
	"code.chimeric.al/chimerical/odidere/internal/llm"
	"code.chimeric.al/chimerical/odidere/internal/stt"
	"code.chimeric.al/chimerical/odidere/internal/tool"
	"code.chimeric.al/chimerical/odidere/internal/tts"

	"gopkg.in/yaml.v3"
)

// Config holds all application configuration sections.
type Config struct {
	// Address is the host:port to listen on.
	Address string `yaml:"address"`

	// ShutdownTimeout is the maximum time to wait for graceful shutdown.
	// Defaults to "30s".
	ShutdownTimeout string `yaml:"shutdown_timeout"`
	// shutdownTimeout is the parsed duration, set during Load.
	shutdownTimeout time.Duration `yaml:"-"`

	// LLM configures the language model client.
	LLM llm.Config `yaml:"llm"`
	// STT configures the speech-to-text client.
	STT stt.Config `yaml:"stt"`
	// Jobs defines scheduled autonomous tasks.
	Jobs []job.Config `yaml:"jobs"`
	// Tools defines external commands available to the LLM.
	Tools []tool.Tool `yaml:"tools"`
	// TTS configures the text-to-speech client.
	TTS tts.Config `yaml:"tts"`
}

// ApplyDefaults sets default values for optional configuration fields.
// Called automatically by Load before validation.
func (cfg *Config) ApplyDefaults() {
	if cfg.ShutdownTimeout == "" {
		cfg.ShutdownTimeout = "30s"
	}
	if cfg.Address == "" {
		cfg.Address = ":8080"
	}
}

// GetShutdownTimeout returns the parsed shutdown timeout duration.
func (cfg *Config) GetShutdownTimeout() time.Duration {
	return cfg.shutdownTimeout
}

// Validate checks that all required fields are present and valid.
// Delegates to each subsection's Validate method.
func (cfg *Config) Validate() error {
	if cfg.Address == "" {
		return fmt.Errorf("address required")
	}

	if err := cfg.LLM.Validate(); err != nil {
		return fmt.Errorf("invalid llm config: %w", err)
	}
	if err := cfg.STT.Validate(); err != nil {
		return fmt.Errorf("invalid stt config: %w", err)
	}
	if err := cfg.TTS.Validate(); err != nil {
		return fmt.Errorf("invalid tts config: %w", err)
	}
	if _, err := time.ParseDuration(cfg.ShutdownTimeout); err != nil {
		return fmt.Errorf(
			"invalid shutdown_timeout %q: %w",
			cfg.ShutdownTimeout, err,
		)
	}
	for i, jc := range cfg.Jobs {
		if err := jc.Validate(); err != nil {
			return fmt.Errorf("jobs[%d] invalid: %w", i, err)
		}
	}
	for i, t := range cfg.Tools {
		if err := t.Validate(); err != nil {
			return fmt.Errorf("tools[%d] invalid: %w", i, err)
		}
	}

	return nil
}

// Load reads a YAML configuration file, expands environment variables,
// applies defaults, and validates the result. Returns an error if the
// file cannot be read, parsed, or contains invalid configuration.
func Load(path string) (*Config, error) {
	data, err := os.ReadFile(path)
	if err != nil {
		return nil, fmt.Errorf("read file: %w", err)
	}

	// Expand environment variables.
	// Unset variables are replaced with empty strings.
	re := regexp.MustCompile(`\$\{([^}]+)\}`)
	expanded := re.ReplaceAllStringFunc(
		string(data),
		func(match string) string {
			// Extract variable name from ${VAR}.
			v := match[2 : len(match)-1]
			return os.Getenv(v)
		},
	)

	var cfg Config
	if err := yaml.Unmarshal([]byte(expanded), &cfg); err != nil {
		return nil, fmt.Errorf("parse config: %w", err)
	}
	cfg.ApplyDefaults()

	if err := cfg.Validate(); err != nil {
		return nil, fmt.Errorf("invalid config: %v", err)
	}

	d, err := time.ParseDuration(cfg.ShutdownTimeout)
	if err != nil {
		return nil, fmt.Errorf("parse shutdown timeout: %v", err)
	}
	cfg.shutdownTimeout = d

	return &cfg, nil
}
