package config

import (
	"os"
	"path/filepath"
	"testing"

	"github.com/mitchellh/mapstructure"
	"github.com/spf13/viper"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestRcuQuotaConfigsDecodeServicePlanDefaults(t *testing.T) {
	var cfg Config
	input := map[string]any{
		"biz": map[string]any{
			"rcuQuotaConfigs": map[string]any{
				"Essential_V2": map[string]any{
					"maxRCUQuotaPerCluster":      20000,
					"totalMaxRCU":                50000,
					"burstyRatio":                1.2,
					"idleResourcePoolTtlInHours": 8,
					"vcpusLimitPerResourcePool":  0,
					"minimalRcuBaseline":         1000,
					"minimalRcuCoefficient":      0.1,
					"scaleDownCooldownMinutes":   120,
				},
			},
		},
	}

	err := mapstructure.Decode(input, &cfg)

	require.NoError(t, err)
	quotaConfig, ok := cfg.BizConfig.RcuQuotaConfigs["Essential_V2"]
	require.True(t, ok)
	require.NotNil(t, quotaConfig.MaxRCUQuotaPerCluster)
	require.NotNil(t, quotaConfig.TotalMaxRCU)
	require.NotNil(t, quotaConfig.BurstyRatio)
	require.NotNil(t, quotaConfig.IdleResourcePoolTtlInHours)
	require.NotNil(t, quotaConfig.VcpusLimitPerResourcePool)
	require.NotNil(t, quotaConfig.MinimalRcuBaseline)
	require.NotNil(t, quotaConfig.MinimalRcuCoefficient)
	require.NotNil(t, quotaConfig.ScaleDownCooldownMinutes)
	assert.Equal(t, uint(20000), *quotaConfig.MaxRCUQuotaPerCluster)
	assert.Equal(t, uint(50000), *quotaConfig.TotalMaxRCU)
	assert.InEpsilon(t, float32(1.2), *quotaConfig.BurstyRatio, 1e-6)
	assert.Equal(t, uint(8), *quotaConfig.IdleResourcePoolTtlInHours)
	assert.Equal(t, uint(0), *quotaConfig.VcpusLimitPerResourcePool)
	assert.Equal(t, uint32(1000), *quotaConfig.MinimalRcuBaseline)
	assert.InEpsilon(t, float32(0.1), *quotaConfig.MinimalRcuCoefficient, 1e-6)
	assert.Equal(t, uint(120), *quotaConfig.ScaleDownCooldownMinutes)
}

func TestResourcePoolNetworkConfigDecodesAWSPrefixLengthKeys(t *testing.T) {
	var cfg Config
	input := map[string]any{
		"resourcePoolNetwork": map[string]any{
			"awsMinPrefixLength": 17,
			"awsMaxPrefixLength": 21,
		},
	}

	err := mapstructure.Decode(input, &cfg)

	require.NoError(t, err)
	assert.Equal(t, 17, cfg.ResourcePoolNetwork.AWSMinPrefixLength)
	assert.Equal(t, 21, cfg.ResourcePoolNetwork.AWSMaxPrefixLength)
}

func TestInitConfigLoadsRcuQuotaConfigsFromYaml(t *testing.T) {
	viper.Reset()
	t.Cleanup(viper.Reset)

	configPath := filepath.Join(t.TempDir(), "config.yaml")
	configYAML := `proxy:
  auth0Domain: test.auth0.com
  auth0ClientID: test-client-id
  auth0Audience: test-audience
aws:
  pcaArn: arn:aws:acm-pca:us-west-2:111111111111:certificate-authority/test
  cmkArn: arn:aws:kms:us-west-2:111111111111:key/test
regionalserver:
  requestTimeoutInSec: 5
  retryCount: 3
  retryWaitTimeoutInSec: 1
resourcePoolNetwork:
  awsRecommendationStartPrefixLength: 18
biz:
  maxRCUQuotaPerCluster: 100000
  totalMaxRCU: 200000
  minimalRcuBaseline: 5000
  minimalRcuCoefficient: 0.25
  rcuQuotaConfigs:
    Essential_V2:
      minimalRcuBaseline: 2000
      minimalRcuCoefficient: 0.1
`
	require.NoError(t, os.WriteFile(configPath, []byte(configYAML), 0o600))

	cfg, err := InitConfig(configPath)

	require.NoError(t, err)
	_, okCanonical := cfg.BizConfig.RcuQuotaConfigs["Essential_V2"]
	quotaConfig, okLower := cfg.BizConfig.RcuQuotaConfigs["essential_v2"]
	assert.False(t, okCanonical)
	require.True(t, okLower)
	require.NotNil(t, quotaConfig.MinimalRcuBaseline)
	require.NotNil(t, quotaConfig.MinimalRcuCoefficient)
	assert.Equal(t, uint32(2000), *quotaConfig.MinimalRcuBaseline)
	assert.InEpsilon(t, float32(0.1), *quotaConfig.MinimalRcuCoefficient, 1e-6)
	assert.Equal(t, 16, cfg.ResourcePoolNetwork.AWSMinPrefixLength)
	assert.Equal(t, 22, cfg.ResourcePoolNetwork.AWSMaxPrefixLength)
	assert.Equal(t, 18, cfg.ResourcePoolNetwork.AWSRecommendationStartPrefixLength)
}

func TestValidateResourcePoolNetworkConfig(t *testing.T) {
	t.Parallel()

	tests := []struct {
		name            string
		cidrs           []string
		awsMinPrefix    int
		awsMaxPrefix    int
		startPrefix     int
		wantErr         bool
		wantErrContains string
	}{
		{name: "default prefix range", awsMinPrefix: 16, awsMaxPrefix: 22, startPrefix: 16},
		{name: "configured prefix range", awsMinPrefix: 17, awsMaxPrefix: 24, startPrefix: 18},
		{
			name:            "minimum prefix is zero",
			awsMaxPrefix:    22,
			wantErr:         true,
			wantErrContains: "resourcePoolNetwork.awsMinPrefixLength (0)",
		},
		{
			name:            "maximum prefix is zero",
			awsMinPrefix:    16,
			wantErr:         true,
			wantErrContains: "resourcePoolNetwork.awsMaxPrefixLength (0)",
		},
		{name: "minimum prefix below IPv4 range", awsMinPrefix: -1, awsMaxPrefix: 22, wantErr: true},
		{name: "maximum prefix above IPv4 range", awsMinPrefix: 16, awsMaxPrefix: 33, wantErr: true},
		{
			name:            "minimum prefix above maximum",
			awsMinPrefix:    23,
			awsMaxPrefix:    22,
			wantErr:         true,
			wantErrContains: "resourcePoolNetwork.awsMinPrefixLength (23) must not exceed resourcePoolNetwork.awsMaxPrefixLength (22)",
		},
		{
			name:            "start prefix below configured range",
			awsMinPrefix:    17,
			awsMaxPrefix:    22,
			startPrefix:     16,
			wantErr:         true,
			wantErrContains: "resourcePoolNetwork.awsRecommendationStartPrefixLength (16) must be between resourcePoolNetwork.awsMinPrefixLength (17) and resourcePoolNetwork.awsMaxPrefixLength (22)",
		},
		{name: "start prefix above configured range", awsMinPrefix: 16, awsMaxPrefix: 21, startPrefix: 22, wantErr: true},
		{name: "start prefix below supported range", awsMinPrefix: 16, awsMaxPrefix: 22, startPrefix: 15, wantErr: true},
		{name: "start prefix above recommendation range", awsMinPrefix: 16, awsMaxPrefix: 22, startPrefix: 23, wantErr: true},
		{name: "canonical private IPv4", cidrs: []string{"10.0.0.0/16", "172.16.0.0/20", "192.168.1.0/24"}, awsMinPrefix: 16, awsMaxPrefix: 22},
		{name: "surrounding whitespace", cidrs: []string{" 10.0.0.0/16"}, awsMinPrefix: 16, awsMaxPrefix: 22, wantErr: true},
		{name: "noncanonical", cidrs: []string{"10.0.1.1/16"}, awsMinPrefix: 16, awsMaxPrefix: 22, wantErr: true},
		{name: "IPv6", cidrs: []string{"fd00::/16"}, awsMinPrefix: 16, awsMaxPrefix: 22, wantErr: true},
		{name: "public", cidrs: []string{"8.8.0.0/16"}, awsMinPrefix: 16, awsMaxPrefix: 22, wantErr: true},
		{name: "crosses RFC1918 boundary", cidrs: []string{"10.0.0.0/7"}, awsMinPrefix: 16, awsMaxPrefix: 22, wantErr: true},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			err := validateResourcePoolNetworkConfig(ResourcePoolNetworkConfig{
				ReservedCIDRs:                      tt.cidrs,
				AWSMinPrefixLength:                 tt.awsMinPrefix,
				AWSMaxPrefixLength:                 tt.awsMaxPrefix,
				AWSRecommendationStartPrefixLength: tt.startPrefix,
			})
			if tt.wantErr {
				require.Error(t, err)
				if tt.wantErrContains != "" {
					require.ErrorContains(t, err, tt.wantErrContains)
				}
				return
			}
			require.NoError(t, err)
		})
	}
}
