package providerconfig

import (
	"context"
	"testing"

	aliv1beta1 "github.com/crossplane-contrib/provider-upjet-alibabacloud/apis/v1beta1"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/config"
	awsUpboundV1 "github.com/upbound/provider-aws/apis/v1beta1"
	azurev1beta1 "github.com/upbound/provider-azure/apis/v1beta1"
	"gopkg.in/yaml.v2"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/types"
	clientgoscheme "k8s.io/client-go/kubernetes/scheme"
	"sigs.k8s.io/controller-runtime/pkg/client/fake"
)

func makeTestRootConfig() config.RootConfig {
	return config.RootConfig{
		DNSProviderConfig: config.DNSProviderConfig{
			AwsRoute53: config.AwsRoute53Config{
				AccountID:  "aws-dns-account-123",
				RoleArn:    "arn:aws:iam::123456789012:role/dns-role",
				ExternalId: "test-external-id",
				ZoneId:     "Z1234567890ABC",
				Domain:     "example.com",
			},
			AliCloudDNS: config.AliCloudDNSConfig{
				AccountID:       "ali-cloud-dns-123",
				SecretName:      "ali-cloud-dns-secret",
				SecretNamespace: "crossplane-system",
				Domain:          "dev.aliyun.tidb-cloud.com",
			},
		},
	}
}

func TestAWSInitProviderConfigs(t *testing.T) {
	scheme := runtime.NewScheme()
	require.NoError(t, clientgoscheme.AddToScheme(scheme))
	require.NoError(t, awsUpboundV1.SchemeBuilder.AddToScheme(scheme))

	accountList := struct {
		Accounts []AWSAccountEntry `yaml:"accounts"`
	}{
		Accounts: []AWSAccountEntry{
			{
				AccountID:  "12345678",
				RoleName:   "dev-role",
				ExternalID: "",
			},
			{
				AccountID:  "87654321",
				RoleName:   "prod-role",
				ExternalID: "prod-external-id",
			},
		},
	}
	readAccountList = func(_ string) ([]byte, error) {
		return yaml.Marshal(&accountList)
	}

	cfg := makeTestRootConfig()

	client := fake.NewClientBuilder().
		WithScheme(scheme).
		Build()

	initializer := &awsInitializer{}
	err := initializer.InitializeProviderConfig(context.Background(), client, "test", &cfg)
	require.NoError(t, err)

	var upboundPC awsUpboundV1.ProviderConfig
	err = client.Get(context.Background(), types.NamespacedName{Name: "12345678"}, &upboundPC)
	require.NoError(t, err)
	require.Len(t, upboundPC.Spec.AssumeRoleChain, 1)
	require.Equal(t, "arn:aws:iam::12345678:role/dev-role", *upboundPC.Spec.AssumeRoleChain[0].RoleARN)
	require.Equal(t, "IRSA", string(upboundPC.Spec.Credentials.Source))

	err = client.Get(context.Background(), types.NamespacedName{Name: "87654321"}, &upboundPC)
	require.NoError(t, err)
	require.Len(t, upboundPC.Spec.AssumeRoleChain, 1)
	require.Equal(t, "arn:aws:iam::87654321:role/prod-role", *upboundPC.Spec.AssumeRoleChain[0].RoleARN)
	require.Equal(t, "prod-external-id", *upboundPC.Spec.AssumeRoleChain[0].ExternalID)

	err = client.Get(context.Background(), types.NamespacedName{Name: "dns-aws-dns-account-123"}, &upboundPC)
	require.NoError(t, err)
	require.Len(t, upboundPC.Spec.AssumeRoleChain, 1)
	require.Equal(t, "arn:aws:iam::123456789012:role/dns-role", *upboundPC.Spec.AssumeRoleChain[0].RoleARN)
	require.Equal(t, "test-external-id", *upboundPC.Spec.AssumeRoleChain[0].ExternalID)
	require.Equal(t, "IRSA", string(upboundPC.Spec.Credentials.Source))
}

func TestAliCloudInitProviderConfigs(t *testing.T) {
	scheme := runtime.NewScheme()
	require.NoError(t, clientgoscheme.AddToScheme(scheme))
	require.NoError(t, aliv1beta1.SchemeBuilder.AddToScheme(scheme))

	accountList := struct {
		Accounts []AliCloudAccountEntry `yaml:"accounts"`
	}{
		Accounts: []AliCloudAccountEntry{
			{
				AccountID:       "12345678",
				SecretName:      "ali-cloud-secret-12345678",
				SecretNamespace: "crossplane-system",
			},
			{
				AccountID:       "87654321",
				SecretName:      "ali-cloud-secret-87654321",
				SecretNamespace: "crossplane-system",
			},
		},
	}
	readAccountList = func(_ string) ([]byte, error) {
		return yaml.Marshal(&accountList)
	}

	cfg := makeTestRootConfig()

	client := fake.NewClientBuilder().
		WithScheme(scheme).
		Build()

	initializer := &aliCloudInitializer{}
	err := initializer.InitializeProviderConfig(context.Background(), client, "test", &cfg)
	require.NoError(t, err)

	var upboundPC1 aliv1beta1.ProviderConfig
	err = client.Get(context.Background(), types.NamespacedName{Name: "12345678"}, &upboundPC1)
	assert.NoError(t, err)
	assert.Equal(t, "ali-cloud-secret-12345678", upboundPC1.Spec.Credentials.SecretRef.Name)
	assert.Equal(t, "crossplane-system", upboundPC1.Spec.Credentials.SecretRef.Namespace)
	assert.Equal(t, "credentials", upboundPC1.Spec.Credentials.SecretRef.Key)
	assert.Equal(t, "Secret", string(upboundPC1.Spec.Credentials.Source))

	var upboundPC2 aliv1beta1.ProviderConfig
	err = client.Get(context.Background(), types.NamespacedName{Name: "87654321"}, &upboundPC2)
	assert.NoError(t, err)
	assert.Equal(t, "ali-cloud-secret-87654321", upboundPC2.Spec.Credentials.SecretRef.Name)
	assert.Equal(t, "crossplane-system", upboundPC2.Spec.Credentials.SecretRef.Namespace)
	assert.Equal(t, "credentials", upboundPC2.Spec.Credentials.SecretRef.Key)
	assert.Equal(t, "Secret", string(upboundPC2.Spec.Credentials.Source))

	var dnsUpboundPC aliv1beta1.ProviderConfig
	err = client.Get(context.Background(), types.NamespacedName{Name: "dns-ali-cloud-dns-123"}, &dnsUpboundPC)
	assert.NoError(t, err)
	assert.Equal(t, "ali-cloud-dns-secret", dnsUpboundPC.Spec.Credentials.SecretRef.Name)
	assert.Equal(t, "crossplane-system", dnsUpboundPC.Spec.Credentials.SecretRef.Namespace)
	assert.Equal(t, "credentials", dnsUpboundPC.Spec.Credentials.SecretRef.Key)
	assert.Equal(t, "Secret", string(dnsUpboundPC.Spec.Credentials.Source))
}

func TestAzureInitProviderConfigs(t *testing.T) {
	scheme := runtime.NewScheme()
	require.NoError(t, clientgoscheme.AddToScheme(scheme))
	require.NoError(t, azurev1beta1.SchemeBuilder.AddToScheme(scheme))

	accountList := struct {
		Accounts []AzureAccountEntry `yaml:"accounts"`
	}{
		Accounts: []AzureAccountEntry{
			{
				SubscriptionID: "sub-1",
				TenantID:       "tenant-1",
				ClientID:       "client-1",
			},
			{
				SubscriptionID: "sub-2",
				TenantID:       "tenant-2",
				ClientID:       "client-2",
			},
		},
	}
	readAccountList = func(_ string) ([]byte, error) {
		return yaml.Marshal(&accountList)
	}

	cfg := makeTestRootConfig()
	cfg.DNSProviderConfig.AzureDNSZone = config.AzureDNSZoneConfig{
		Domain:         "example.com",
		ResourceGroup:  "rg-1",
		SubscriptionID: "dns-sub",
		TenantID:       "dns-tenant",
		ClientID:       "dns-client",
	}
	cfg.BYOC.AzureGatewayDNSZone = config.AzureDNSZoneConfig{
		Domain:         "clusters.eastus.dev.azure.tidb-cloud.com",
		ResourceGroup:  "gateway-rg",
		SubscriptionID: "gateway-dns-sub",
		TenantID:       "gateway-dns-tenant",
		ClientID:       "gateway-dns-client",
	}
	cfg.BYOC.AzureAPIServerDNSZone = config.AzureDNSZoneConfig{
		Domain:         "private.eastus.azmk8s.io",
		ResourceGroup:  "rg-2",
		SubscriptionID: "api-dns-sub",
		TenantID:       "api-dns-tenant",
		ClientID:       "api-dns-client",
	}

	client := fake.NewClientBuilder().
		WithScheme(scheme).
		Build()

	initializer := &azureInitializer{}
	err := initializer.InitializeProviderConfig(context.Background(), client, "test", &cfg)
	require.NoError(t, err)

	var pc1 azurev1beta1.ProviderConfig
	err = client.Get(context.Background(), types.NamespacedName{Name: "sub-1"}, &pc1)
	require.NoError(t, err)
	require.Equal(t, oidcTokenFile, string(pc1.Spec.Credentials.Source))
	require.NotNil(t, pc1.Spec.SubscriptionID)
	require.NotNil(t, pc1.Spec.TenantID)
	require.NotNil(t, pc1.Spec.ClientID)
	require.Equal(t, "sub-1", *pc1.Spec.SubscriptionID)
	require.Equal(t, "tenant-1", *pc1.Spec.TenantID)
	require.Equal(t, "client-1", *pc1.Spec.ClientID)

	var pc2 azurev1beta1.ProviderConfig
	err = client.Get(context.Background(), types.NamespacedName{Name: "sub-2"}, &pc2)
	require.NoError(t, err)
	require.Equal(t, oidcTokenFile, string(pc2.Spec.Credentials.Source))
	require.NotNil(t, pc2.Spec.SubscriptionID)
	require.NotNil(t, pc2.Spec.TenantID)
	require.NotNil(t, pc2.Spec.ClientID)
	require.Equal(t, "sub-2", *pc2.Spec.SubscriptionID)
	require.Equal(t, "tenant-2", *pc2.Spec.TenantID)
	require.Equal(t, "client-2", *pc2.Spec.ClientID)

	var dnsPC azurev1beta1.ProviderConfig
	err = client.Get(context.Background(), types.NamespacedName{Name: "dns-dns-sub"}, &dnsPC)
	require.NoError(t, err)
	require.Equal(t, oidcTokenFile, string(dnsPC.Spec.Credentials.Source))
	require.NotNil(t, dnsPC.Spec.SubscriptionID)
	require.NotNil(t, dnsPC.Spec.TenantID)
	require.NotNil(t, dnsPC.Spec.ClientID)
	require.Equal(t, "dns-sub", *dnsPC.Spec.SubscriptionID)
	require.Equal(t, "dns-tenant", *dnsPC.Spec.TenantID)
	require.Equal(t, "dns-client", *dnsPC.Spec.ClientID)

	var gatewayDNSPC azurev1beta1.ProviderConfig
	err = client.Get(context.Background(), types.NamespacedName{Name: "dns-gateway-dns-sub"}, &gatewayDNSPC)
	require.NoError(t, err)
	require.Equal(t, oidcTokenFile, string(gatewayDNSPC.Spec.Credentials.Source))
	require.NotNil(t, gatewayDNSPC.Spec.SubscriptionID)
	require.NotNil(t, gatewayDNSPC.Spec.TenantID)
	require.NotNil(t, gatewayDNSPC.Spec.ClientID)
	require.Equal(t, "gateway-dns-sub", *gatewayDNSPC.Spec.SubscriptionID)
	require.Equal(t, "gateway-dns-tenant", *gatewayDNSPC.Spec.TenantID)
	require.Equal(t, "gateway-dns-client", *gatewayDNSPC.Spec.ClientID)

	var apiDNSPC azurev1beta1.ProviderConfig
	err = client.Get(context.Background(), types.NamespacedName{Name: "dns-api-dns-sub"}, &apiDNSPC)
	require.NoError(t, err)
	require.Equal(t, oidcTokenFile, string(apiDNSPC.Spec.Credentials.Source))
	require.NotNil(t, apiDNSPC.Spec.SubscriptionID)
	require.NotNil(t, apiDNSPC.Spec.TenantID)
	require.NotNil(t, apiDNSPC.Spec.ClientID)
	require.Equal(t, "api-dns-sub", *apiDNSPC.Spec.SubscriptionID)
	require.Equal(t, "api-dns-tenant", *apiDNSPC.Spec.TenantID)
	require.Equal(t, "api-dns-client", *apiDNSPC.Spec.ClientID)
}

func TestAzureInitProviderConfigs_DNSErrorMessageUsesAzureSubscriptionID(t *testing.T) {
	scheme := runtime.NewScheme()
	require.NoError(t, clientgoscheme.AddToScheme(scheme))

	accountList := struct {
		Accounts []AzureAccountEntry `yaml:"accounts"`
	}{
		Accounts: []AzureAccountEntry{},
	}
	readAccountList = func(_ string) ([]byte, error) {
		return yaml.Marshal(&accountList)
	}

	cfg := makeTestRootConfig()
	cfg.DNSProviderConfig.AzureDNSZone = config.AzureDNSZoneConfig{
		SubscriptionID: "azure-sub-should-appear",
		TenantID:       "dns-tenant",
		ClientID:       "dns-client",
	}

	client := fake.NewClientBuilder().
		WithScheme(scheme).
		Build()

	initializer := &azureInitializer{}
	err := initializer.InitializeProviderConfig(context.Background(), client, "test", &cfg)
	require.Error(t, err)
	require.Contains(t, err.Error(), "upbound ProviderConfig for DNS azure-sub-should-appear failed")
	require.NotContains(t, err.Error(), cfg.DNSProviderConfig.AliCloudDNS.AccountID)
}
