package config

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

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/consts"
	"sigs.k8s.io/yaml"
)

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

	zero := int64(0)
	custom := int64(75)
	tests := []struct {
		name string
		cfg  RootConfig
		want int64
	}{
		{name: "omitted uses default", cfg: RootConfig{}, want: 50},
		{name: "explicit zero is preserved", cfg: RootConfig{NetworkCapacity: NetworkCapacityConfig{ReservedAdminPodIPs: &zero}}, want: 0},
		{name: "custom value", cfg: RootConfig{NetworkCapacity: NetworkCapacityConfig{ReservedAdminPodIPs: &custom}}, want: 75},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()
			require.Equal(t, tt.want, tt.cfg.EffectiveReservedAdminPodIPs())
		})
	}
}

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

	path := filepath.Join(t.TempDir(), "root-config.yaml")
	err := os.WriteFile(path, []byte(`
provider: eks
networkCapacity:
  reservedAdminPodIPs: -1
`), 0o600)
	require.NoError(t, err)

	_, err = LoadRootConfig(path)
	require.ErrorContains(t, err, "networkCapacity.reservedAdminPodIPs must not be negative")
}

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

	dir := t.TempDir()
	basePath := filepath.Join(dir, "root-config-base.yaml")
	regionPath := filepath.Join(dir, "root-config-region.yaml")

	err := os.WriteFile(basePath, []byte(`
provider: eks
env: dev
regionConfig:
  logicalRegionName: aws-us-west-2
  name: us-west-2
  zones:
    - us-west-2a
    - us-west-2b
resourceLabels:
  app: dataplane-manager
  region: base
additionalCloudTags:
  owner: cloud
appBoxConfig:
  k8sVersion: "1.31"
  authorizedSubjects:
    - base-subject
  nodePools:
    - instanceType: c6i.2xlarge
      systemVolumeSize: 50
      cpu: 8
      availableCpu: 7
      memoryGi: 16
      availableMemoryMi: 14336
`), 0o600)
	require.NoError(t, err)

	err = os.WriteFile(regionPath, []byte(`
regionConfig:
  nodeZones:
    - us-west-2a
resourceLabels:
  region: us-west-2
  cell: cell-1
additionalCloudTags:
  cost-center: tc
appBoxConfig:
  authorizedSubjects:
    - region-subject
  nodePools:
    - instanceType: c7i.2xlarge
      systemVolumeSize: 80
      cpu: 8
      availableCpu: 7
      memoryGi: 16
      availableMemoryMi: 14336
networkCapacity:
  reservedAdminPodIPs: 0
`), 0o600)
	require.NoError(t, err)

	config, err := LoadRootConfigs([]string{basePath, regionPath})
	require.NoError(t, err)

	assert.Equal(t, "eks", config.Provider)
	assert.Equal(t, "dev", config.Env)
	assert.Equal(t, "aws-us-west-2", config.RegionConfig.LogicalRegionName)
	assert.Equal(t, []string{"us-west-2a", "us-west-2b"}, config.RegionConfig.Zones)
	assert.Equal(t, []string{"us-west-2a"}, config.RegionConfig.NodeZones)
	assert.Equal(t, map[string]string{
		"app":    "dataplane-manager",
		"region": "us-west-2",
		"cell":   "cell-1",
	}, config.ResourceLabels)
	assert.Equal(t, map[string]string{
		"owner":       "cloud",
		"cost-center": "tc",
	}, config.AdditionalCloudTags)
	assert.Equal(t, []string{"region-subject"}, config.APPBoxConfig.AuthorizedSubjects)
	require.Len(t, config.APPBoxConfig.NodePools, 1)
	assert.Equal(t, "c7i.2xlarge", config.APPBoxConfig.NodePools[0].InstanceType)
	assert.EqualValues(t, 0, config.EffectiveReservedAdminPodIPs())
}

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

	_, err := LoadRootConfigs(nil)
	require.ErrorContains(t, err, "root config path is empty")

	_, err = LoadRootConfigs([]string{""})
	require.ErrorContains(t, err, "root config path is empty")
}

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

	dir := t.TempDir()
	basePath := filepath.Join(dir, "root-config-base.yaml")
	regionPath := filepath.Join(dir, "root-config-region.yaml")

	err := os.WriteFile(basePath, []byte(`
provider: eks
regionConfig:
  nodeZones:
    - us-west-2a
`), 0o600)
	require.NoError(t, err)

	err = os.WriteFile(regionPath, []byte(`
regionConfig:
  zones:
    - us-west-2a
    - us-west-2b
`), 0o600)
	require.NoError(t, err)

	config, err := LoadRootConfigs([]string{basePath, regionPath})
	require.NoError(t, err)
	assert.Equal(t, []string{"us-west-2a"}, config.RegionConfig.NodeZones)
}

func TestRootConfig(t *testing.T) {
	config, err := LoadRootConfig("../../examples/root-config-eks.yaml")
	assert.NoError(t, err)
	assert.NotNil(t, config.APPBoxConfig)
}

func TestRootConfigEnableO11yVectorHostNetwork(t *testing.T) {
	var config RootConfig
	err := yaml.Unmarshal([]byte(`
provider: aks
appBoxConfig:
  enableO11yVectorHostNetwork: true
`), &config)

	require.NoError(t, err)
	require.True(t, config.APPBoxConfig.EnableO11yVectorHostNetwork)
}

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

	var config RootConfig
	err := yaml.Unmarshal([]byte(`
provider: eks
objectStorageMetrics:
  refreshInterval: 2h
  awsStorageLensReports:
    - name: dev-us-west-2
      region: us-west-2
      accountID: "790636686964"
      configID: tidb-cluster-bucket
      reportBucket: s3-lens-790636686964-us-west-2-an
      reportPrefix: StorageLens/
      roleARN: arn:aws:iam::790636686964:role/ObjectStorageMetricsExporterReadRole
`), &config)

	require.NoError(t, err)
	require.Equal(t, "2h", config.ObjectStorageMetrics.RefreshInterval)
	require.Len(t, config.ObjectStorageMetrics.AWSStorageLensReports, 1)
	report := config.ObjectStorageMetrics.AWSStorageLensReports[0]
	require.Equal(t, "dev-us-west-2", report.Name)
	require.Equal(t, "us-west-2", report.Region)
	require.Equal(t, "790636686964", report.AccountID)
	require.Equal(t, "tidb-cluster-bucket", report.ConfigID)
	require.Equal(t, "s3-lens-790636686964-us-west-2-an", report.ReportBucket)
	require.Equal(t, "StorageLens/", report.ReportPrefix)
	require.Equal(t, "arn:aws:iam::790636686964:role/ObjectStorageMetricsExporterReadRole", report.RoleARN)
}

func TestRegionConfig_EffectiveNodeZones(t *testing.T) {
	t.Run("fallback to zones when workload zones empty", func(t *testing.T) {
		regionConfig := RegionConfig{
			Zones: []string{"us-west-2a", "us-west-2b", "us-west-2c"},
		}

		assert.Equal(t, []string{"us-west-2a", "us-west-2b", "us-west-2c"}, regionConfig.EffectiveNodeZones())
	})

	t.Run("use workload zones when configured", func(t *testing.T) {
		regionConfig := RegionConfig{
			Zones:     []string{"us-west-2a", "us-west-2b", "us-west-2c"},
			NodeZones: []string{"us-west-2a", "us-west-2b"},
		}

		assert.Equal(t, []string{"us-west-2a", "us-west-2b"}, regionConfig.EffectiveNodeZones())
	})
}

func TestRegionConfig_ValidateNodeZones(t *testing.T) {
	t.Run("valid when node zones empty", func(t *testing.T) {
		regionConfig := RegionConfig{
			Zones: []string{"us-west-2a", "us-west-2b", "us-west-2c"},
		}
		assert.NoError(t, regionConfig.ValidateNodeZones())
	})

	t.Run("valid when node zones are subset of zones", func(t *testing.T) {
		regionConfig := RegionConfig{
			Zones:     []string{"us-west-2a", "us-west-2b", "us-west-2c"},
			NodeZones: []string{"us-west-2a", "us-west-2b"},
		}
		assert.NoError(t, regionConfig.ValidateNodeZones())
	})

	t.Run("invalid when node zones not in zones", func(t *testing.T) {
		regionConfig := RegionConfig{
			Zones:     []string{"us-west-2a", "us-west-2b", "us-west-2c"},
			NodeZones: []string{"us-west-2a", "us-west-2d"},
		}
		assert.Error(t, regionConfig.ValidateNodeZones())
	})

	t.Run("invalid when node zones equal zones", func(t *testing.T) {
		regionConfig := RegionConfig{
			Zones:     []string{"us-west-2a", "us-west-2b"},
			NodeZones: []string{"us-west-2a", "us-west-2b"},
		}
		assert.Error(t, regionConfig.ValidateNodeZones())
	})

	t.Run("invalid when zones empty but node zones configured", func(t *testing.T) {
		regionConfig := RegionConfig{
			NodeZones: []string{"us-west-2a"},
		}
		assert.Error(t, regionConfig.ValidateNodeZones())
	})
}

func TestRootConfig_ShouldProvisionPrivateLinkEndpointSubnets(t *testing.T) {
	tests := []struct {
		name      string
		provider  string
		byoc      bool
		nodeZones []string
		want      bool
	}{
		{name: "EKS single AZ", provider: consts.ProviderTypeEKS, nodeZones: []string{"us-east-1a"}, want: true},
		{name: "ACK single AZ", provider: consts.ProviderTypeACK, nodeZones: []string{"cn-hangzhou-b"}, want: true},
		{name: "EKS multi AZ", provider: consts.ProviderTypeEKS, nodeZones: []string{"us-east-1a", "us-east-1b"}},
		{name: "EKS without node zones", provider: consts.ProviderTypeEKS},
		{name: "AKS single AZ", provider: consts.ProviderTypeAKS, nodeZones: []string{"1"}},
		{name: "BYOC EKS single AZ", provider: consts.ProviderTypeEKS, byoc: true, nodeZones: []string{"us-east-1a"}},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			config := RootConfig{ControllerConfig: ControllerConfig{Provider: tt.provider}}
			if tt.byoc {
				config.BYOC.BYOCPrivateLink.ControlPlaneVPCID = "vpc-control-plane"
			}
			assert.Equal(t, tt.want, config.ShouldProvisionPrivateLinkEndpointSubnets(tt.nodeZones))
		})
	}
}

func TestLoadRootConfigIgnoresDeprecatedPrivateLinkEndpointSubnetGate(t *testing.T) {
	path := filepath.Join(t.TempDir(), "root-config.yaml")
	err := os.WriteFile(path, []byte(`
provider: eks
regionConfig:
  zones:
    - us-east-1a
    - us-east-1b
  nodeZones:
    - us-east-1a
  enablePrivateLinkEndpointSubnets: true
`), 0o600)
	require.NoError(t, err)

	config, err := LoadRootConfig(path)
	require.NoError(t, err)
	assert.True(t, config.ShouldProvisionPrivateLinkEndpointSubnets(config.RegionConfig.NodeZones))
}

func TestBYOCConfig_AKSManagedIdentityConfig(t *testing.T) {
	t.Run("disabled when config is empty", func(t *testing.T) {
		cfg, enabled, err := BYOCConfig{}.AKSManagedIdentityConfig()
		require.NoError(t, err)
		assert.False(t, enabled)
		assert.Nil(t, cfg)
	})

	t.Run("enabled when all fields are configured", func(t *testing.T) {
		cfg, enabled, err := BYOCConfig{
			AKSControlPlaneIdentityName:         " control-plane ",
			AKSKubeletIdentityName:              " kubelet ",
			AKSManagedIdentityResourceGroupName: " identities-rg ",
		}.AKSManagedIdentityConfig()
		require.NoError(t, err)
		require.True(t, enabled)
		require.NotNil(t, cfg)
		assert.Equal(t, "control-plane", cfg.ControlPlaneIdentityName)
		assert.Equal(t, "kubelet", cfg.KubeletIdentityName)
		assert.Equal(t, "identities-rg", cfg.ManagedIdentityResourceGroup)
	})

	t.Run("error when config is partial", func(t *testing.T) {
		_, _, err := BYOCConfig{
			AKSControlPlaneIdentityName: "control-plane",
		}.AKSManagedIdentityConfig()
		require.Error(t, err)
		assert.Contains(t, err.Error(), "must be configured together")
	})
}

func TestRootConfig_ShouldBypassDynamicKeyspaceRole(t *testing.T) {
	t.Run("disabled when byoc config is empty", func(t *testing.T) {
		assert.False(t, RootConfig{}.ShouldBypassDynamicKeyspaceRole())
	})

	t.Run("enabled by private link config", func(t *testing.T) {
		assert.True(t, RootConfig{
			BYOC: BYOCConfig{
				BYOCPrivateLink: BYOCPrivateLinkConfig{
					ControlPlaneVPCID: "vpc-1",
				},
			},
		}.ShouldBypassDynamicKeyspaceRole())
	})

	t.Run("enabled by static node role config", func(t *testing.T) {
		assert.True(t, RootConfig{
			BYOC: BYOCConfig{
				NodeRoleName: "node-role",
			},
		}.ShouldBypassDynamicKeyspaceRole())
	})

	t.Run("enabled by static import role config", func(t *testing.T) {
		assert.True(t, RootConfig{
			BYOC: BYOCConfig{
				ImportRoleName: "import-role",
			},
		}.ShouldBypassDynamicKeyspaceRole())
	})

	t.Run("disabled by partial AKS identity config alone", func(t *testing.T) {
		assert.False(t, RootConfig{
			BYOC: BYOCConfig{
				AKSControlPlaneIdentityName: "control-plane",
			},
		}.ShouldBypassDynamicKeyspaceRole())
	})
}

func TestRootConfig_IsAKSBYOCStaticAuthEnabled(t *testing.T) {
	t.Run("disabled for non-aks provider", func(t *testing.T) {
		enabled, err := RootConfig{
			ControllerConfig: ControllerConfig{
				Provider: consts.ProviderTypeEKS,
			},
			BYOC: BYOCConfig{
				AKSControlPlaneIdentityName:         "control-plane",
				AKSKubeletIdentityName:              "kubelet",
				AKSManagedIdentityResourceGroupName: "identities-rg",
			},
		}.IsAKSBYOCStaticAuthEnabled()
		require.NoError(t, err)
		assert.False(t, enabled)
	})

	t.Run("enabled for aks provider with complete config", func(t *testing.T) {
		enabled, err := RootConfig{
			ControllerConfig: ControllerConfig{
				Provider: consts.ProviderTypeAKS,
			},
			BYOC: BYOCConfig{
				AKSControlPlaneIdentityName:         "control-plane",
				AKSKubeletIdentityName:              "kubelet",
				AKSManagedIdentityResourceGroupName: "identities-rg",
			},
		}.IsAKSBYOCStaticAuthEnabled()
		require.NoError(t, err)
		assert.True(t, enabled)
	})

	t.Run("error for aks provider with partial config", func(t *testing.T) {
		_, err := RootConfig{
			ControllerConfig: ControllerConfig{
				Provider: consts.ProviderTypeAKS,
			},
			BYOC: BYOCConfig{
				AKSControlPlaneIdentityName: "control-plane",
			},
		}.IsAKSBYOCStaticAuthEnabled()
		require.Error(t, err)
		assert.Contains(t, err.Error(), "must be configured together")
	})
}

func TestRootConfig_AzureMeteringStorageScope(t *testing.T) {
	t.Run("returns scope when azure blob metering provider is configured", func(t *testing.T) {
		scope := RootConfig{
			MeteringStorageProviderConfig: MeteringStorageProviderConfig{
				Provider: AzureBlobMeteringStorageProvider,
				AzureBlob: AzureBlobMeteringStorageConfig{
					Scope: "  /subscriptions/test/resourceGroups/rg/providers/Microsoft.Storage/storageAccounts/sa/blobServices/default/containers/metering  ",
				},
			},
		}.AzureMeteringStorageScope()

		assert.Equal(t, "/subscriptions/test/resourceGroups/rg/providers/Microsoft.Storage/storageAccounts/sa/blobServices/default/containers/metering", scope)
	})

	t.Run("returns empty string for non azure metering provider", func(t *testing.T) {
		scope := RootConfig{
			MeteringStorageProviderConfig: MeteringStorageProviderConfig{
				Provider: AwsS3MeteringStorageProvider,
				AwsS3: AwsS3MeteringStorageConfig{
					AssumeRoleARN: "arn:aws:iam::123456789012:role/metering-writer",
				},
			},
		}.AzureMeteringStorageScope()

		assert.Empty(t, scope)
	})
}
