package common_test

import (
	"context"
	"testing"
	"time"

	"github.com/go-logr/logr/testr"
	"github.com/stretchr/testify/require"
	"go.uber.org/mock/gomock"
	"k8s.io/apimachinery/pkg/runtime"
	"sigs.k8s.io/controller-runtime/pkg/client/fake"
	"sigs.k8s.io/yaml"

	"github.com/tidbcloud/infra-provider/pkg/deploy"
	"github.com/tidbcloud/infra-provider/pkg/util"
	"github.com/tidbcloud/infra-provider/regional-server/api/v1alpha1"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers/approute/task"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers/approute/task/common"
	resapproute "github.com/tidbcloud/infra-provider/regional-server/pkg/resourcemanager/approute"
	"github.com/tidbcloud/infra-provider/regional-server/test/service/mockclustercli"
	"github.com/tidbcloud/infra-provider/regional-server/test/service/mockgcpcontainer"
	"github.com/tidbcloud/infra-provider/regional-server/test/testdata"
)

func TestGuardianDeploy(t *testing.T) {
	s := runtime.NewScheme()
	require.NoError(t, v1alpha1.AddToScheme(s))
	logger := testr.New(t)
	ctrl := gomock.NewController(t)
	clusterCli := mockclustercli.NewMockClusterClient(ctrl)
	defaultAPPBox := new(v1alpha1.APPBox)
	defaultAppRoute := new(v1alpha1.APPRoute)
	defaultVPC := new(v1alpha1.VPC)
	{
		bs, err := testdata.LoadDefaultAPPBoxYAML()
		require.NoError(t, err)
		require.NoError(t, yaml.Unmarshal(bs, defaultAPPBox))
	}
	{
		bs, err := testdata.LoadDefaultAPPRouteYAML()
		require.NoError(t, err)
		require.NoError(t, yaml.Unmarshal(bs, defaultAppRoute))
	}
	{
		bs, err := testdata.LoadDefaultVPCYAML()
		require.NoError(t, err)
		require.NoError(t, yaml.Unmarshal(bs, defaultVPC))
	}
	{
		// normal case should not return any error.
		approute := defaultAppRoute.DeepCopy()
		approute.Status.Phase = ""
		appbox := defaultAPPBox.DeepCopy()
		vpc := defaultVPC.DeepCopy()
		kubeClient := fake.NewClientBuilder().WithScheme(s).WithObjects(
			vpc,
			approute,
			appbox).Build()
		clusterCli.EXPECT().CreateCluster(gomock.Any(), gomock.Any()).Times(0)
		c, err := (&task.Context{
			Scheme:     s,
			Local:      kubeClient,
			Logger:     logger,
			APPRoute:   approute,
			APPRouteRW: resapproute.NewAPPRouteReaderWriter(kubeClient),
			BeginAt:    util.Pointer(time.Now()),
			ClusterCli: clusterCli,
		}).Default()
		require.NoError(t, err)
		task := common.NewGuardian(c)
		d := deploy.NewDeployer([]deploy.Task{task}, nil, logger)
		results := d.Deploy(context.TODO())
		require.Contains(t, task.APPRoute.GetFinalizers(), v1alpha1.Finalizer)
		require.Equal(t, task.APPRoute.Status.Phase, v1alpha1.APPRoutePhaseCreating)
		require.True(t, isAllDone(results))
		require.NoError(t, results[0].Error())
		ctrl.Satisfied()
	}
	{
		// missing finalizers case
		approute := defaultAppRoute.DeepCopy()
		approute.Finalizers = []string{}
		approute.Status.Phase = v1alpha1.APPRoutePhaseCreating
		appbox := defaultAPPBox.DeepCopy()
		vpc := defaultVPC.DeepCopy()
		kubeClient := fake.NewClientBuilder().WithScheme(s).WithObjects(
			vpc,
			approute,
			appbox).Build()
		clusterCli.EXPECT().CreateCluster(gomock.Any(), gomock.Any()).Times(0)
		c, err := (&task.Context{
			Scheme:     s,
			Local:      kubeClient,
			Logger:     logger,
			APPRoute:   approute,
			BeginAt:    util.Pointer(time.Now()),
			ClusterCli: clusterCli,
		}).Default()
		require.NoError(t, err)
		task := common.NewGuardian(c)
		d := deploy.NewDeployer([]deploy.Task{task}, nil, logger)
		results := d.Deploy(context.TODO())
		require.Contains(t, task.APPRoute.GetFinalizers(), v1alpha1.Finalizer)
		require.Equal(t, task.APPRoute.Status.Phase, v1alpha1.APPRoutePhaseCreating)
		require.False(t, isAllDone(results))
		require.Error(t, results[0].Error())
		ctrl.Satisfied()
	}
	{
		// invalid status case
		approute := defaultAppRoute.DeepCopy()
		approute.Finalizers = []string{}
		approute.Status.Phase = v1alpha1.APPRoutePhaseDeleting
		appbox := defaultAPPBox.DeepCopy()
		vpc := defaultVPC.DeepCopy()
		kubeClient := fake.NewClientBuilder().WithScheme(s).WithObjects(
			vpc,
			approute,
			appbox).Build()
		clusterCli.EXPECT().CreateCluster(gomock.Any(), gomock.Any()).Times(0)
		c, err := (&task.Context{
			Scheme:     s,
			Local:      kubeClient,
			Logger:     logger,
			APPRoute:   approute,
			BeginAt:    util.Pointer(time.Now()),
			ClusterCli: clusterCli,
		}).Default()
		require.NoError(t, err)
		task := common.NewGuardian(c)
		d := deploy.NewDeployer([]deploy.Task{task}, nil, logger)
		results := d.Deploy(context.TODO())
		require.Contains(t, task.APPRoute.GetFinalizers(), v1alpha1.Finalizer)
		require.False(t, isAllDone(results))
		require.Error(t, results[0].Error())
		ctrl.Satisfied()
	}
	{
		// having gcp cluster manager client when provider is gcp
		approute := defaultAppRoute.DeepCopy()
		approute.Status.Phase = v1alpha1.APPRoutePhaseCreating
		appbox := defaultAPPBox.DeepCopy()
		appbox.Spec.Provider = "gcp"
		vpc := defaultVPC.DeepCopy()
		kubeClient := fake.NewClientBuilder().WithScheme(s).WithObjects(
			vpc,
			approute,
			appbox).Build()
		clusterCli.EXPECT().CreateCluster(gomock.Any(), gomock.Any()).Times(0)
		c, err := (&task.Context{
			Scheme:          s,
			Local:           kubeClient,
			APPRouteRW:      resapproute.NewAPPRouteReaderWriter(kubeClient),
			Logger:          logger,
			APPRoute:        approute,
			BeginAt:         util.Pointer(time.Now()),
			GCPContainerCli: mockgcpcontainer.NewMockClusterGetter(ctrl),
		}).Default()
		require.NoError(t, err)
		task := common.NewGuardian(c)
		d := deploy.NewDeployer([]deploy.Task{task}, nil, logger)
		results := d.Deploy(context.TODO())
		require.Contains(t, task.APPRoute.GetFinalizers(), v1alpha1.Finalizer)
		require.True(t, isAllDone(results))
		require.NoError(t, results[0].Error())
		ctrl.Satisfied()
	}
}

func TestGuardianDestroy(t *testing.T) {
	s := runtime.NewScheme()
	require.NoError(t, v1alpha1.AddToScheme(s))
	logger := testr.New(t)
	ctrl := gomock.NewController(t)
	clusterCli := mockclustercli.NewMockClusterClient(ctrl)
	defaultAppRoute := new(v1alpha1.APPRoute)
	{
		bs, err := testdata.LoadDefaultAPPRouteYAML()
		require.NoError(t, err)
		require.NoError(t, yaml.Unmarshal(bs, defaultAppRoute))
	}
	{
		approute := defaultAppRoute.DeepCopy()
		kubeClient := fake.NewClientBuilder().WithScheme(s).WithObjects(
			approute).Build()
		clusterCli.EXPECT().CreateCluster(gomock.Any(), gomock.Any()).Times(0)
		c, err := (&task.Context{
			Scheme:     s,
			Local:      kubeClient,
			Logger:     logger,
			APPRoute:   approute,
			BeginAt:    util.Pointer(time.Now()),
			ClusterCli: clusterCli,
		}).Default()
		require.NoError(t, err)
		task := common.NewGuardian(c)
		d := deploy.NewDeployer([]deploy.Task{task}, nil, logger)
		results := d.Destroy(context.TODO())
		require.True(t, isAllDone(results))
		require.NoError(t, results[0].Error())
		ctrl.Satisfied()
	}
}

func isAllDone(results []deploy.Result) bool {
	for _, r := range results {
		if !r.IsDoneState() {
			return false
		}
	}
	return true
}
