package helper

import (
	"context"
	"time"

	"github.com/onsi/ginkgo"
	"github.com/onsi/gomega"
	"sigs.k8s.io/controller-runtime/pkg/client"

	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"github.com/tidbcloud/shared-provider/tests/e2e/common/data"
	waitutil "github.com/tidbcloud/shared-provider/tests/e2e/common/wait"
	"github.com/tidbcloud/shared-provider/tests/framework"
)

var (
	DefaultTimeout  = time.Minute * 1
	LongTaskTimeout = time.Minute * 20
)

const (
	TypeNetwork = "network"
	TypeCluster = "cluster"
	TypeBackup  = "backup"
)

func Patch(obj client.Object, patches ...data.Patch) client.Object {
	for _, patch := range patches {
		obj = patch.Patch(obj)
	}
	return obj
}

func CreateNetwork(
	ctx context.Context,
	f *framework.Framework,
	wait bool,
	to string,
	patches ...data.Patch,
) *cloudv1.Network {
	ns := f.InfraNamespace
	net := data.NewDefaultNetwork(f.Provider, ns, f.NetworkRegion())
	patches = append(patches, data.WithKubernetesCluster(f.KubeClusterName(to)))
	err := f.InfraClient.Create(ctx, Patch(net, patches...))
	gomega.Expect(err).NotTo(gomega.HaveOccurred())

	if wait {
		ginkgo.By("wait for network normal")
		err = waitutil.WaitForNetworkPhase(
			ctx,
			f.InfraClient,
			net,
			cloudv1.NetworkPhaseNormal,
			LongTaskTimeout,
		)
		gomega.Expect(err).NotTo(gomega.HaveOccurred())
	}

	return net
}

func CreateCluster(
	ctx context.Context,
	f *framework.Framework,
	net *cloudv1.Network,
	wait bool,
	to string,
	patches ...data.Patch,
) *cloudv1.Cluster {
	cluster := data.NewDefaultCluster(f.InfraNamespace, net)
	patches = append(patches, data.WithKubernetesCluster(f.KubeClusterName(to)))
	err := f.InfraClient.Create(ctx, Patch(cluster, patches...))
	gomega.Expect(err).NotTo(gomega.HaveOccurred())

	if wait {
		ginkgo.By("wait for cluster running")
		err = waitutil.WaitForClusterPhase(
			ctx,
			f.InfraClient,
			cluster,
			cloudv1.ClusterPhaseRunning,
			LongTaskTimeout,
		)
		gomega.Expect(err).NotTo(gomega.HaveOccurred())
	}

	return cluster
}

func CreateBackup(
	ctx context.Context,
	f *framework.Framework,
	_ *cloudv1.Network, // not used
	cluster *cloudv1.Cluster,
	wait bool,
	to string,
	patches ...data.Patch,
) *cloudv1.Backup {
	backup := data.NewDefaultBackup(f.InfraNamespace, cluster)
	patches = append(patches, data.WithKubernetesCluster(f.KubeClusterName(to)))
	err := f.InfraClient.Create(ctx, Patch(backup, patches...))
	gomega.Expect(err).NotTo(gomega.HaveOccurred())

	if wait {
		ginkgo.By("wait for backup complete")
		err = waitutil.WaitForBackupPhase(
			ctx,
			f.InfraClient,
			backup,
			cloudv1.BackupComplete,
			LongTaskTimeout,
		)
		gomega.Expect(err).NotTo(gomega.HaveOccurred())
	}

	return backup
}

func CreateRestore(
	ctx context.Context,
	f *framework.Framework,
	_ *cloudv1.Network,
	cluster *cloudv1.Cluster,
	backup *cloudv1.Backup,
	wait bool,
	to string,
	patches ...data.Patch,
) *cloudv1.Restore {
	restore := data.NewDefaultRestore(f.InfraNamespace, backup.Name, cluster)
	patches = append(patches, data.WithKubernetesCluster(f.KubeClusterName(to)))
	err := f.InfraClient.Create(ctx, Patch(restore, patches...))
	gomega.Expect(err).NotTo(gomega.HaveOccurred())

	if wait {
		ginkgo.By("wait for restore complete")
		err = waitutil.WaitForRestorePhase(
			ctx,
			f.InfraClient,
			restore,
			cloudv1.RestoreComplete,
			LongTaskTimeout,
		)
		gomega.Expect(err).NotTo(gomega.HaveOccurred())
	}

	return restore
}

func Create(
	ctx context.Context,
	f *framework.Framework,
	obj string,
	wait bool,
	to string,
	patches ...data.Patch,
) (*cloudv1.Network, *cloudv1.Cluster, *cloudv1.Backup) {
	switch obj {
	case TypeNetwork:
		ginkgo.By("create network")
		net := CreateNetwork(ctx, f, wait, to, patches...)
		return net, nil, nil
	case TypeCluster:
		ginkgo.By("create network and cluster")
		net := CreateNetwork(ctx, f, true, to, patches...)
		cluster := CreateCluster(ctx, f, net, wait, to, patches...)
		return net, cluster, nil
	case TypeBackup:
		ginkgo.By("create network, cluster and backup")
		net := CreateNetwork(ctx, f, true, to, patches...)
		cluster := CreateCluster(ctx, f, net, true, to, patches...)
		backup := CreateBackup(ctx, f, net, cluster, wait, to, patches...)
		return net, cluster, backup
	}

	return nil, nil, nil
}
