package config

import (
	"bytes"
	"fmt"
	"io"
	"os"
	"strconv"
	"strings"

	"gopkg.in/yaml.v3"
)

const maxConfigFileBytes = 1 << 20

type fileConfig struct {
	Environment        string            `yaml:"environment"`
	ListenMode         string            `yaml:"listen_mode"`
	Port               *int              `yaml:"port,omitempty"`
	TrustForwardedHost bool              `yaml:"trust_forwarded_host,omitempty"`
	Tenants            []rawTenantConfig `yaml:"tenants"`
}

func loadConfigFile(path string) (Config, error) {
	path = strings.TrimSpace(path)
	file, err := os.Open(path)
	if err != nil {
		return Config{}, fmt.Errorf("open AGS_CONFIG_FILE %q: %w", path, err)
	}
	defer file.Close()

	contents, err := io.ReadAll(io.LimitReader(file, maxConfigFileBytes+1))
	if err != nil {
		return Config{}, fmt.Errorf("read AGS_CONFIG_FILE %q: %w", path, err)
	}
	if len(contents) > maxConfigFileBytes {
		return Config{}, fmt.Errorf("AGS_CONFIG_FILE %q exceeds %d bytes", path, maxConfigFileBytes)
	}

	decoder := yaml.NewDecoder(bytes.NewReader(contents))
	decoder.KnownFields(true)
	var raw fileConfig
	if err := decoder.Decode(&raw); err != nil {
		if err == io.EOF {
			return Config{}, fmt.Errorf("AGS_CONFIG_FILE %q is empty", path)
		}
		return Config{}, fmt.Errorf("invalid AGS_CONFIG_FILE %q: %w", path, err)
	}
	var trailing yaml.Node
	if err := decoder.Decode(&trailing); err != io.EOF {
		if err == nil {
			return Config{}, fmt.Errorf("invalid AGS_CONFIG_FILE %q: multiple YAML documents", path)
		}
		return Config{}, fmt.Errorf("invalid AGS_CONFIG_FILE %q: %w", path, err)
	}
	if len(raw.Tenants) == 0 {
		return Config{}, fmt.Errorf("invalid AGS_CONFIG_FILE %q: at least one tenant is required", path)
	}

	cfg := Config{
		Environment:        raw.Environment,
		ListenMode:         raw.ListenMode,
		TrustForwardedHost: raw.TrustForwardedHost,
	}
	if raw.Port != nil {
		if *raw.Port < 1 || *raw.Port > 65535 {
			return Config{}, fmt.Errorf("invalid AGS_CONFIG_FILE %q: port must be between 1 and 65535", path)
		}
		cfg.Port = strconv.Itoa(*raw.Port)
	}
	cfg.Tenants, err = tenantConfigsFromRaw(raw.Tenants, "AGS_CONFIG_FILE")
	if err != nil {
		return Config{}, err
	}
	return Normalize(cfg)
}

func configuredConfigFileEnvironment() []string {
	configured := make([]string, 0)
	for _, name := range configFileEnvironmentNames() {
		if value, ok := os.LookupEnv(name); ok && strings.TrimSpace(value) != "" {
			configured = append(configured, name)
		}
	}
	return configured
}

func configFileEnvironmentNames() []string {
	names := []string{
		"TENANT_CONFIG",
		"PORT",
		"ENVIRONMENT",
		"LISTEN_MODE",
		"TRUST_FORWARDED_HOST",
	}
	names = append(names, configuredLegacyTenantEnvironmentNames()...)

	unique := make([]string, 0, len(names))
	seen := make(map[string]struct{}, len(names))
	for _, name := range names {
		if _, ok := seen[name]; ok {
			continue
		}
		seen[name] = struct{}{}
		unique = append(unique, name)
	}
	return unique
}
