package eks

import (
	"context"
	"fmt"
	"testing"
	"time"

	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
	"github.com/go-logr/logr"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"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/privatelinkservice/task"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/deploy"
	"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/pkg/repository"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/util"
	"github.com/tidbcloud/infra-provider/regional-server/test/kubemock"
	"github.com/tidbcloud/infra-provider/regional-server/test/testdata"
	ec2v1beta1 "github.com/upbound/provider-aws/apis/ec2/v1beta1"
	route53v1beta2 "github.com/upbound/provider-aws/apis/route53/v1beta2"
	"go.uber.org/mock/gomock"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/utils/pointer"
	"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)
	ec2v1beta1.SchemeBuilder.AddToScheme(s)
	route53v1beta2.SchemeBuilder.AddToScheme(s)
	fakeVPC := &v1alpha1.VPC{}
	bs, err := testdata.LoadDefaultVPCYAML()
	require.NoError(t, err)
	require.NoError(t, yaml.Unmarshal(bs, fakeVPC))
	fakepls := &v1alpha1.PrivateLinkService{}
	bs, err = testdata.LoadEKSPrivateLinkService()
	require.NoError(t, err)
	assert.NoError(t, yaml.Unmarshal(bs, fakepls))
	cli := fake.NewClientBuilder().WithScheme(s).WithObjects(fakeVPC, fakepls).Build()
	c := &task.Context{
		Local:                k8s.NewMutClientWithOwner(cli, fakeVPC.DeepCopy(), namer.NewNamer()).Client(),
		Logger:               logr.Discard(),
		VPC:                  fakeVPC.DeepCopy(),
		VPCR:                 repository.NewVPCReader(cli),
		Config:               config.RootConfig{},
		PrivateLinkService:   fakepls.DeepCopy(),
		PrivateLinkServiceRW: repository.NewPrivateLinkServiceReaderWriter(cli),
	}
	c, err = c.Default()
	assert.Nil(t, err)
	return c
}

func TestDNSRecord_CreateDoesNotChangeExistingRecordSpec(t *testing.T) {
	ctx := fakeTaskContext(t)
	ctx.Config.RegionConfig.Name = "us-east-1"
	ctx.Config.DNSProviderConfig.AwsRoute53 = config.AwsRoute53Config{
		AccountID: "123456789012",
		ZoneId:    "new-zone-id",
		Domain:    "example.com",
	}
	ctx.PrivateLinkService.Status.EndpointService.Name = "endpoint-service"
	ctx.PrivateLinkService.Status.EndpointService.PrivateDNSName = "existing-record"

	ves := &ec2v1beta1.VPCEndpointService{
		ObjectMeta: metav1.ObjectMeta{Name: ctx.PrivateLinkService.Status.EndpointService.Name},
		Status: ec2v1beta1.VPCEndpointServiceStatus{
			AtProvider: ec2v1beta1.VPCEndpointServiceObservation{
				PrivateDNSNameConfiguration: []ec2v1beta1.PrivateDNSNameConfigurationObservation{
					{
						Name:  pointer.String("_verification"),
						Type:  pointer.String("TXT"),
						Value: pointer.String("verification-token"),
					},
				},
			},
		},
	}
	require.NoError(t, ctx.Local.Create(context.Background(), ves))

	existingRecord := &route53v1beta2.Record{
		ObjectMeta: metav1.ObjectMeta{Name: ctx.PrivateLinkService.Status.EndpointService.PrivateDNSName},
		Spec: route53v1beta2.RecordSpec{
			ResourceSpec: xpv1.ResourceSpec{
				ProviderConfigReference: &xpv1.Reference{Name: "dns-123456789012"},
			},
			ForProvider: route53v1beta2.RecordParameters{
				Type:   pointer.String("TXT"),
				TTL:    pointer.Float64(defaultDNSTTLSeconds),
				Region: pointer.String("us-east-1"),
				Name:   pointer.String("_verification.example.com"),
				Records: []*string{
					pointer.String("verification-token"),
				},
				ZoneID: pointer.String("old-zone-id"),
			},
		},
	}
	require.NoError(t, ctx.Local.Create(context.Background(), existingRecord))

	result := NewDNSRecord(ctx, nil).Create(context.Background())
	assert.Equal(t, deploy.Done(), result)

	actual := &route53v1beta2.Record{}
	require.NoError(t, ctx.Local.Get(context.Background(), types.NamespacedName{Name: existingRecord.Name}, actual))
	assert.Equal(t, pointer.String("old-zone-id"), actual.Spec.ForProvider.ZoneID)
	assert.Equal(t, pointer.String("_verification.example.com"), actual.Spec.ForProvider.Name)
	assert.Equal(t, []*string{pointer.String("verification-token")}, actual.Spec.ForProvider.Records)
}

func TestEndpointService_Create(t *testing.T) {
	t.Run("create endpointservice failed", func(t *testing.T) {
		task := NewEndpointService(fakeTaskContext(t), nil)
		ctl := gomock.NewController(t)
		k8scli := kubemock.NewMockClient(ctl)
		task.Local = k8scli
		k8scli.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.Any()).Return(apierrors.NewNotFound(schema.GroupResource{}, "not found"))
		k8scli.EXPECT().Create(gomock.Any(), gomock.Any(), gomock.Any()).Return(assert.AnError)
		result := task.Create(context.Background())
		res, err, retriableErr := deploy.MinRequeue([]deploy.Result{result})
		assert.Nil(t, err)
		assert.ErrorContains(t, retriableErr, "assert.AnError")
		assert.Equal(t, time.Second*30, res.RequeueAfter)
	})
	t.Run("create endpointservice success", func(t *testing.T) {
		task := NewEndpointService(fakeTaskContext(t), nil)
		tags := cloudv1.TagsFromLabels(task.PrivateLinkService.Labels)
		tags = util.BuildAWSTagWithName(tags, task.PrivateLinkService.Name)

		ctl := gomock.NewController(t)
		k8scli := kubemock.NewMockClient(ctl)
		task.Local = k8scli
		k8scli.EXPECT().Get(gomock.Any(), types.NamespacedName{
			Name: task.EndpointServiceName(),
		}, gomock.Any()).Return(apierrors.NewNotFound(schema.GroupResource{}, "not found"))
		k8scli.EXPECT().Create(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, obj runtime.Object, opts ...client.CreateOption) error {
			ves, ok := obj.(*ec2v1beta1.VPCEndpointService)
			if !ok {
				return assert.AnError
			}
			assert.Equal(t, task.EndpointServiceName(), ves.Name)
			// assert.Equal(t, labels.NewOwnerLabels(task.PrivateLinkService, nil), ves.Labels)
			assert.Equal(t, task.ProviderConfigName(), ves.Spec.ProviderConfigReference.Name)
			assert.Equal(t, task.PrivateLinkService.Spec.Region, *ves.Spec.ForProvider.Region)
			assert.Equal(t, pointer.Bool(true), ves.Spec.ForProvider.AcceptanceRequired)
			assert.Equal(t, pointer.String(task.privateDNSName()), ves.Spec.ForProvider.PrivateDNSName)
			assert.Equal(t, util.ToMapPointer(tags), ves.Spec.ForProvider.Tags)
			return nil
		})
		// k8scli.EXPECT().Patch(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil)
		result := task.Create(context.Background())
		assert.Equal(t, deploy.Done(), result)
	})
}

func Test_privateDNSName(t *testing.T) {
	task := NewEndpointService(fakeTaskContext(t), nil)
	task.PrivateLinkService.Name = "test"
	assert.Equal(t, fmt.Sprintf("privatelink-%s.%s.%s", task.PrivateLinkService.Name, task.PrivateLinkService.Status.DNSIdentify, task.Config.DNSProviderConfig.AwsRoute53.Domain), task.privateDNSName())

	task.PrivateLinkService.Name = "test.test"
	assert.Equal(t, fmt.Sprintf("privatelink-%s.%s.%s", task.PrivateLinkService.Name[:8], task.PrivateLinkService.Status.DNSIdentify, task.Config.DNSProviderConfig.AwsRoute53.Domain), task.privateDNSName())
}
