package fixtures

import (
	"context"
	"fmt"
	"strconv"

	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"
	"github.com/pingcap/errors"
	corev1 "k8s.io/api/core/v1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apimachinery/pkg/api/resource"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"

	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"github.com/tidbcloud/infra-api/pkg/k8s"
	"github.com/tidbcloud/infra-api/test/e2ev2/framework"
)

var _ = Describe("cluster", func() {
	var (
		f   = framework.DefaultFramework
		p   = f.Provider
		ctx = context.Background()
		c   = f.InfraCli
		t   = f.TestContext
		ns  *corev1.Namespace
	)

	BeforeEach(func() {
		ns = &corev1.Namespace{
			ObjectMeta: metav1.ObjectMeta{
				GenerateName: fmt.Sprintf("e2e-%s-", p.Name()),
			},
		}
		f.ExpectNoError(c.Create(ctx, ns), "create namespace")
		f.Log("namespace: %s", ns.Name)
	})

	AfterEach(func() {
		f.ExpectNoError(c.Delete(ctx, ns), "delete namespace")
	})

	It("cluster capability", func() {
		network := p.NewNetwork(ctx)
		network.Namespace = ns.Name
		f.ExpectNoError(c.Create(ctx, network), "create network")
		f.Log("network: %s", network.Name)

		Eventually(func() error {
			err := c.Get(ctx, types.NamespacedName{Namespace: network.Namespace, Name: network.Name}, network)
			if err != nil {
				return err
			}
			if network.Status.Phase != cloudv1.NetworkPhaseNormal {
				return errors.New("network status is not normal")
			}

			return nil

		}, t.NetworkCreateTimeout, t.Interval).Should(Succeed(), fmt.Sprintf("network status to normal within %v", t.NetworkCreateTimeout))

		cluster := p.NewCluster(ctx, network)

		f.ExpectNoError(c.Create(ctx, cluster), "create cluster")
		f.Log("cluster: %s", cluster.Name)

		Eventually(func() error {
			err := c.Get(ctx, types.NamespacedName{Namespace: cluster.Namespace, Name: cluster.Name}, cluster)
			if err != nil {
				return err
			}
			if cluster.Status.Phase != cloudv1.ClusterPhaseRunning {
				return errors.New("cluster status is not running")
			}

			return nil
		}, t.ClusterCreateTimeout, t.Interval).Should(Succeed(), fmt.Sprintf("cluster status to normal within %v", t.ClusterCreateTimeout))

		tenantCli, err := p.ConnectNetwork(ctx, c, network)
		f.ExpectNoError(err, "connect to network")

		f.ExpectNoError(p.AfterClusterCreated(ctx, c, tenantCli, cluster), "after cluster created")

		if p.SupportTiFlash() {
			cluster.Spec.Cluster.TiFlash.Replicas = 1

			tiFlashGp3IOPS, err := strconv.Atoi(k8s.TiFlashGp3IOPS)
			f.ExpectNoError(err, "convert TiFlashGp3IOPS to int")

			cluster.Spec.Cluster.TiFlash.StorageSize = resource.MustParse(fmt.Sprintf("%dGi", tiFlashGp3IOPS/k8s.IopsToVolumeSizeRatioMaximum))
			f.ExpectNoError(c.Update(ctx, cluster), "add a tiflash nodes")

			Eventually(func() error {
				err := c.Get(ctx, types.NamespacedName{Namespace: cluster.Namespace, Name: cluster.Name}, cluster)
				if err != nil {
					return err
				}
				if cluster.Status.DB != nil && cluster.Status.DB.TiFlash != nil && cluster.Status.DB.TiFlash.Status == cloudv1.ComponentNormal {
					return nil
				}
				return errors.New("tiflash status is not normal")
			}, t.ClusterCreateTimeout, t.Interval).Should(Succeed(), fmt.Sprintf("tiflash status is not normal %v", t.ClusterCreateTimeout))
		}

		// TODO(zhengjiajin): test scaling cluster

		f.ExpectNoError(c.Delete(ctx, cluster), "delete cluster")

		Eventually(func() error {
			err := c.Get(ctx, types.NamespacedName{Namespace: cluster.Namespace, Name: cluster.Name}, cluster)
			if err != nil && !apierrors.IsNotFound(err) {
				return err
			}
			if apierrors.IsNotFound(err) {
				return nil
			}
			return errors.New("cluster is not deleted")
		}, t.ClusterDeleteTimeout, t.Interval).Should(Succeed(), fmt.Sprintf("cluster is deleted within %v", t.NetworkDeleteTimeout))

		f.ExpectNoError(p.AfterClusterDeleted(ctx, c, tenantCli, cluster), "after cluster deleted")

		f.ExpectNoError(c.Delete(ctx, network), "delete network")

		Eventually(func() error {
			err := c.Get(ctx, types.NamespacedName{Namespace: network.Namespace, Name: network.Name}, network)
			if err != nil && !apierrors.IsNotFound(err) {
				return err
			}
			if apierrors.IsNotFound(err) {
				return nil
			}
			return errors.New("network is not deleted")
		}, t.NetworkDeleteTimeout, t.Interval).Should(Succeed(), fmt.Sprintf("network is deleted within %v", t.NetworkDeleteTimeout))
	})
})
