package main

import (
	"context"
	"flag"
	"fmt"
	"os"
	"strconv"

	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/resource"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
	clientgoscheme "k8s.io/client-go/kubernetes/scheme"
	"k8s.io/client-go/tools/clientcmd"
	"sigs.k8s.io/controller-runtime/pkg/client"

	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"github.com/tidbcloud/shared-provider/pkg/utils/labels"
)

var (
	infraAPIServerKubeconfig string
	nsPrefix                 string
	nsSuffixStart            int
	nsSuffixEnd              int
	region                   string
	clusterName              string
	instanceType             string
	providerType             string

	clear bool

	scheme = runtime.NewScheme()
)

func init() {
	flag.StringVar(&infraAPIServerKubeconfig, "infra-api-server-config", "", "KUBECONFIG for the infra API server")
	flag.StringVar(&nsPrefix, "ns-prefix", "", "the prefix of namespaces")
	flag.IntVar(&nsSuffixStart, "ns-suffix-start", -1, "the start index of the suffix for namespaces")
	flag.IntVar(&nsSuffixEnd, "ns-suffix-end", -1, "the end index of the suffix for namespaces")
	flag.StringVar(&region, "region", "us-east-1", "the region of the cluster")
	flag.StringVar(&clusterName, "cluster-name", "dev-us-east-1-s01", "the cluster name")
	flag.StringVar(&instanceType, "instance-type", "aws_sharedtier_6", "the instance type")
	flag.StringVar(&providerType, "provider-type", "aws-shared-tier", "the provider type")
	flag.BoolVar(&clear, "clear", false, "whether to clear existing resources")
}

func main() {
	flag.Parse()

	if infraAPIServerKubeconfig == "" || nsPrefix == "" ||
		nsSuffixStart < 0 || nsSuffixEnd < 0 || nsSuffixEnd <= nsSuffixStart {
		flag.Usage()
		os.Exit(-1)
	}

	utilruntime.Must(clientgoscheme.AddToScheme(scheme))
	utilruntime.Must(cloudv1.AddToScheme(scheme))

	if err := run(); err != nil {
		fmt.Println(err)
		os.Exit(-1)
	}
}

func run() error {
	cli, err := buildClient(infraAPIServerKubeconfig)
	if err != nil {
		return fmt.Errorf("failed to build client for infra API server: %v", err)
	}

	ctx := context.Background()

	if clear {
		return runClear(ctx, cli)
	}
	return runCreate(ctx, cli)
}

func runCreate(ctx context.Context, cli client.Client) error {
	for i := nsSuffixStart; i < nsSuffixEnd; i++ {
		nsName := genNamespaceName(i)
		fmt.Println("creating namespace", nsName)
		if err := cli.Create(ctx, buildNamespace(nsName)); err != nil {
			return err
		}

		net := buildNetwork(nsName, region, clusterName)
		fmt.Println("creating network", net.Name)
		if err := cli.Create(ctx, net); err != nil {
			return err
		}

		cluster := buildCluster(nsName, net.Name, genClusterName(i))
		fmt.Println("creating cluster", cluster.Name)
		if err := cli.Create(ctx, cluster); err != nil {
			return err
		}
	}

	return nil
}

func runClear(ctx context.Context, cli client.Client) error {
	for i := nsSuffixStart; i < nsSuffixEnd; i++ {
		nsName := genNamespaceName(i)
		net := buildNetwork(nsName, region, clusterName)
		cluster := buildCluster(nsName, net.Name, genClusterName(i))

		fmt.Println("deleting cluster", cluster.Name)
		if err := cli.Delete(ctx, cluster); err != nil {
			return err
		}

		fmt.Println("deleting network", net.Name)
		if err := cli.Delete(ctx, net); err != nil {
			return err
		}

		fmt.Println("deleting namespace", nsName)
		if err := cli.Delete(ctx, buildNamespace(nsName)); err != nil {
			return err
		}
	}
	return nil
}

func genNamespaceName(nsSuffix int) string {
	return fmt.Sprintf("%s-%d", nsPrefix, nsSuffix)
}

func genClusterName(nsSuffix int) string {
	const base = 10
	return strconv.FormatInt(int64(nsSuffix), base)
}

func buildNamespace(ns string) *corev1.Namespace {
	return &corev1.Namespace{
		ObjectMeta: metav1.ObjectMeta{Name: ns},
	}
}

func buildNetwork(ns, region, eksName string) *cloudv1.Network {
	return &cloudv1.Network{
		ObjectMeta: metav1.ObjectMeta{
			Name:      region, // use region as the network name now.
			Namespace: ns,
			Labels: map[string]string{
				cloudv1.ProviderTypeLabel:        providerType,
				labels.LabelKeyKubernetesCluster: eksName,
			},
		},
		Spec: cloudv1.NetworkSpec{
			Provider: providerType,
			Region:   region,
			CIDR:     "10.0.0.0/8",
		},
	}
}

func buildCluster(ns, net, name string) *cloudv1.Cluster {
	var (
		enabled = true
	)
	return &cloudv1.Cluster{
		ObjectMeta: metav1.ObjectMeta{
			Name:      name,
			Namespace: ns,
		},
		Spec: cloudv1.ClusterSpec{
			NetworkName: net,
			SqlEndpoints: cloudv1.SqlEndpoints{
				Public: cloudv1.Service{
					Enabled: &enabled,
					LoadBalancerSourceRanges: []string{ // only one CIDR
						"0.0.0.0/0", // public access, easy to verify
					},
				},
			},
			Cluster: cloudv1.ClusterTopology{
				PD: cloudv1.PD{
					Replicas:     1,
					InstanceType: instanceType,
					StorageSize:  resource.MustParse("2Gi"),
				},
				TiKV: cloudv1.TiKV{
					Replicas:     1,
					InstanceType: instanceType,
					StorageSize:  resource.MustParse("10Gi"),
				},
				TiFlash: cloudv1.TiFlash{
					Replicas:     1,
					InstanceType: instanceType,
					StorageSize:  resource.MustParse("10Gi"),
				},
				TiDB: cloudv1.TiDB{
					Replicas:     1,
					InstanceType: instanceType,
				},
			},
		},
	}
}

func buildClient(kubePath string) (client.Client, error) {
	cfg, err := clientcmd.BuildConfigFromFlags("", kubePath)
	if err != nil {
		return nil, err
	}

	return client.New(cfg, client.Options{
		Scheme: scheme,
	})
}
