package controller

import (
	"context"
	"fmt"
	"sort"
	"strconv"
	"time"

	"github.com/sirupsen/logrus"
	"github.com/tidbcloud/api-gateway/ui/internal/util"
	networkingv1 "k8s.io/api/networking/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/labels"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/client-go/informers"
	"k8s.io/client-go/kubernetes"
	corev1lister "k8s.io/client-go/listers/core/v1"
	"k8s.io/client-go/rest"
)

var controllerObj *Controller

type Controller struct {
	clientset kubernetes.Interface

	serviceLister   corev1lister.ServiceLister
	namespaceLister corev1lister.NamespaceLister
	// services map[string]map[string]struct{}
}

func Init(cfg *rest.Config) {
	kubeClient, err := kubernetes.NewForConfig(cfg)
	if err != nil {
		logrus.Fatalf("Error building kubernetes clientset: %s", err.Error())
	}
	kubeInformerFactory := informers.NewSharedInformerFactory(kubeClient, time.Minute*10)
	serviceInformer := kubeInformerFactory.Core().V1().Services()
	namespaceInformer := kubeInformerFactory.Core().V1().Namespaces()

	controller := &Controller{
		clientset:       kubeClient,
		serviceLister:   serviceInformer.Lister(),
		namespaceLister: namespaceInformer.Lister(),
	}

	kubeInformerFactory.Start(wait.NeverStop)
	kubeInformerFactory.WaitForCacheSync(wait.NeverStop)

	controllerObj = controller
}

func ListNamespaceNames() ([]string, error) {
	namespaces, err := controllerObj.namespaceLister.List(labels.NewSelector())
	if err != nil {
		return nil, err
	}
	result := make([]string, 0, 10)
	for _, namespace := range namespaces {
		result = append(result, namespace.Name)
	}
	return result, nil
}

func ListNodePortServiceNames(namespace string) ([]string, error) {
	services, err := controllerObj.serviceLister.Services(namespace).List(labels.NewSelector())
	if err != nil {
		return nil, err
	}
	result := make([]string, 0, 10)
	for _, service := range services {
		// if service.Spec.Type == "NodePort" {
		// 	result = append(result, service.Name)
		// }
		// TODO: delete
		result = append(result, service.Name)
	}
	return result, nil
}

func ListServicePorts(namespace, serviceName string) ([]string, error) {
	services, err := controllerObj.serviceLister.Services(namespace).List(labels.NewSelector())
	if err != nil {
		return nil, err
	}
	result := make([]string, 0, 10)
	for _, service := range services {
		if service.Name != serviceName {
			continue
		}
		for _, servicePort := range service.Spec.Ports {
			result = append(result, strconv.Itoa(int(servicePort.Port)))
		}
	}
	return result, nil
}

func BuildIngresses(routeModels util.RouteModels) []*networkingv1.Ingress {
	sort.Sort(routeModels)
	preNamespace := "none"
	var ingress *networkingv1.Ingress
	ruleValue := &networkingv1.HTTPIngressRuleValue{
		Paths: make([]networkingv1.HTTPIngressPath, 0),
	}
	result := make([]*networkingv1.Ingress, 0, 10)
	for _, routeModel := range routeModels {
		if routeModel.Namespace != preNamespace {
			if ingress != nil {
				ingress.Spec.Rules = append(ingress.Spec.Rules, networkingv1.IngressRule{
					Host: routeModel.Host,
					IngressRuleValue: networkingv1.IngressRuleValue{
						HTTP: ruleValue,
					},
				})
				result = append(result, ingress)
				ruleValue = &networkingv1.HTTPIngressRuleValue{
					Paths: make([]networkingv1.HTTPIngressPath, 0),
				}
			}
			ingress = &networkingv1.Ingress{
				TypeMeta: metav1.TypeMeta{
					Kind:       "Ingress",
					APIVersion: "networking.k8s.io/v1",
				},
				ObjectMeta: metav1.ObjectMeta{
					Name:      fmt.Sprintf("apigateway-generated-%s", routeModel.Host),
					Namespace: routeModel.Namespace,
				},
				Spec: networkingv1.IngressSpec{
					Rules: make([]networkingv1.IngressRule, 0),
				},
			}
			preNamespace = routeModel.Namespace
		}
		ruleValue.Paths = append(ruleValue.Paths, buildPath(routeModel))
	}
	// TODO: tricky
	if ingress != nil {
		ingress.Spec.Rules = append(ingress.Spec.Rules, networkingv1.IngressRule{
			Host: routeModels[0].Host,
			IngressRuleValue: networkingv1.IngressRuleValue{
				HTTP: ruleValue,
			},
		})
		result = append(result, ingress)
	}

	return result
}

func DeployIngresses(ingresses []*networkingv1.Ingress) error {
	for _, ingress := range ingresses {
		if _, err := controllerObj.clientset.NetworkingV1().Ingresses(ingress.Namespace).Create(context.Background(), ingress, metav1.CreateOptions{}); err != nil {
			_, err := controllerObj.clientset.NetworkingV1().Ingresses(ingress.Namespace).Update(context.Background(), ingress, metav1.UpdateOptions{})
			return err
		}
	}
	return nil
}

func buildPath(routeModel util.RouteModel) networkingv1.HTTPIngressPath {
	backend := networkingv1.IngressBackend{
		Service: &networkingv1.IngressServiceBackend{
			Name: routeModel.ServiceName,
			Port: networkingv1.ServiceBackendPort{
				Number: int32(routeModel.ServicePort),
			},
		},
	}

	str := networkingv1.PathTypeImplementationSpecific
	path := networkingv1.HTTPIngressPath{
		Path:     routeModel.Path,
		PathType: &str,
		Backend:  backend,
	}

	return path
}
