package config

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

	"gopkg.in/yaml.v3"
)

type Config struct {
	Env                   string   `yaml:"env"`
	Port                  string   `yaml:"port"`
	DSN                   string   `yaml:"dsn"`
	StorageDir            string   `yaml:"storage_dir"`
	MaxUploadBytes        int64    `yaml:"max_upload_bytes"`
	AuthSecret            string   `yaml:"auth_secret"`
	SystemAIKey           string   `yaml:"system_ai_key"`
	AIKeyEncryptionSecret string   `yaml:"ai_key_encryption_secret"`
	AllowedOrigins        []string `yaml:"allowed_origins"`
}

func Load() Config {
	cfg, err := LoadFromDir("etc")
	if err != nil {
		panic(err)
	}
	return cfg
}

func LoadFromDir(configDir string) (Config, error) {
	mode := strings.TrimSpace(os.Getenv("SENLIN_APP_MODE"))
	if mode == "" {
		mode = "dev"
	}
	path := filepath.Join(configDir, "agent."+strings.ToLower(mode)+".yaml")
	data, err := os.ReadFile(path)
	if err != nil {
		return Config{}, err
	}
	var cfg Config
	if err := yaml.Unmarshal(data, &cfg); err != nil {
		return Config{}, err
	}
	selectedEnvironment, err := canonicalEnvironment(mode)
	if err != nil {
		return Config{}, err
	}
	configuredEnvironment, err := canonicalEnvironment(cfg.Env)
	if err != nil {
		return Config{}, err
	}
	if selectedEnvironment != configuredEnvironment {
		return Config{}, fmt.Errorf("SENLIN_APP_MODE %s does not match config env %s", mode, cfg.Env)
	}
	if cfg.MaxUploadBytes <= 0 {
		cfg.MaxUploadBytes = 32 << 20
	}
	if strings.TrimSpace(cfg.StorageDir) == "" {
		return Config{}, fmt.Errorf("storage_dir must not be empty")
	}
	hasAllowedOrigin := false
	for _, origin := range cfg.AllowedOrigins {
		if strings.TrimSpace(origin) != "" {
			hasAllowedOrigin = true
			break
		}
	}
	if !hasAllowedOrigin {
		return Config{}, fmt.Errorf("allowed_origins must include at least one origin")
	}
	production := selectedEnvironment == "production"
	if err := validateSecret(production, "auth_secret", cfg.AuthSecret); err != nil {
		return Config{}, err
	}
	if err := validateSecret(production, "ai_key_encryption_secret", cfg.AIKeyEncryptionSecret); err != nil {
		return Config{}, err
	}
	return cfg, nil
}

func canonicalEnvironment(value string) (string, error) {
	switch strings.ToLower(strings.TrimSpace(value)) {
	case "dev", "development":
		return "development", nil
	case "prod", "production":
		return "production", nil
	case "test":
		return "test", nil
	default:
		return "", fmt.Errorf("unsupported environment %q", value)
	}
}

func validateSecret(production bool, field, value string) error {
	secret := strings.TrimSpace(value)
	if secret == "" {
		return fmt.Errorf("%s must not be empty", field)
	}
	if production {
		if len([]byte(secret)) < 32 || estimatedEntropyBits(secret) < 128 || isCommonSecret(secret) {
			return fmt.Errorf("%s must contain at least 32 bytes and an estimated 128 bits of entropy, without development sentinels, in production", field)
		}
	}
	return nil
}

func estimatedEntropyBits(value string) float64 {
	data := []byte(value)
	counts := make(map[byte]int, len(data))
	for _, item := range data {
		counts[item]++
	}
	length := float64(len(data))
	entropyPerByte := 0.0
	for _, count := range counts {
		probability := float64(count) / length
		entropyPerByte -= probability * math.Log2(probability)
	}
	return entropyPerByte * length
}

func isCommonSecret(value string) bool {
	normalized := strings.ToLower(strings.TrimSpace(value))
	for _, marker := range []string{"change-me", "changeme", "development", "dev-secret", "local-secret", "test-secret", "placeholder"} {
		if strings.Contains(normalized, marker) {
			return true
		}
	}
	switch normalized {
	case "secret", "password", "default", "admin":
		return true
	default:
		return false
	}
}
