package service_test

import (
	"context"
	"strings"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

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

func TestValidateThrottlingRejectsHardCapViolations(t *testing.T) {
	svc := service.NewConfigService(nil)

	tests := []struct {
		name      string
		mutate    func(cfg *caasmmodel.ThrottlingConfig)
		wantError string
	}{
		{
			name: "rejects token capacity above hard cap",
			mutate: func(cfg *caasmmodel.ThrottlingConfig) {
				cfg.TokenCapacity = caasmmodel.HardCapThrottleTokenCapacity + 1
			},
			wantError: "token_capacity must be <=",
		},
		{
			name: "rejects refill above hard cap",
			mutate: func(cfg *caasmmodel.ThrottlingConfig) {
				cfg.TokenRefillPerSec = caasmmodel.HardCapThrottleTokenRefillPerSec + 1
			},
			wantError: "token_refill_per_sec must be <=",
		},
		{
			name: "rejects adaptive threshold above hard cap",
			mutate: func(cfg *caasmmodel.ThrottlingConfig) {
				cfg.AdaptiveThreshold = caasmmodel.HardCapThrottleAdaptiveThreshold + 1
			},
			wantError: "adaptive_threshold must be <=",
		},
		{
			name: "rejects adaptive window above hard cap",
			mutate: func(cfg *caasmmodel.ThrottlingConfig) {
				cfg.AdaptiveWindowSec = caasmmodel.HardCapThrottleAdaptiveWindowSec + 1
			},
			wantError: "adaptive_window_sec must be <=",
		},
		{
			name: "rejects cool off above hard cap",
			mutate: func(cfg *caasmmodel.ThrottlingConfig) {
				cfg.CoolOffSec = caasmmodel.HardCapThrottleCoolOffSec + 1
			},
			wantError: "cool_off_sec must be <=",
		},
		{
			name: "rejects max concurrency above hard cap",
			mutate: func(cfg *caasmmodel.ThrottlingConfig) {
				cfg.MaxConcurrency = caasmmodel.HardCapThrottleMaxConcurrency + 1
			},
			wantError: "max_concurrency must be <=",
		},
		{
			name: "rejects min concurrency above hard cap",
			mutate: func(cfg *caasmmodel.ThrottlingConfig) {
				cfg.MinConcurrency = caasmmodel.HardCapThrottleMinConcurrency + 1
			},
			wantError: "min_concurrency must be <=",
		},
		{
			name: "rejects min concurrency above max concurrency",
			mutate: func(cfg *caasmmodel.ThrottlingConfig) {
				cfg.MaxConcurrency = 2
				cfg.MinConcurrency = 3
			},
			wantError: "min_concurrency cannot exceed max_concurrency",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			defaults := caasmmodel.ConfigDefaults()
			require.NotNil(t, defaults.Throttling)

			cfg := *defaults.Throttling
			tt.mutate(&cfg)

			err := svc.ValidateThrottling(&cfg)
			require.Error(t, err)
			assert.Contains(t, err.Error(), tt.wantError)
		})
	}
}

func TestConfigDefaultsStayWithinHardCaps(t *testing.T) {
	defaults := caasmmodel.ConfigDefaults()
	require.NotNil(t, defaults.Throttling)

	cfg := defaults.Throttling
	assert.LessOrEqual(t, cfg.TokenCapacity, caasmmodel.HardCapThrottleTokenCapacity)
	assert.LessOrEqual(t, cfg.TokenRefillPerSec, float64(caasmmodel.HardCapThrottleTokenRefillPerSec))
	assert.LessOrEqual(t, cfg.AdaptiveThreshold, caasmmodel.HardCapThrottleAdaptiveThreshold)
	assert.LessOrEqual(t, cfg.AdaptiveWindowSec, caasmmodel.HardCapThrottleAdaptiveWindowSec)
	assert.LessOrEqual(t, cfg.CoolOffSec, caasmmodel.HardCapThrottleCoolOffSec)
	assert.LessOrEqual(t, cfg.MaxConcurrency, caasmmodel.HardCapThrottleMaxConcurrency)
	assert.LessOrEqual(t, cfg.MinConcurrency, caasmmodel.HardCapThrottleMinConcurrency)
	require.NotNil(t, cfg.CircuitBreaker)
	assert.LessOrEqual(t, cfg.CircuitBreaker.Threshold, caasmmodel.HardCapThrottleAdaptiveThreshold)
	assert.LessOrEqual(t, cfg.CircuitBreaker.CoolOffSec, caasmmodel.HardCapThrottleCoolOffSec)
	require.Contains(t, cfg.ServiceLimits, "route53")
	route53 := cfg.ServiceLimits["route53"]
	assert.LessOrEqual(t, route53.TokenCapacity, caasmmodel.HardCapThrottleTokenCapacity)
	assert.LessOrEqual(t, route53.TokenRefillPerSec, float64(caasmmodel.HardCapThrottleTokenRefillPerSec))
	assert.LessOrEqual(t, route53.MaxConcurrency, caasmmodel.HardCapThrottleMaxConcurrency)
	assert.LessOrEqual(t, route53.MinConcurrency, caasmmodel.HardCapThrottleMinConcurrency)
}

func TestLargeAWSAccountSafeThrottlePresetStaysWithinHardCaps(t *testing.T) {
	svc := service.NewConfigService(nil)
	cfg := caasmmodel.DefaultThrottleLargeAWSAccountSafeConfig()

	require.NoError(t, svc.ValidateThrottling(&cfg))
	assert.True(t, cfg.Enabled)
	assert.Equal(t, 6, cfg.TokenCapacity)
	assert.Equal(t, 2.0, cfg.TokenRefillPerSec)
	assert.Equal(t, 2, cfg.MaxConcurrency)
	assert.Equal(t, 1, cfg.MinConcurrency)
	if assert.NotNil(t, cfg.CircuitBreaker) {
		assert.Equal(t, 4, cfg.CircuitBreaker.Threshold)
		assert.Equal(t, 180, cfg.CircuitBreaker.CoolOffSec)
	}
	require.Contains(t, cfg.ServiceLimits, "route53")
	assert.Equal(t, 1, cfg.ServiceLimits["route53"].MaxConcurrency)
}

func TestValidateThrottlingRejectsUnsupportedServiceLimits(t *testing.T) {
	svc := service.NewConfigService(nil)
	defaults := caasmmodel.ConfigDefaults()
	require.NotNil(t, defaults.Throttling)

	cfg := *defaults.Throttling
	cfg.ServiceLimits = map[string]caasmmodel.ThrottlingServiceSpec{
		"madeup": {
			TokenCapacity: 10,
		},
	}

	err := svc.ValidateThrottling(&cfg)
	require.Error(t, err)
	assert.Contains(t, err.Error(), "service_limits contains unsupported service key")
}

func TestSupportedThrottleServiceKeysMatchCollectorSurface(t *testing.T) {
	assert.Equal(t, []string{
		"apigateway",
		"cloudfront",
		"ec2",
		"ecs",
		"eks",
		"elasticache",
		"elasticloadbalancing",
		"rds",
		"redshift",
		"route53",
		"s3",
	}, service.SupportedThrottleServiceKeys())
}

func TestValidateThrottlingRejectsServiceLimitHardCapViolations(t *testing.T) {
	svc := service.NewConfigService(nil)
	defaults := caasmmodel.ConfigDefaults()
	require.NotNil(t, defaults.Throttling)

	cfg := *defaults.Throttling
	cfg.ServiceLimits = map[string]caasmmodel.ThrottlingServiceSpec{
		"ec2": {
			TokenCapacity:  caasmmodel.HardCapThrottleTokenCapacity + 1,
			MaxConcurrency: caasmmodel.HardCapThrottleMaxConcurrency + 1,
		},
	}

	err := svc.ValidateThrottling(&cfg)
	require.Error(t, err)
	assert.True(t,
		strings.Contains(err.Error(), "service_limits.ec2.token_capacity must be <=") ||
			strings.Contains(err.Error(), "service_limits.ec2.max_concurrency must be <="),
	)
}

func TestValidateRejectsUnknownRawThrottlingFields(t *testing.T) {
	svc := service.NewConfigService(nil)

	err := svc.Validate(map[string]interface{}{
		"throttling": map[string]interface{}{
			"enabled": true,
			"service_limits": map[string]interface{}{
				"ec2": map[string]interface{}{
					"token_capacity":    10,
					"budget_per_minute": 60,
				},
			},
		},
	})
	require.Error(t, err)
	assert.Contains(t, err.Error(), "throttling.service_limits.ec2.budget_per_minute is not supported")

	err = svc.Validate(map[string]interface{}{
		"throttling": map[string]interface{}{
			"enabled": true,
			"circuit_breaker": map[string]interface{}{
				"threshold": 3,
				"mode":      "trip",
			},
		},
	})
	require.Error(t, err)
	assert.Contains(t, err.Error(), "throttling.circuit_breaker.mode is not supported")
}

func TestValidateCollectionScopeRejectsInvalidValues(t *testing.T) {
	svc := service.NewConfigService(nil)

	err := svc.Validate(map[string]interface{}{
		"collection_scope": map[string]interface{}{
			"ip_collection_scope": "internet_only",
		},
	})
	require.Error(t, err)
	assert.Contains(t, err.Error(), "collection_scope.ip_collection_scope")

	err = svc.Validate(map[string]interface{}{
		"collection_scope": map[string]interface{}{
			"port_collection_scope": "private_only",
		},
	})
	require.Error(t, err)
	assert.Contains(t, err.Error(), "collection_scope.port_collection_scope")

	err = svc.Validate(map[string]interface{}{
		"collection_scope": map[string]interface{}{
			"domain_collection_scope": "private_only",
		},
	})
	require.Error(t, err)
	assert.Contains(t, err.Error(), "collection_scope.domain_collection_scope")
}

func TestConfigDefaultsIncludeCollectionScopeDefaults(t *testing.T) {
	defaults := caasmmodel.ConfigDefaults()
	require.NotNil(t, defaults.CollectionScope)
	assert.Equal(t, caasmmodel.CollectionScopeAll, defaults.CollectionScope.IPCollectionScope)
	assert.Equal(t, caasmmodel.CollectionScopeAll, defaults.CollectionScope.PortCollectionScope)
	assert.Equal(t, caasmmodel.CollectionScopeAll, defaults.CollectionScope.DomainCollectionScope)
}

func TestValidateRejectsUnknownRawSyncSettingsFields(t *testing.T) {
	svc := service.NewConfigService(nil)

	err := svc.Validate(map[string]interface{}{
		"sync_settings": map[string]interface{}{
			"mode":               "queue",
			"kill_switch_reason": "manual",
		},
	})
	require.Error(t, err)
	assert.Contains(t, err.Error(), "sync_settings.kill_switch_reason is not supported")
}

func TestValidateSyncSettingsRejectsNonPositiveTimeouts(t *testing.T) {
	svc := service.NewConfigService(nil)

	err := svc.Validate(map[string]interface{}{
		"sync_settings": map[string]interface{}{
			"mode":                      "queue",
			"execution_timeout_minutes": 30,
			"running_timeout_minutes":   0,
			"queued_timeout_minutes":    30,
		},
	})
	require.Error(t, err)
	assert.Contains(t, err.Error(), "running_timeout_minutes")

	err = svc.Validate(map[string]interface{}{
		"sync_settings": map[string]interface{}{
			"mode":                      "queue",
			"execution_timeout_minutes": 30,
			"running_timeout_minutes":   60,
			"queued_timeout_minutes":    0,
		},
	})
	require.Error(t, err)
	assert.Contains(t, err.Error(), "queued_timeout_minutes")

	err = svc.Validate(map[string]interface{}{
		"sync_settings": map[string]interface{}{
			"mode":                      "queue",
			"execution_timeout_minutes": 0,
			"running_timeout_minutes":   60,
			"queued_timeout_minutes":    30,
		},
	})
	require.Error(t, err)
	assert.Contains(t, err.Error(), "execution_timeout_minutes")
}

func TestResolveSyncSettingsAndSetKillSwitch(t *testing.T) {
	ctx := context.Background()
	repo := &stubConfigRepo{
		cfg: &caasmmodel.Config{
			Data: coremodel.JSONB{
				"sync_settings": map[string]interface{}{
					"mode":                      "serverless",
					"lambda_function_name":      "collector-a",
					"kill_switch_enabled":       true,
					"execution_timeout_minutes": 35,
					"running_timeout_minutes":   75,
					"queued_timeout_minutes":    45,
				},
			},
		},
	}
	svc := service.NewConfigService(repo)

	settings, err := svc.ResolveSyncSettings(ctx)
	require.NoError(t, err)
	assert.Equal(t, "serverless", settings.Mode)
	assert.Equal(t, "collector-a", settings.LambdaFunctionName)
	assert.True(t, settings.KillSwitchEnabled)
	assert.Equal(t, 35, settings.ExecutionTimeoutMinutes)
	assert.Equal(t, 75, settings.RunningTimeoutMinutes)
	assert.Equal(t, 45, settings.QueuedTimeoutMinutes)

	updated, err := svc.SetKillSwitch(ctx, false)
	require.NoError(t, err)

	settings, err = svc.ResolveSyncSettings(ctx)
	require.NoError(t, err)
	assert.Equal(t, "serverless", settings.Mode)
	assert.Equal(t, "collector-a", settings.LambdaFunctionName)
	assert.False(t, settings.KillSwitchEnabled)
	assert.Equal(t, 35, settings.ExecutionTimeoutMinutes)
	assert.Equal(t, 75, settings.RunningTimeoutMinutes)
	assert.Equal(t, 45, settings.QueuedTimeoutMinutes)
	assert.Equal(t, updated.Data["sync_settings"], repo.cfg.Data["sync_settings"])
}
