package aks

import (
	"context"
	"testing"

	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
	"github.com/crossplane/crossplane-runtime/pkg/meta"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	azurenetworkv1beta1 "github.com/upbound/provider-azure/apis/network/v1beta1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/utils/ptr"

	"github.com/tidbcloud/infra-provider/regional-server/api/v1alpha1"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers/privatelinkendpoint/task"
)

func newFakeDNSTask(c *task.Context) *dns {
	return NewDNS(c).(*dns)
}

func applyFakePrivateEndpointWithPrivateIP(t *testing.T, c *task.Context, dnsTask *dns, privateIP string) {
	t.Helper()

	pe := &azurenetworkv1beta1.PrivateEndpoint{
		ObjectMeta: metav1.ObjectMeta{
			Name: dnsTask.EndpointName(),
		},
	}
	pe.Status.SetConditions(xpv1.Available())
	pe.Status.AtProvider.PrivateServiceConnection = []azurenetworkv1beta1.PrivateServiceConnectionObservation{
		{
			PrivateIPAddress: ptr.To(privateIP),
		},
	}
	require.NoError(t, c.Local.Create(context.Background(), pe))
}

func TestDNS_Create(t *testing.T) {
	tests := []struct {
		name    string
		mutate  func(*task.Context)
		wantErr bool
		verify  func(*testing.T, *task.Context, *dns)
	}{
		{
			name: "missing root domain",
			mutate: func(c *task.Context) {
				c.PrivateLinkEndpoint.Spec.RootDomainName = ""
				c.PrivateLinkEndpoint.Status.EndpointAddress = "10.0.0.1"
			},
			wantErr: true,
		},
		{
			name: "missing resource group",
			mutate: func(c *task.Context) {
				c.VPC.Status.ResourceGroup.ID = ""
			},
			wantErr: true,
		},
		{
			name: "missing vnet resource id",
			mutate: func(c *task.Context) {
				c.VPC.Status.VPC.ResourceID = ""
			},
			wantErr: true,
		},
		{
			name:    "missing private endpoint ips",
			wantErr: true,
		},
		{
			name:    "success",
			wantErr: false,
			mutate: func(t *task.Context) {
				t.PrivateLinkEndpoint.Status.EndpointAddress = "10.0.0.4"
			},
			verify: func(t *testing.T, c *task.Context, dnsTask *dns) {
				t.Helper()

				zoneDomain := c.PrivateLinkEndpoint.Spec.RootDomainName
				expectedIPs := []*string{ptr.To("10.0.0.4")}

				zone := &azurenetworkv1beta1.PrivateDNSZone{}
				require.NoError(t, c.Local.Get(context.Background(), types.NamespacedName{Name: dnsTask.getDNSResourceName()}, zone))
				assert.Equal(t, dnsTask.ProviderConfigName(), zone.Spec.ProviderConfigReference.Name)
				assert.Equal(t, ptr.To(c.VPC.Status.ResourceGroup.ID), zone.Spec.ForProvider.ResourceGroupName)
				assert.Equal(t, zoneDomain, zone.GetAnnotations()[meta.AnnotationKeyExternalName])

				link := &azurenetworkv1beta1.PrivateDNSZoneVirtualNetworkLink{}
				require.NoError(t, c.Local.Get(context.Background(), types.NamespacedName{Name: dnsTask.getDNSResourceName()}, link))
				assert.Equal(t, dnsTask.ProviderConfigName(), link.Spec.ProviderConfigReference.Name)
				assert.Equal(t, ptr.To(zoneDomain), link.Spec.ForProvider.PrivateDNSZoneName)
				assert.Equal(t, ptr.To(c.VPC.Status.ResourceGroup.ID), link.Spec.ForProvider.ResourceGroupName)
				assert.Equal(t, ptr.To(c.VPC.Status.VPC.ResourceID), link.Spec.ForProvider.VirtualNetworkID)

				base := &azurenetworkv1beta1.PrivateDNSARecord{}
				require.NoError(t, c.Local.Get(context.Background(), types.NamespacedName{Name: dnsTask.getDNSResourceName()}, base))
				assert.Equal(t, expectedIPs, base.Spec.ForProvider.Records)
				assert.Equal(t, ptr.To(zoneDomain), base.Spec.ForProvider.ZoneName)
				assert.Equal(t, ptr.To(c.VPC.Status.ResourceGroup.ID), base.Spec.ForProvider.ResourceGroupName)
				assert.Equal(t, ptr.To(float64(defaultRecordTTLSeconds)), base.Spec.ForProvider.TTL)
				assert.Equal(t, "*", base.GetAnnotations()[meta.AnnotationKeyExternalName])
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			c := fakeTaskContext(t)
			if tt.mutate != nil {
				tt.mutate(c)
			}
			dnsTask := newFakeDNSTask(c)
			if tt.name == "success" {
				applyFakePrivateEndpointWithPrivateIP(t, c, dnsTask, "10.0.0.4")
			}

			result := dnsTask.Create(context.Background())
			if tt.wantErr {
				assert.Error(t, result.Error())
				return
			}
			assert.True(t, result.IsDoneState())
			assert.NoError(t, result.Error())
			if tt.verify != nil {
				tt.verify(t, c, dnsTask)
			}
		})
	}
}

func TestDNS_PostCreate(t *testing.T) {
	t.Run("zone not ready", func(t *testing.T) {
		c := fakeTaskContext(t)
		dnsTask := newFakeDNSTask(c)
		applyFakePrivateEndpointWithPrivateIP(t, c, dnsTask, "10.0.0.4")
		c.PrivateLinkEndpoint.Status.EndpointAddress = "10.0.0.4"

		require.True(t, dnsTask.Create(context.Background()).IsDoneState())
		result := dnsTask.PostCreate(context.Background())
		assert.Error(t, result.Error())
		assert.Equal(t, zoneNotReady, c.PrivateLinkEndpoint.Status.Message)
	})

	t.Run("vnet link not ready", func(t *testing.T) {
		c := fakeTaskContext(t)
		dnsTask := newFakeDNSTask(c)
		applyFakePrivateEndpointWithPrivateIP(t, c, dnsTask, "10.0.0.4")
		c.PrivateLinkEndpoint.Status.EndpointAddress = "10.0.0.4"

		require.True(t, dnsTask.Create(context.Background()).IsDoneState())

		zone := &azurenetworkv1beta1.PrivateDNSZone{}
		require.NoError(t, c.Local.Get(context.Background(), types.NamespacedName{Name: dnsTask.getDNSResourceName()}, zone))
		zone.Status.SetConditions(xpv1.Available())
		require.NoError(t, c.Local.Update(context.Background(), zone))

		result := dnsTask.PostCreate(context.Background())
		assert.Error(t, result.Error())
		assert.Equal(t, vnetLinkNotReady, c.PrivateLinkEndpoint.Status.Message)
	})

	t.Run("record not ready", func(t *testing.T) {
		c := fakeTaskContext(t)
		dnsTask := newFakeDNSTask(c)
		applyFakePrivateEndpointWithPrivateIP(t, c, dnsTask, "10.0.0.4")
		c.PrivateLinkEndpoint.Status.EndpointAddress = "10.0.0.4"

		require.True(t, dnsTask.Create(context.Background()).IsDoneState())

		zone := &azurenetworkv1beta1.PrivateDNSZone{}
		require.NoError(t, c.Local.Get(context.Background(), types.NamespacedName{Name: dnsTask.getDNSResourceName()}, zone))
		zone.Status.SetConditions(xpv1.Available())
		require.NoError(t, c.Local.Update(context.Background(), zone))

		link := &azurenetworkv1beta1.PrivateDNSZoneVirtualNetworkLink{}
		require.NoError(t, c.Local.Get(context.Background(), types.NamespacedName{Name: dnsTask.getDNSResourceName()}, link))
		link.Status.SetConditions(xpv1.Available())
		require.NoError(t, c.Local.Update(context.Background(), link))

		result := dnsTask.PostCreate(context.Background())
		assert.Error(t, result.Error())
		assert.Equal(t, recordNotReady, c.PrivateLinkEndpoint.Status.Message)
	})

	t.Run("all ready", func(t *testing.T) {
		c := fakeTaskContext(t)
		dnsTask := newFakeDNSTask(c)
		applyFakePrivateEndpointWithPrivateIP(t, c, dnsTask, "10.0.0.4")
		c.PrivateLinkEndpoint.Status.EndpointAddress = "10.0.0.4"

		require.True(t, dnsTask.Create(context.Background()).IsDoneState())

		zone := &azurenetworkv1beta1.PrivateDNSZone{}
		require.NoError(t, c.Local.Get(context.Background(), types.NamespacedName{Name: dnsTask.getDNSResourceName()}, zone))
		zone.Status.SetConditions(xpv1.Available())
		require.NoError(t, c.Local.Update(context.Background(), zone))

		link := &azurenetworkv1beta1.PrivateDNSZoneVirtualNetworkLink{}
		require.NoError(t, c.Local.Get(context.Background(), types.NamespacedName{Name: dnsTask.getDNSResourceName()}, link))
		link.Status.SetConditions(xpv1.Available())
		require.NoError(t, c.Local.Update(context.Background(), link))

		recordList := &azurenetworkv1beta1.PrivateDNSARecordList{}
		require.NoError(t, c.Local.List(context.Background(), recordList))
		for i := range recordList.Items {
			recordList.Items[i].Status.SetConditions(xpv1.Available())
			require.NoError(t, c.Local.Update(context.Background(), &recordList.Items[i]))
		}

		result := dnsTask.PostCreate(context.Background())
		assert.True(t, result.IsDoneState())
	})
}

func TestDNS_DeleteAndPostDelete(t *testing.T) {
	t.Run("success", func(t *testing.T) {
		c := fakeTaskContext(t)
		dnsTask := newFakeDNSTask(c)
		applyFakePrivateEndpointWithPrivateIP(t, c, dnsTask, "10.0.0.4")
		c.PrivateLinkEndpoint.Status.EndpointAddress = "10.0.0.4"

		require.True(t, dnsTask.Create(context.Background()).IsDoneState())

		result := dnsTask.Delete(context.Background())
		assert.True(t, result.IsDoneState())

		result = dnsTask.PostDelete(context.Background())
		assert.True(t, result.IsDoneState())
	})

	t.Run("post delete while records exist", func(t *testing.T) {
		c := fakeTaskContext(t)
		dnsTask := newFakeDNSTask(c)
		applyFakePrivateEndpointWithPrivateIP(t, c, dnsTask, "10.0.0.4")
		c.PrivateLinkEndpoint.Status.EndpointAddress = "10.0.0.4"

		require.True(t, dnsTask.Create(context.Background()).IsDoneState())

		result := dnsTask.PostDelete(context.Background())
		assert.Error(t, result.Error())
	})
}

func TestDNS_providerConfigName(t *testing.T) {
	c := fakeTaskContext(t)
	dnsTask := newFakeDNSTask(c)
	applyFakePrivateEndpointWithPrivateIP(t, c, dnsTask, "10.0.0.4")
	c.PrivateLinkEndpoint.Status.EndpointAddress = "10.0.0.4"

	require.True(t, dnsTask.Create(context.Background()).IsDoneState())

	zone := &azurenetworkv1beta1.PrivateDNSZone{}
	require.NoError(t, c.Local.Get(context.Background(), types.NamespacedName{Name: dnsTask.getDNSResourceName()}, zone))
	assert.Equal(t, dnsTask.ProviderConfigName(), zone.Spec.ProviderConfigReference.Name)

	link := &azurenetworkv1beta1.PrivateDNSZoneVirtualNetworkLink{}
	require.NoError(t, c.Local.Get(context.Background(), types.NamespacedName{Name: dnsTask.getDNSResourceName()}, link))
	assert.Equal(t, dnsTask.ProviderConfigName(), link.Spec.ProviderConfigReference.Name)

	record := &azurenetworkv1beta1.PrivateDNSARecord{}
	require.NoError(t, c.Local.Get(context.Background(), types.NamespacedName{Name: dnsTask.getDNSResourceName()}, record))
	assert.Equal(t, dnsTask.ProviderConfigName(), record.Spec.ProviderConfigReference.Name)
}

func TestDNS_applyARecord_labels(t *testing.T) {
	c := fakeTaskContext(t)
	dnsTask := newFakeDNSTask(c)
	applyFakePrivateEndpointWithPrivateIP(t, c, dnsTask, "10.0.0.4")
	c.PrivateLinkEndpoint.Status.EndpointAddress = "10.0.0.4"

	require.True(t, dnsTask.Create(context.Background()).IsDoneState())

	record := &azurenetworkv1beta1.PrivateDNSARecord{}
	require.NoError(t, c.Local.Get(context.Background(), types.NamespacedName{Name: dnsTask.getDNSResourceName()}, record))
	assert.Equal(t, c.PrivateLinkEndpoint.Name, record.Labels[v1alpha1.PrivateLinkEndpointNameKey])
}
