package common

import (
	"context"
	"testing"
	"time"

	ec2v1alpha1 "github.com/crossplane-contrib/provider-aws/apis/ec2/v1alpha1"
	ec2v1beta1 "github.com/crossplane-contrib/provider-aws/apis/ec2/v1beta1"
	"github.com/go-logr/logr"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	clusterctlpb "github.com/tidbcloud/infra-provider/api/tidbcloud/cluster-server/v1"
	"github.com/tidbcloud/infra-provider/pkg/deploy"
	"github.com/tidbcloud/infra-provider/pkg/k8s"
	"github.com/tidbcloud/infra-provider/pkg/namer"
	"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"
	respls "github.com/tidbcloud/infra-provider/regional-server/pkg/resourcemanager/privatelinkservice"
	resvpc "github.com/tidbcloud/infra-provider/regional-server/pkg/resourcemanager/vpc"
	"github.com/tidbcloud/infra-provider/regional-server/test/service/mockclustercli"
	"github.com/tidbcloud/infra-provider/regional-server/test/testdata"
	"go.uber.org/mock/gomock"
	"k8s.io/apimachinery/pkg/runtime"
	"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)
	ec2v1alpha1.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).WithStatusSubresource(fakeVPC, fakepls).Build()
	c := &task.Context{
		Local:                k8s.NewMutClientWithOwner(cli, fakeVPC.DeepCopy(), namer.NewNamer()).Client(),
		Logger:               logr.Discard(),
		VPC:                  fakeVPC.DeepCopy(),
		VPCR:                 resvpc.NewVPCReader(cli),
		Config:               config.RootConfig{},
		PrivateLinkService:   fakepls.DeepCopy(),
		PrivateLinkServiceRW: respls.NewPrivateLinkServiceReaderWriter(cli),
		ClusterCli:           nil,
	}
	c, err = c.Default()
	assert.Nil(t, err)
	return c
}

func TestLoadBalancer_Create(t *testing.T) {
	task := NewLoadBalancer(fakeTaskContext(t))
	ctl := gomock.NewController(t)
	clustercli := mockclustercli.NewMockClusterClient(ctl)
	task.ClusterCli = clustercli
	t.Run("get loadbalancer failed", func(t *testing.T) {
		clustercli.EXPECT().GetTidbGroup(gomock.Any(), &clusterctlpb.GetTidbGroupRequest{
			ClusterId:   task.PrivateLinkService.Spec.LoadBalancers[0].ClusterID,
			TidbGroupId: task.PrivateLinkService.Spec.LoadBalancers[0].ID,
		}).Return(nil, 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("loadbalancer not found", func(t *testing.T) {
		clustercli.EXPECT().GetTidbGroup(gomock.Any(), &clusterctlpb.GetTidbGroupRequest{
			ClusterId:   task.PrivateLinkService.Spec.LoadBalancers[0].ClusterID,
			TidbGroupId: task.PrivateLinkService.Spec.LoadBalancers[0].ID,
		}).Return(nil, nil)
		result := task.Create(context.Background())
		res, err, retriableErr := deploy.MinRequeue([]deploy.Result{result})
		assert.Nil(t, err)
		assert.ErrorContains(t, retriableErr, "loadbalancer not found")
		assert.Equal(t, time.Second*30, res.RequeueAfter)
	})
	t.Run("create success", func(t *testing.T) {
		clustercli.EXPECT().GetTidbGroup(gomock.Any(), &clusterctlpb.GetTidbGroupRequest{
			ClusterId:   task.PrivateLinkService.Spec.LoadBalancers[0].ClusterID,
			TidbGroupId: task.PrivateLinkService.Spec.LoadBalancers[0].ID,
		}).Return(&clusterctlpb.TidbGroup{
			Id:              task.PrivateLinkService.Spec.LoadBalancers[0].ID,
			EnablePrivateLb: true,
			PrivateLb: &clusterctlpb.LoadBalancer{
				CloudResourceId: "arn",
				Port:            4000,
				Host:            "host",
			},
			ClusterIdentifierHash: "foo",
		}, nil)

		result := task.Create(context.Background())
		assert.True(t, result.IsDoneState())
		assert.Equal(t, v1alpha1.PrivateLinkServiceStatus{
			DNSIdentify: "foo",
			LoadBalancers: []v1alpha1.LoadBalancerStatus{
				{
					ID:              task.PrivateLinkService.Spec.LoadBalancers[0].ID,
					CloudResourceId: "arn",
					Port:            4000,
					Host:            "host",
					Type:            clusterctlpb.LoadBalancer_PRIVATE.String(),
					Phase:           v1alpha1.LoadBalancerPhaseActive,
				},
			},
		}, task.PrivateLinkService.Status)
	})
	t.Run("create success but not ready", func(t *testing.T) {
		clustercli.EXPECT().GetTidbGroup(gomock.Any(), &clusterctlpb.GetTidbGroupRequest{
			ClusterId:   task.PrivateLinkService.Spec.LoadBalancers[0].ClusterID,
			TidbGroupId: task.PrivateLinkService.Spec.LoadBalancers[0].ID,
		}).Return(&clusterctlpb.TidbGroup{
			Id:              task.PrivateLinkService.Spec.LoadBalancers[0].ID,
			EnablePrivateLb: true,
			PrivateLb: &clusterctlpb.LoadBalancer{
				CloudResourceId: "",
				Port:            4000,
				Host:            "",
			},
			ClusterIdentifierHash: "foo",
		}, nil)

		result := task.Create(context.Background())
		res, err, retriableErr := deploy.MinRequeue([]deploy.Result{result})
		assert.Nil(t, err)
		assert.ErrorContains(t, retriableErr, "loadbalancer is not ready")
		assert.Equal(t, time.Second*30, res.RequeueAfter)
	})
}
