package main

import (
	"context"
	"fmt"
	v1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/labels"
	"k8s.io/client-go/kubernetes"
	"log"
	"slices"
	"strconv"
)

type Cluster struct {
	namespaces map[string]v1.Namespace
	clientset  *kubernetes.Clientset
	// map of namespace to list of all pods
	pods map[string][]v1.Pod
}

func NewCluster(clientset *kubernetes.Clientset) (*Cluster, error) {
	cluster := &Cluster{
		clientset:  clientset,
		namespaces: make(map[string]v1.Namespace),
		pods:       make(map[string][]v1.Pod),
	}
	nslist, err := cluster.clientset.CoreV1().Namespaces().List(context.Background(), metav1.ListOptions{})
	if err != nil {
		return nil, err
	}
	for _, ns := range nslist.Items {
		cluster.namespaces[ns.Name] = ns
		podList, err := cluster.clientset.CoreV1().Pods(ns.Name).List(context.Background(), metav1.ListOptions{})
		if err != nil {
			return nil, err
		}
		cluster.pods[ns.Name] = podList.Items
	}
	return cluster, nil
}

func (c *Cluster) Pods(application *Application) []v1.Pod {
	selector, err := metav1.LabelSelectorAsSelector(application.Selector())
	if err != nil {
		log.Fatalf("Error creating selector: %v", err)
	}
	pods := c.pods[application.Namespace.Name]
	pods = slices.DeleteFunc(slices.Clone(pods), func(pod v1.Pod) bool {
		return !selector.Matches(labels.Set(pod.Labels))
	})
	return pods
}

func (c *Cluster) ServiceAccounts(application *Application) []string {
	var res []string
	for _, pod := range c.Pods(application) {
		if !slices.Contains(res, pod.Spec.ServiceAccountName) {
			res = append(res, pod.Spec.ServiceAccountName)
		}
	}
	return res
}

func (c *Cluster) OwnerReferences(application *Application) []string {
	var ownerReferences []string
	for _, pod := range c.Pods(application) {
		//log.Printf("  %s %v", pod.Name, pod.OwnerReferences)
		for _, ownerReference := range pod.OwnerReferences {
			owner := ownerReference.Kind + "/" + ownerReference.Name
			if !slices.Contains(ownerReferences, owner) {
				ownerReferences = append(ownerReferences, owner)
			}
		}
	}
	return ownerReferences
}

func (c *Cluster) IsLinkerdEnabled(application *Application) bool {
	pods := c.Pods(application)

	ndisabled := 0
	for _, pod := range pods {
		if pod.Annotations["linkerd.io/inject"] == "enabled" {
			return true
		}
		if pod.Annotations["linkerd.io/inject"] == "disabled" {
			ndisabled++
		}
	}
	if ndisabled == len(pods) {
		return false
	}
	ns := c.namespaces[application.Namespace.Name]
	return ns.Annotations["linkerd.io/inject"] == "enabled"
}

func (c *Cluster) NamespaceLIst() []v1.Namespace {
	return MapValues(c.namespaces)
}

func (c *Cluster) Namespace(name string) v1.Namespace {
	return c.namespaces[name]
}

func (c *Cluster) PodList(namespace string) []v1.Pod {
	return c.pods[namespace]
}

func (c *Cluster) PortNumbers(application *Application) []Port {
	if !c.IsLinkerdEnabled(application) {
		return nil
	}
	tcpPorts := make(map[int]Port)
	udpPorts := make(map[int]Port)
	for _, pod := range c.Pods(application) {
		for _, container := range pod.Spec.Containers {
			for _, port := range container.Ports {
				switch port.Protocol {
				case "TCP":
					tcpPorts[int(port.ContainerPort)] = Port{
						Port:     strconv.Itoa(int(port.ContainerPort)),
						Protocol: string(port.Protocol),
					}
				case "UDP":
					udpPorts[int(port.ContainerPort)] = Port{
						Port:     strconv.Itoa(int(port.ContainerPort)),
						Protocol: string(port.Protocol),
					}

				default:
					panic(fmt.Sprintf("Unknown port type for pod %s/%s: %s",
						pod.Namespace, pod.Name, port.Protocol))
				}
			}
		}
	}
	res := MapValues(tcpPorts)
	res = append(res, MapValues(udpPorts)...)
	return res
}