package service

import (
	"context"
	"encoding/json"
	"maps"
	"slices"

	caasmmodel "security-center-backend/internal/modules/caasm/model"
)

// supportedThrottleServiceKeys mirrors the AWS service IDs that the current
// collector routes into throttling service keys. Keep this list aligned with
// normalizeThrottleServiceKey so the config API only advertises live keys.
var supportedThrottleServiceKeys = []string{
	"apigateway",
	"cloudfront",
	"ec2",
	"ecs",
	"eks",
	"elasticache",
	"elasticloadbalancing",
	"rds",
	"redshift",
	"route53",
	"s3",
}

type ThrottlingResolution struct {
	Effective caasmmodel.ThrottlingConfig
	Sources   map[string]string
}

func SupportedThrottleServiceKeys() []string {
	return slices.Clone(supportedThrottleServiceKeys)
}

func (s *ConfigService) ResolveEffectiveThrottling(ctx context.Context) (ThrottlingResolution, error) {
	cfg, err := s.Get(ctx)
	if err != nil {
		return ThrottlingResolution{}, err
	}
	return ResolveGlobalThrottling(throttleConfigFromData(cfg.Data)), nil
}

func (s *CloudAccountService) ResolveAccountThrottling(ctx context.Context, account *caasmmodel.CloudAccount) (ThrottlingResolution, error) {
	base := ResolveGlobalThrottling(nil)
	if s.config != nil {
		var err error
		base, err = s.config.ResolveEffectiveThrottling(ctx)
		if err != nil {
			return ThrottlingResolution{}, err
		}
	}
	return ResolveAccountThrottlingWithBase(base, account), nil
}

func (s *CloudAccountService) currentGlobalThrottling(ctx context.Context) (caasmmodel.ThrottlingConfig, error) {
	resolution, err := s.resolveGlobalThrottlingWithSources(ctx)
	if err != nil {
		return caasmmodel.ThrottlingConfig{}, err
	}
	return resolution.Effective, nil
}

func (s *CloudAccountService) resolveGlobalThrottlingWithSources(ctx context.Context) (ThrottlingResolution, error) {
	if s.config == nil {
		return ResolveGlobalThrottling(nil), nil
	}
	return s.config.ResolveEffectiveThrottling(ctx)
}

func ResolveGlobalThrottling(raw *caasmmodel.ThrottlingConfig) ThrottlingResolution {
	defaults := caasmmodel.ConfigDefaults().Throttling
	if defaults == nil {
		defaults = &caasmmodel.ThrottlingConfig{}
	}
	resolved := cloneThrottlingConfig(*defaults)
	sources := map[string]string{
		"enabled":                "global_default",
		"token_capacity":         "global_default",
		"token_refill_per_sec":   "global_default",
		"adaptive_threshold":     "global_default",
		"adaptive_window_sec":    "global_default",
		"cool_off_sec":           "global_default",
		"max_concurrency":        "global_default",
		"min_concurrency":        "global_default",
		"circuit_breaker":        "global_default",
		"service_limits":         "global_default",
		"override_policy":        "inherit_or_narrow",
		"reject_out_of_range":    "api_boundary",
		"coordination_substrate": "db_backed",
	}
	if raw == nil {
		return ThrottlingResolution{Effective: resolved, Sources: sources}
	}

	resolved.Enabled = raw.Enabled
	sources["enabled"] = "global_config"
	if raw.TokenCapacity > 0 {
		resolved.TokenCapacity = raw.TokenCapacity
		sources["token_capacity"] = "global_config"
	}
	if raw.TokenRefillPerSec > 0 {
		resolved.TokenRefillPerSec = raw.TokenRefillPerSec
		sources["token_refill_per_sec"] = "global_config"
	}
	if raw.AdaptiveThreshold > 0 {
		resolved.AdaptiveThreshold = raw.AdaptiveThreshold
		sources["adaptive_threshold"] = "global_config"
	}
	if raw.AdaptiveWindowSec > 0 {
		resolved.AdaptiveWindowSec = raw.AdaptiveWindowSec
		sources["adaptive_window_sec"] = "global_config"
	}
	if raw.CoolOffSec > 0 {
		resolved.CoolOffSec = raw.CoolOffSec
		sources["cool_off_sec"] = "global_config"
	}
	if raw.MaxConcurrency > 0 {
		resolved.MaxConcurrency = raw.MaxConcurrency
		sources["max_concurrency"] = "global_config"
	}
	if raw.MinConcurrency > 0 {
		resolved.MinConcurrency = raw.MinConcurrency
		sources["min_concurrency"] = "global_config"
	}
	if raw.CircuitBreaker != nil {
		if resolved.CircuitBreaker == nil {
			resolved.CircuitBreaker = &caasmmodel.CircuitBreakerConfig{}
		}
		if raw.CircuitBreaker.Threshold > 0 {
			resolved.CircuitBreaker.Threshold = raw.CircuitBreaker.Threshold
		}
		if raw.CircuitBreaker.CoolOffSec > 0 {
			resolved.CircuitBreaker.CoolOffSec = raw.CircuitBreaker.CoolOffSec
		}
		sources["circuit_breaker"] = "global_config"
	}
	if len(raw.ServiceLimits) > 0 {
		resolved.ServiceLimits = make(map[string]caasmmodel.ThrottlingServiceSpec, len(raw.ServiceLimits))
		for key, spec := range raw.ServiceLimits {
			resolved.ServiceLimits[key] = spec
		}
		sources["service_limits"] = "global_config"
	}

	return ThrottlingResolution{Effective: resolved, Sources: sources}
}

func ResolveAccountThrottling(global caasmmodel.ThrottlingConfig, account *caasmmodel.CloudAccount) ThrottlingResolution {
	return ResolveAccountThrottlingWithBase(ResolveGlobalThrottling(&global), account)
}

func ResolveAccountThrottlingWithBase(base ThrottlingResolution, account *caasmmodel.CloudAccount) ThrottlingResolution {
	resolved := cloneThrottlingConfig(base.Effective)
	sources := maps.Clone(base.Sources)
	if sources == nil {
		sources = map[string]string{}
	}
	if account == nil || account.Provider != caasmmodel.CloudProviderAWS {
		return ThrottlingResolution{Effective: resolved, Sources: sources}
	}

	if enabled, ok := account.Metadata["throttle_enabled"].(bool); ok {
		resolved.Enabled = enabled
		sources["enabled"] = "account_override"
	}
	if value, ok := metadataInt(account.Metadata, "throttle_tokens"); ok && value > 0 {
		if value < resolved.TokenCapacity {
			resolved.TokenCapacity = value
			sources["token_capacity"] = "account_override"
		} else if value > resolved.TokenCapacity {
			sources["token_capacity"] = "legacy_capped_to_global"
		}
	}
	if value, ok := metadataFloat(account.Metadata, "throttle_refill_per_sec"); ok && value > 0 {
		if value < resolved.TokenRefillPerSec {
			resolved.TokenRefillPerSec = value
			sources["token_refill_per_sec"] = "account_override"
		} else if value > resolved.TokenRefillPerSec {
			sources["token_refill_per_sec"] = "legacy_capped_to_global"
		}
	}
	if value, ok := metadataInt(account.Metadata, "throttle_max_concurrency"); ok && value > 0 {
		if value < resolved.MaxConcurrency {
			resolved.MaxConcurrency = value
			sources["max_concurrency"] = "account_override"
		} else if value > resolved.MaxConcurrency {
			sources["max_concurrency"] = "legacy_capped_to_global"
		}
	}
	if value, ok := metadataInt(account.Metadata, "throttle_min_concurrency"); ok && value > 0 {
		if value < resolved.MinConcurrency {
			resolved.MinConcurrency = value
			sources["min_concurrency"] = "account_override"
		} else if value > resolved.MinConcurrency {
			sources["min_concurrency"] = "legacy_capped_to_global"
		}
	}
	if resolved.MinConcurrency > resolved.MaxConcurrency {
		resolved.MinConcurrency = resolved.MaxConcurrency
		if source, ok := sources["min_concurrency"]; !ok || source == "global_default" {
			sources["min_concurrency"] = "constrained_by_max_concurrency"
		}
	}

	return ThrottlingResolution{Effective: resolved, Sources: sources}
}

func throttleConfigFromData(data map[string]interface{}) *caasmmodel.ThrottlingConfig {
	if data == nil {
		return nil
	}
	raw, ok := data["throttling"]
	if !ok || raw == nil {
		return nil
	}
	bytes, err := json.Marshal(raw)
	if err != nil {
		return nil
	}
	var cfg caasmmodel.ThrottlingConfig
	if err := json.Unmarshal(bytes, &cfg); err != nil {
		return nil
	}
	return &cfg
}

func cloneThrottlingConfig(cfg caasmmodel.ThrottlingConfig) caasmmodel.ThrottlingConfig {
	out := cfg
	if cfg.CircuitBreaker != nil {
		cb := *cfg.CircuitBreaker
		out.CircuitBreaker = &cb
	}
	if cfg.ServiceLimits != nil {
		out.ServiceLimits = maps.Clone(cfg.ServiceLimits)
	}
	return out
}

func metadataInt(meta map[string]interface{}, key string) (int, bool) {
	if meta == nil {
		return 0, false
	}
	switch v := meta[key].(type) {
	case int:
		return v, true
	case int32:
		return int(v), true
	case int64:
		return int(v), true
	case float64:
		return int(v), true
	default:
		return 0, false
	}
}

func metadataFloat(meta map[string]interface{}, key string) (float64, bool) {
	if meta == nil {
		return 0, false
	}
	switch v := meta[key].(type) {
	case float64:
		return v, true
	case float32:
		return float64(v), true
	case int:
		return float64(v), true
	case int64:
		return float64(v), true
	default:
		return 0, false
	}
}
