package config

import (
	"bytes"
	"os"
	"strings"

	"github.com/tech/sendico/pkg/merrors"
	"github.com/tech/sendico/pkg/mlogger"
	"go.uber.org/zap"
	"gopkg.in/yaml.v3"
)

type service struct {
	logger mlogger.Logger
}

// New creates a configuration loader.
func New(logger mlogger.Logger) Loader {
	if logger == nil {
		logger = zap.NewNop()
	}
	return &service{logger: logger.Named("config")}
}

func (s *service) Load(path string) (*Config, error) {
	if strings.TrimSpace(path) == "" {
		return nil, merrors.InvalidArgument("config path is required", "path")
	}

	//nolint:gosec // Configuration file path is provided by service startup configuration.
	data, err := os.ReadFile(path)
	if err != nil {
		s.logger.Error("Failed to read config file", zap.String("path", path), zap.Error(err))
		return nil, merrors.InternalWrap(err, "failed to read callbacks config")
	}

	cfg := &Config{}
	decoder := yaml.NewDecoder(bytes.NewReader(data))
	decoder.KnownFields(true)
	if err := decoder.Decode(cfg); err != nil {
		s.logger.Error("Failed to parse config yaml", zap.String("path", path), zap.Error(err))
		return nil, merrors.InternalWrap(err, "failed to parse callbacks config")
	}

	s.applyDefaults(cfg)
	if err := s.validate(cfg); err != nil {
		return nil, err
	}

	return cfg, nil
}

func (s *service) applyDefaults(cfg *Config) {
	if cfg.Runtime == nil {
		cfg.Runtime = &RuntimeConfig{ShutdownTimeoutSeconds: defaultShutdownTimeoutSeconds}
	}

	if cfg.Metrics == nil {
		cfg.Metrics = &MetricsConfig{Address: defaultMetricsAddress}
	} else if strings.TrimSpace(cfg.Metrics.Address) == "" {
		cfg.Metrics.Address = defaultMetricsAddress
	}

	if cfg.Delivery.WorkerConcurrency <= 0 {
		cfg.Delivery.WorkerConcurrency = defaultWorkerConcurrency
	}
	if cfg.Delivery.WorkerPollMS <= 0 {
		cfg.Delivery.WorkerPollMS = defaultWorkerPollIntervalMS
	}
	if cfg.Delivery.LockTTLSeconds <= 0 {
		cfg.Delivery.LockTTLSeconds = defaultLockTTLSeconds
	}
	if cfg.Delivery.RequestTimeoutMS <= 0 {
		cfg.Delivery.RequestTimeoutMS = defaultRequestTimeoutMS
	}
	if cfg.Delivery.MaxAttempts <= 0 {
		cfg.Delivery.MaxAttempts = defaultMaxAttempts
	}
	if cfg.Delivery.MinDelayMS <= 0 {
		cfg.Delivery.MinDelayMS = defaultMinDelayMS
	}
	if cfg.Delivery.MaxDelayMS <= 0 {
		cfg.Delivery.MaxDelayMS = defaultMaxDelayMS
	}
	if cfg.Delivery.JitterRatio <= 0 {
		cfg.Delivery.JitterRatio = defaultJitterRatio
	}
	if cfg.Delivery.JitterRatio > 1 {
		cfg.Delivery.JitterRatio = 1
	}

	if cfg.Security.DNSResolveTimeout <= 0 {
		cfg.Security.DNSResolveTimeout = defaultDNSResolveTimeoutMS
	}
	if len(cfg.Security.AllowedPorts) == 0 {
		cfg.Security.AllowedPorts = []int{443}
	}
	if !cfg.Security.RequireHTTPS {
		cfg.Security.RequireHTTPS = true
	}

	if cfg.Secrets.Static == nil {
		cfg.Secrets.Static = map[string]string{}
	}
	if strings.TrimSpace(cfg.Secrets.Vault.DefaultField) == "" {
		cfg.Secrets.Vault.DefaultField = defaultSecretsVaultField
	}
}

func (s *service) validate(cfg *Config) error {
	if cfg.Database == nil {
		return merrors.InvalidArgument("database configuration is required", "database")
	}
	if cfg.Messaging == nil {
		return merrors.InvalidArgument("messaging configuration is required", "messaging")
	}
	if strings.TrimSpace(string(cfg.Messaging.Driver)) == "" {
		return merrors.InvalidArgument("messaging.driver is required", "messaging.driver")
	}
	if cfg.Delivery.MinDelay() > cfg.Delivery.MaxDelay() {
		return merrors.InvalidArgument("delivery min delay must be <= max delay", "delivery.min_delay_ms", "delivery.max_delay_ms")
	}
	if cfg.Delivery.MaxAttempts < 1 {
		return merrors.InvalidArgument("delivery.max_attempts must be > 0", "delivery.max_attempts")
	}
	vaultAddress := strings.TrimSpace(cfg.Secrets.Vault.Address)
	vaultTokenEnv := strings.TrimSpace(cfg.Secrets.Vault.TokenEnv)
	vaultMountPath := strings.TrimSpace(cfg.Secrets.Vault.MountPath)
	hasVault := vaultAddress != "" || vaultTokenEnv != "" || vaultMountPath != ""
	if hasVault {
		if vaultAddress == "" {
			return merrors.InvalidArgument("secrets.vault.address is required when vault settings are configured", "secrets.vault.address")
		}
		if vaultTokenEnv == "" {
			return merrors.InvalidArgument("secrets.vault.token_env is required when vault settings are configured", "secrets.vault.token_env")
		}
		if vaultMountPath == "" {
			return merrors.InvalidArgument("secrets.vault.mount_path is required when vault settings are configured", "secrets.vault.mount_path")
		}
	}

	return nil
}
