package gcp_test

import (
	"context"
	"testing"
	"time"

	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
	gcpvpcpeeringv1beta1 "github.com/crossplane/provider-gcp/apis/vpcpeering/v1beta1"
	"github.com/go-logr/logr/testr"
	"github.com/stretchr/testify/require"
	_ "k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apimachinery/pkg/runtime"
	_ "sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/client/fake"
	"sigs.k8s.io/yaml"

	"github.com/tidbcloud/infra-provider/pkg/deploy"
	"github.com/tidbcloud/infra-provider/regional-server/api/v1alpha1"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers/vpcpeering/task"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers/vpcpeering/task/gcp"
	"github.com/tidbcloud/infra-provider/regional-server/test/testdata"
)

func TestPeering(t *testing.T) {
	s := runtime.NewScheme()
	require.NoError(t, v1alpha1.SchemeBuilder.AddToScheme(s))
	require.NoError(t, gcpvpcpeeringv1beta1.SchemeBuilder.AddToScheme(s))
	defaultVPCPeering := new(v1alpha1.VPCPeering)
	defaultVPC := new(v1alpha1.VPC)
	{
		bs, err := testdata.LoadDefaultVPCPeeringYAML()
		require.NoError(t, err)
		require.NoError(t, yaml.Unmarshal(bs, defaultVPCPeering))
	}
	{
		bs, err := testdata.LoadDefaultVPCYAML()
		require.NoError(t, err)
		require.NoError(t, yaml.Unmarshal(bs, defaultVPC))
	}
	{
		// deploy under normal case should return without error.
		vpcpeering := defaultVPCPeering.DeepCopy()
		vpcpeering.Status.Phase = v1alpha1.VPCPeeringPhaseCreating
		vpcpeering.Status.ExternalName = ""
		vpcpeering.Status.CrossplaneResourceName = ""
		vpcpeering.Spec.Provider = "gcp"
		vpcpeering.Spec.Region = "us-west1"
		vpcpeering.Spec.Target.AWS = nil
		vpcpeering.Spec.Target.GCP = &v1alpha1.GCP{
			ProjectID:   "bar",
			PeerNetwork: "vpc-bar",
			PeerCIDR:    "10.30.0.0/16",
		}
		delete(vpcpeering.Annotations, v1alpha1.LabelKeyExternalName)
		vpc := defaultVPC.DeepCopy()
		vpc.Spec.Provider = "gcp"
		vpc.Spec.Region = "us-west1"
		kubeClient := fake.NewClientBuilder().WithScheme(s).WithObjects(vpcpeering, vpc).WithStatusSubresource(vpcpeering, vpc, &gcpvpcpeeringv1beta1.Peering{}).Build()
		c, err := (&task.Context{
			Local:        kubeClient,
			ProviderType: "aws",
			VPCPeering:   vpcpeering,
			VPC:          vpc,
		}).Default()
		require.NoError(t, err)
		task := gcp.NewPeering(c)
		{
			d := deploy.NewDeployer([]deploy.Task{task}, nil, testr.New(t))
			results := d.Deploy(context.TODO())
			require.Contains(t, task.VPCPeering.GetFinalizers(), v1alpha1.FinalizerVPCPeering)
			require.NoError(t, results[0].Error())
			require.True(t, isAllDone(results), "deploy results: %v", results)
			res, _, _ := deploy.MinRequeue(results)
			require.Greater(t, res.RequeueAfter, time.Duration(0))
			list := new(gcpvpcpeeringv1beta1.PeeringList)
			require.NoError(t, kubeClient.List(context.TODO(), list))
			require.Len(t, list.Items, 1)
			peering := &list.Items[0]
			require.Equal(t, "foo", peering.Spec.ResourceSpec.ProviderConfigReference.Name)
			require.Equal(t, "vpc-foo", peering.Spec.ForProvider.Network)
			// gcp check if the peering name is rfc 1035 label name
			require.Regexp(t, "(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?)", peering.Spec.ForProvider.Name)
			require.Equal(t, "foo", peering.Spec.ForProvider.Project)
			require.Equal(t, "https://www.googleapis.com/compute/v1/projects/bar/global/networks/vpc-bar", peering.Spec.ForProvider.PeerNetwork)
			require.Contains(t, c.VPCPeering.Status.CrossplaneResourceName, peering.Name)
			// ↓ update crossplane cr, tidbcloud's provider-gcp use external-name in a different way.
			peering.Annotations["crossplane.io/external-name"] = peering.Name
			peering.Status.SetConditions(xpv1.Available(), xpv1.ReconcileSuccess())
			peering.Status.AtProvider.Peering = "https://www.googleapis.com/compute/v1/projects/bar/global/networks/vpc-bar"
			require.NoError(t, kubeClient.Status().Update(context.TODO(), peering))
			require.NoError(t, kubeClient.Update(context.TODO(), peering))
			// ↑ update crossplane cr
		}
		{
			// check status and anno on vpcpeering cr again after crossplane cr being updated
			list := new(gcpvpcpeeringv1beta1.PeeringList)
			require.NoError(t, kubeClient.List(context.TODO(), list))
			require.Len(t, list.Items, 1)
			peering := &list.Items[0]
			d := deploy.NewDeployer([]deploy.Task{task}, nil, testr.New(t))
			results := d.Deploy(context.TODO())
			require.NoError(t, results[0].Error())
			require.True(t, isAllDone(results), "deploy results: %v", results)
			res, _, _ := deploy.MinRequeue(results)
			require.Greater(t, res.RequeueAfter, time.Duration(0))
			// the real external name is `.Spec.ForProvider.Name`
			require.Equal(t, peering.Spec.ForProvider.Name, c.VPCPeering.Labels[v1alpha1.LabelKeyExternalName])
			require.Equal(t, peering.Spec.ForProvider.Name, c.VPCPeering.Status.ExternalName)
			require.Equal(t, v1alpha1.VPCPeeringPhaseActive, c.VPCPeering.Status.Phase)
		}
		{
			// delete under noraml case should delete crossplane cr
			d := deploy.NewDeployer([]deploy.Task{task}, nil, testr.New(t))
			results := d.Destroy(context.TODO())
			require.NoError(t, results[0].Error())
			require.True(t, isAllDone(results), "deploy results: %v", results)
			res, _, _ := deploy.MinRequeue(results)
			require.Equal(t, res.RequeueAfter, time.Duration(0))
			require.False(t, res.Requeue)
			list := new(gcpvpcpeeringv1beta1.PeeringList)
			require.NoError(t, kubeClient.List(context.TODO(), list))
			require.Len(t, list.Items, 0)
		}
	}
}

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