package e2e

import (
	"context"
	"encoding/json"
	"flag"
	"fmt"
	"math/rand"
	"os"
	"testing"
	"time"

	"github.com/tidbcloud/infra-api/pkg/consts"

	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"
	"go.uber.org/multierr"
	"go.uber.org/zap"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes/scheme"
	"k8s.io/client-go/tools/clientcmd"
	"sigs.k8s.io/controller-runtime/pkg/client"

	"github.com/tidbcloud/infra-api/pkg/k8s"

	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
)

// node 1
var namespacePrefix string

// all nodes
var kubeconfig string
var isLocalTest bool
var infraCli client.Client
var ctx context.Context
var logger *zap.SugaredLogger

type E2EEnv struct {
	Namespace  string
	AwsNetwork *cloudv1.Network
	GcpNetwork *cloudv1.Network
	Config     *Config
	Project    string
}

var e2eEnv E2EEnv

func TestMain(m *testing.M) {
	flags := flag.CommandLine
	flags.StringVar(&kubeconfig, "kube-config", os.Getenv("KUBECONFIG"), "the kubeconfig path to access infra apiserver")
	flags.StringVar(&namespacePrefix, "namespace", "e2e", "the namespace prefix to run e2e test")
	flag.Parse()

	RegisterFailHandler(Fail)
	rand.Seed(time.Now().UnixNano())
	os.Exit(m.Run())
}

func TestE2E(t *testing.T) {
	RunSpecs(t, "E2E Suite")
}

var _ = SynchronizedBeforeSuite(func() []byte {
	// run synchronized
	//ini config
	e2eEnv.Config = InitialConfig()

	// randomize a namespace name to avoid collision
	e2eEnv.Namespace = fmt.Sprintf("%s-%d", namespacePrefix, time.Now().UnixNano())
	e2eEnv.Project = fmt.Sprintf("%d", time.Now().UnixNano())

	baseLog, err := zap.NewDevelopment()
	Expect(err).To(Succeed())
	defer func(baseLog *zap.Logger) {
		_ = baseLog.Sync()
	}(baseLog)
	logger = baseLog.Sugar()
	restConfig, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
	Expect(err).To(Succeed())
	Expect(cloudv1.AddToScheme(scheme.Scheme)).To(Succeed())
	infraCli, err = client.New(restConfig, client.Options{
		Scheme: scheme.Scheme,
	})
	ctx = context.Background()
	Expect(err).To(Succeed())

	ns := &corev1.Namespace{
		ObjectMeta: metav1.ObjectMeta{
			Name: e2eEnv.Namespace,
		},
	}
	_, err = k8s.CreateIgnoreExists(ctx, infraCli, ns)
	Expect(err).To(Succeed())
	logger.Infow("run E2E tests with namespace", "namespace", e2eEnv.Namespace)

	//ini network
	//create aws network in us-west-2
	e2eEnv.AwsNetwork = newCommonNetwork(e2eEnv.Config.AWS.Region, e2eEnv.Project, consts.ProviderTypeAWS)
	_, err = k8s.CreateIgnoreExists(ctx, infraCli, e2eEnv.AwsNetwork)
	Expect(err).Should(Succeed())
	waitNetworkReady(ctx, e2eEnv.AwsNetwork, 45*time.Minute, 10*time.Second)

	//create gcp network in us-west1
	e2eEnv.GcpNetwork = newCommonNetwork(e2eEnv.Config.GCP.Region, e2eEnv.Project, consts.ProviderTypeGCP)
	_, err = k8s.CreateIgnoreExists(ctx, infraCli, e2eEnv.GcpNetwork)
	Expect(err).Should(Succeed())
	waitNetworkReady(ctx, e2eEnv.GcpNetwork, 45*time.Minute, 10*time.Second)

	buf, err := json.Marshal(e2eEnv)
	Expect(err).Should(BeNil())
	return buf

}, func(fromNode1 []byte) {
	// run on all nodes
	var env E2EEnv
	err := json.Unmarshal(fromNode1, &env)
	Expect(err).Should(BeNil())
	e2eEnv = env

	baseLog, err := zap.NewDevelopment()
	Expect(err).To(Succeed())
	defer func(baseLog *zap.Logger) {
		_ = baseLog.Sync()
	}(baseLog)
	logger = baseLog.Sugar()
	restConfig, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
	Expect(err).To(Succeed())
	Expect(cloudv1.AddToScheme(scheme.Scheme)).To(Succeed())
	infraCli, err = client.New(restConfig, client.Options{
		Scheme: scheme.Scheme,
	})
	ctx = context.Background()
	Expect(err).To(Succeed())

	_, isLocalTest = os.LookupEnv("LOCAL_TEST")
})

var _ = SynchronizedAfterSuite(func() {
	// run on all nodes
}, func() {
	// run synchronized
	// cleanup all resources to avoid resource leak
	var errs error
	if err := infraCli.DeleteAllOf(ctx, &cloudv1.TiCDC{}, client.InNamespace(e2eEnv.Namespace), client.MatchingLabels(e2eResourceLabels())); err != nil {
		errs = multierr.Append(errs, err)
	}
	if err := infraCli.DeleteAllOf(ctx, &cloudv1.Import{}, client.InNamespace(e2eEnv.Namespace), client.MatchingLabels(e2eResourceLabels())); err != nil {
		errs = multierr.Append(errs, err)
	}
	if err := infraCli.DeleteAllOf(ctx, &cloudv1.Restore{}, client.InNamespace(e2eEnv.Namespace), client.MatchingLabels(e2eResourceLabels())); err != nil {
		errs = multierr.Append(errs, err)
	}
	if err := infraCli.DeleteAllOf(ctx, &cloudv1.Backup{}, client.InNamespace(e2eEnv.Namespace), client.MatchingLabels(e2eResourceLabels())); err != nil {
		errs = multierr.Append(errs, err)
	}
	if err := infraCli.DeleteAllOf(ctx, &cloudv1.NetworkPeer{}, client.InNamespace(e2eEnv.Namespace), client.MatchingLabels(e2eResourceLabels())); err != nil {
		errs = multierr.Append(errs, err)
	}
	if err := infraCli.DeleteAllOf(ctx, &cloudv1.Cluster{}, client.InNamespace(e2eEnv.Namespace), client.MatchingLabels(e2eResourceLabels())); err != nil {
		errs = multierr.Append(errs, err)
	}
	if err := infraCli.DeleteAllOf(ctx, &cloudv1.Network{}, client.InNamespace(e2eEnv.Namespace), client.MatchingLabels(e2eResourceLabels())); err != nil {
		errs = multierr.Append(errs, err)
	}
	Expect(errs).To(Succeed())

	// cleanup namespace
	Expect(infraCli.Delete(ctx, &corev1.Namespace{
		ObjectMeta: metav1.ObjectMeta{Name: e2eEnv.Namespace},
	})).To(Succeed())
})

func e2eResourceLabels() map[string]string {
	return map[string]string{
		"cloud.pingcap.com/usage": "e2e",
		"managed-by":              "e2e-suite",
	}
}
