package aks

import (
	"context"
	"testing"

	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
	"github.com/go-logr/logr"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"github.com/tidbcloud/infra-provider/regional-server/api/v1alpha1"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/config"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers/loadbalancer/task"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/k8s"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/namer"
	"github.com/tidbcloud/infra-provider/regional-server/test/testdata"
	azurenetworkv1beta1 "github.com/upbound/provider-azure/apis/network/v1beta1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/utils/ptr"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/client/fake"
	"sigs.k8s.io/yaml"
)

func fakeTaskContext(t *testing.T) *task.Context {
	s := runtime.NewScheme()
	v1alpha1.AddToScheme(s)
	azurenetworkv1beta1.SchemeBuilder.AddToScheme(s)
	fakeVPC := &v1alpha1.VPC{}
	bs, err := testdata.LoadAKSVPCYAML()
	require.NoError(t, err)
	require.NoError(t, yaml.Unmarshal(bs, fakeVPC))
	fakeLB := &v1alpha1.LoadBalancer{}
	bs, err = testdata.LoadAKSLoadBalancer()
	require.NoError(t, err)
	assert.NoError(t, yaml.Unmarshal(bs, fakeLB))
	fakeAPPBox := &v1alpha1.APPBox{}
	bs, err = testdata.LoadAKSAPPBoxYAML()
	require.NoError(t, err)
	require.NoError(t, yaml.Unmarshal(bs, fakeAPPBox))

	cli := fake.NewClientBuilder().WithScheme(s).WithObjects(fakeVPC, fakeLB, fakeAPPBox).WithStatusSubresource(fakeLB).Build()
	c := &task.Context{
		Local:        k8s.NewMutClientWithOwner(cli, fakeVPC.DeepCopy(), namer.NewNamer()).Client(),
		Logger:       logr.Discard(),
		VPC:          fakeVPC.DeepCopy(),
		AppBox:       fakeAPPBox.DeepCopy(),
		Config:       config.RootConfig{},
		LoadBalancer: fakeLB.DeepCopy(),
	}
	c, err = c.Default()
	assert.Nil(t, err)
	return c
}

func newFakeDNS(c *task.Context) *DNS {
	return &DNS{BaseTask: &task.BaseTask{Context: c}}
}

func newFakeDNSTask(t *testing.T) *DNS {
	return newFakeDNS(fakeTaskContext(t))
}

func TestDNS_Create(t *testing.T) {
	t.Run("no lb ip found", func(t *testing.T) {
		dt := newFakeDNSTask(t)
		result := dt.Create(context.Background())
		assert.Error(t, result.Error())
	})

	t.Run("create dns record successfully", func(t *testing.T) {
		dt := newFakeDNSTask(t)
		dt.LoadBalancer.Status.PrivateIPs = []string{"10.0.0.1"}
		result := dt.Create(context.Background())
		assert.True(t, result.IsDoneState())

		dnsName := dt.GetDNSNamePrefix(dt.LoadBalancer)
		record := &azurenetworkv1beta1.DNSARecord{
			ObjectMeta: metav1.ObjectMeta{
				Name: dnsName,
			},
		}
		err := dt.Local.Get(context.Background(), client.ObjectKeyFromObject(record), record)
		assert.Nil(t, err)

		expectedParams := azurenetworkv1beta1.DNSARecordParameters{
			Records:           []*string{ptr.To("10.0.0.1")},
			ZoneName:          ptr.To(dt.Config.DNSProviderConfig.AzureDNSZone.Domain),
			ResourceGroupName: ptr.To(dt.Config.DNSProviderConfig.AzureDNSZone.ResourceGroup),
			TTL:               ptr.To(float64(60)),
		}
		assert.Equal(t, expectedParams, record.Spec.ForProvider)
	})
}

func TestDNS_PostCreate(t *testing.T) {
	t.Run("record is not ready", func(t *testing.T) {
		dt := newFakeDNSTask(t)
		dt.LoadBalancer.Status.PrivateIPs = []string{"10.0.0.1"}
		result := dt.Create(context.Background())
		assert.True(t, result.IsDoneState())

		result = dt.PostCreate(context.Background())
		assert.Error(t, result.Error())

		dnsName := dt.GetDNSName(dt.LoadBalancer, dt.Config.DNSProviderConfig.AzureDNSZone.Domain)
		assert.Equal(t, dnsName, dt.LoadBalancer.Status.DNS)
	})

	t.Run("record is ready", func(t *testing.T) {
		dt := newFakeDNSTask(t)
		dt.LoadBalancer.Status.PrivateIPs = []string{"10.0.0.1"}
		result := dt.Create(context.Background())
		assert.True(t, result.IsDoneState())

		record := &azurenetworkv1beta1.DNSARecord{
			ObjectMeta: metav1.ObjectMeta{
				Name: dt.GetDNSNamePrefix(dt.LoadBalancer),
			},
		}
		err := dt.Local.Get(context.Background(), client.ObjectKeyFromObject(record), record)
		assert.Nil(t, err)
		record.Status.SetConditions(xpv1.Available())
		err = dt.Local.Update(context.Background(), record)
		assert.Nil(t, err)

		result = dt.PostCreate(context.Background())
		assert.True(t, result.IsDoneState())
		dnsName := dt.GetDNSName(dt.LoadBalancer, dt.Config.DNSProviderConfig.AzureDNSZone.Domain)
		assert.Equal(t, dnsName, dt.LoadBalancer.Status.DNS)
		assert.Equal(t, v1alpha1.LoadBalancerPhaseActive, dt.LoadBalancer.Status.Phase)
	})
}

func TestDNS_Delete(t *testing.T) {
	t.Run("record is already deleted", func(t *testing.T) {
		dt := newFakeDNSTask(t)
		result := dt.Delete(context.Background())
		assert.True(t, result.IsDoneState())
	})

	t.Run("delete record successfully", func(t *testing.T) {
		dt := newFakeDNSTask(t)
		dt.LoadBalancer.Status.PrivateIPs = []string{"10.0.0.1"}
		result := dt.Create(context.Background())
		assert.True(t, result.IsDoneState())

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

		record := &azurenetworkv1beta1.DNSARecord{
			ObjectMeta: metav1.ObjectMeta{
				Name: dt.GetDNSNamePrefix(dt.LoadBalancer),
			},
		}
		err := dt.Local.Get(context.Background(), client.ObjectKeyFromObject(record), record)
		assert.Error(t, err)
		assert.True(t, apierrors.IsNotFound(err))
	})
}
