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

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

	"code.chimeric.al/chimerical/raven/internal/filter"
	"code.chimeric.al/chimerical/raven/internal/imap"
	"code.chimeric.al/chimerical/raven/internal/intake"
	"code.chimeric.al/chimerical/raven/internal/llm"
	"code.chimeric.al/chimerical/raven/internal/smtp"
	"code.chimeric.al/chimerical/raven/internal/tool"

	"gopkg.in/yaml.v3"
)

// Config holds all application configuration sections.
type Config struct {
	// Concurrency is the number of concurrent answer workers.
	// Defaults to 1.
	Concurrency int `yaml:"concurrency"`
	// 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:"-"`

	// Filters configures server-side IMAP search criteria.
	Filters filter.Filters `yaml:"filters"`
	// IMAP configures the mail server connection for reading messages.
	IMAP imap.Config `yaml:"imap"`
	// Intake configures the message retrieval strategy (IDLE or poll).
	Intake intake.Config `yaml:"intake"`
	// LLM configures the language model client.
	LLM llm.Config `yaml:"llm"`
	// SMTP configures the mail server connection for sending responses.
	// Credentials default to IMAP values if not specified.
	SMTP smtp.SMTP `yaml:"smtp"`
	// Tools defines external commands available to the LLM.
	Tools []tool.Tool `yaml:"tools"`
}

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

	// Intake
	if cfg.Intake.Mode == "" {
		cfg.Intake.Mode = intake.ModePoll
	}
	if cfg.Intake.PollInterval == "" {
		cfg.Intake.PollInterval = "30s"
	}

	// SMTP defaults to IMAP credentials.
	if cfg.SMTP.User == "" {
		cfg.SMTP.User = cfg.IMAP.User
	}
	if cfg.SMTP.Password == "" {
		cfg.SMTP.Password = cfg.IMAP.Password
	}
	if cfg.SMTP.From == "" {
		cfg.SMTP.From = cfg.IMAP.User
	}
}

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

// Validate checks that all required fields are present and valid.
// Delegates to each subsection's Validate method.
func (c *Config) Validate() error {
	if err := c.IMAP.Validate(); err != nil {
		return fmt.Errorf("invalid imap config: %v", err)
	}
	if err := c.Intake.Validate(); err != nil {
		return fmt.Errorf("invalid intake config: %v", err)
	}
	if err := c.LLM.Validate(); err != nil {
		return fmt.Errorf("invalid llm config: %v", err)
	}
	if err := c.SMTP.Validate(); err != nil {
		return fmt.Errorf("invalid smtp config: %v", err)
	}
	if _, err := time.ParseDuration(c.ShutdownTimeout); err != nil {
		return fmt.Errorf(
			"invalid shutdown_timeout %q: %w",
			c.ShutdownTimeout, err,
		)
	}
	for i, t := range c.Tools {
		if err := t.Validate(); err != nil {
			return fmt.Errorf("tools[%d] invalid: %v", 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
}
