// Copyright 2022 PingCAP, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.

package fixtures

import (
	"context"
	"fmt"
	"time"

	. "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"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"
	kclient "sigs.k8s.io/controller-runtime/pkg/client"

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

type importTestContext struct {
	framework   *framework.Framework
	provider    provider.Provider
	ctx         context.Context
	client      kclient.Client
	testContext framework.TestContext
	ns          *corev1.Namespace
	network     *cloudv1.Network
	cluster     *cloudv1.Cluster
}

func (c *importTestContext) prepareNetworkAndCluster() {
	startTime := time.Now()
	c.network = c.provider.NewNetwork(c.ctx)
	c.network.Namespace = c.ns.Name
	c.framework.ExpectNoError(c.client.Create(c.ctx, c.network), "create network")
	c.framework.Log("network: %s", c.network.Name)
	Eventually(func() error {
		err := c.client.Get(c.ctx, types.NamespacedName{Namespace: c.network.Namespace, Name: c.network.Name}, c.network)
		if err != nil {
			return err
		}
		if c.network.Status.Phase != cloudv1.NetworkPhaseNormal {
			return errors.New("network status is not normal")
		}

		return nil

	}, c.testContext.NetworkCreateTimeout, c.testContext.Interval).Should(Succeed(), fmt.Sprintf("network status to normal within %v", c.testContext.NetworkCreateTimeout))
	c.framework.Log("create network used: %s", time.Since(startTime))

	startTime = time.Now()
	c.cluster = c.provider.NewCluster(c.ctx, c.network)
	c.framework.ExpectNoError(c.client.Create(c.ctx, c.cluster), "create cluster")
	c.framework.Log("cluster: %s", c.cluster.Name)
	Eventually(func() error {
		err := c.client.Get(c.ctx, types.NamespacedName{Namespace: c.cluster.Namespace, Name: c.cluster.Name}, c.cluster)
		if err != nil {
			return err
		}
		if c.cluster.Status.Phase != cloudv1.ClusterPhaseRunning {
			return errors.New("cluster status is not running")
		}

		return nil
	}, c.testContext.ClusterCreateTimeout, c.testContext.Interval).Should(Succeed(), fmt.Sprintf("cluster status to normal within %v", c.testContext.ClusterCreateTimeout))
	c.framework.Log("create cluster used: %s", time.Since(startTime))

	_, err := c.provider.ConnectNetwork(c.ctx, c.client, c.network)
	c.framework.ExpectNoError(err, "connect to network")
}

func (c *importTestContext) deleteNetworkAndCluster() {
	startTime := time.Now()
	c.framework.ExpectNoError(c.client.Delete(c.ctx, c.cluster), "delete cluster")
	Eventually(func() error {
		err := c.client.Get(c.ctx, types.NamespacedName{Namespace: c.cluster.Namespace, Name: c.cluster.Name}, c.cluster)
		if err != nil && !apierrors.IsNotFound(err) {
			return err
		}
		if apierrors.IsNotFound(err) {
			return nil
		}
		return errors.New("cluster is not deleted")
	}, c.testContext.ClusterDeleteTimeout, c.testContext.Interval).Should(Succeed(), fmt.Sprintf("cluster is deleted within %v", c.testContext.NetworkDeleteTimeout))
	c.framework.Log("delete cluster used: %s", time.Since(startTime))

	_, err := c.provider.ConnectNetwork(c.ctx, c.client, c.network)
	c.framework.ExpectNoError(err, "connect to network")

	startTime = time.Now()
	c.framework.ExpectNoError(c.client.Delete(c.ctx, c.network), "delete network")
	Eventually(func() error {
		err := c.client.Get(c.ctx, types.NamespacedName{Namespace: c.network.Namespace, Name: c.network.Name}, c.network)
		if err != nil && !apierrors.IsNotFound(err) {
			return err
		}
		if apierrors.IsNotFound(err) {
			return nil
		}
		return errors.New("network is not deleted")
	}, c.testContext.NetworkDeleteTimeout, c.testContext.Interval).Should(Succeed(), fmt.Sprintf("network is deleted within %v", c.testContext.NetworkDeleteTimeout))
	c.framework.Log("delete network used: %s", time.Since(startTime))
}

var _ = Describe("import", func() {
	ictx := &importTestContext{
		framework:   framework.DefaultFramework,
		provider:    framework.DefaultFramework.Provider,
		ctx:         context.Background(),
		client:      framework.DefaultFramework.InfraCli,
		testContext: framework.DefaultFramework.TestContext,
	}

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

	AfterEach(func() {
		ictx.framework.ExpectNoError(ictx.client.Delete(ictx.ctx, ictx.ns), "delete namespace")
	})

	It("import capability", func() {
		ictx.prepareNetworkAndCluster()

		startTime := time.Now()
		importIns := ictx.provider.NewImport(ictx.ctx, ictx.cluster)
		ictx.framework.ExpectNoError(ictx.client.Create(ictx.ctx, importIns), "create import")
		ictx.framework.Log("import: %s", importIns.Name)
		Eventually(func() error {
			err := ictx.client.Get(ictx.ctx, types.NamespacedName{Namespace: importIns.Namespace, Name: importIns.Name}, importIns)
			if err != nil {
				return err
			}
			if importIns.Status.Phase != cloudv1.ImportCompleted {
				return errors.New("import status is not completed")
			}

			return nil
		}, ictx.testContext.ClusterCreateTimeout, ictx.testContext.Interval).Should(Succeed(), fmt.Sprintf("import status to normal within %v", ictx.testContext.ClusterCreateTimeout))
		ictx.framework.Log("import used: %s", time.Since(startTime))
		ictx.framework.ExpectNoError(ictx.client.Delete(ictx.ctx, importIns), "delete import")

		ictx.deleteNetworkAndCluster()
	})
})
