package ingressroute

import (
	"context"
	"errors"
	"fmt"
	"log"
	"time"

	"git.ar21.de/yolokube/go-traefik-certmanager/pkg/certmanager"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/apimachinery/pkg/watch"
	"k8s.io/client-go/tools/cache"
	"k8s.io/client-go/util/workqueue"
)

const (
	group    = "traefik.io"
	version  = "v1alpha1"
	resource = "ingressroutes"
)

type ingressRouteClient struct {
	client *Client
}

func (i *ingressRouteClient) Watch(stopCh chan struct{}) {
	gvr := schema.GroupVersionResource{
		Group:    group,
		Version:  version,
		Resource: resource,
	}

	listWatch := &cache.ListWatch{
		ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
			return i.client.crdClient.Resource(gvr).Namespace(corev1.NamespaceAll).List(context.Background(), options)
		},
		WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
			return i.client.crdClient.Resource(gvr).Namespace(corev1.NamespaceAll).Watch(context.Background(), options)
		},
	}

	queue := workqueue.NewTypedRateLimitingQueue(workqueue.DefaultTypedControllerRateLimiter[any]())

	informer := cache.NewSharedInformer(listWatch, &unstructured.Unstructured{}, 0)
	_, err := informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
		AddFunc: func(obj interface{}) {
			key, err := cache.MetaNamespaceKeyFunc(obj)
			if err == nil {
				queue.Add(event{key: key, eventType: watch.Added, object: &obj})
			}
		},
		UpdateFunc: func(_, newObj interface{}) {
			key, err := cache.MetaNamespaceKeyFunc(newObj)
			if err == nil {
				queue.Add(event{key: key, eventType: watch.Modified, object: &newObj})
			}
		},
		DeleteFunc: func(obj interface{}) {
			if !i.client.certCleanup {
				return
			}

			key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj)
			if err == nil {
				queue.Add(event{key: key, eventType: watch.Deleted, object: &obj})
			}
		},
	})
	if err != nil {
		log.Printf("Cannot add event handler: %v", err)
	}

	go informer.Run(stopCh)

	wait.Until(func() {
		for i.processNextItem(queue) {
		}
	}, time.Second, stopCh)
}

func (i *ingressRouteClient) processNextItem(queue workqueue.TypedRateLimitingInterface[any]) bool {
	item, quit := queue.Get()
	if quit {
		return false
	}
	defer queue.Done(item)

	log.Printf("Processing key %v", item)

	event, ok := item.(event)
	if !ok {
		log.Printf("Invalid data struct: %v", item)
		return true
	}

	namespace, name, err := cache.SplitMetaNamespaceKey(event.key)
	if err != nil {
		log.Printf("Failed to split namespace and name: %v", err)
		return true
	}

	convObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(event.object)
	if err != nil {
		log.Printf("Failed to convert unstructured object for key %s: %v", event.key, err)
		return true
	}

	rawRoutes, found, err := unstructured.NestedSlice(convObj, "spec", "routes")
	if err != nil || !found {
		log.Printf("No routes found for key %s", event.key)
		return true
	}

	secretName, found, err := unstructured.NestedString(convObj, "spec", "tls", "secretName")
	if err != nil {
		log.Printf("Failed to scrape secret name for %s: %v", event.key, err)
		return true
	}
	if !found {
		log.Printf("No secret name found, using ingressroute name %s", name)
		secretName = name
	}

	routes, err := routeInterfaceToMapSlice(rawRoutes)
	if err != nil {
		log.Printf("Failed to convert routes for key %s: %v", event.key, err)
		return true
	}

	//nolint:exhaustive // ignore missing switch cases
	switch event.eventType {
	case watch.Added, watch.Modified:
		createErr := i.client.certmanager.Certificates.Create(context.Background(), namespace, secretName, routes)
		if createErr != nil {
			if errors.Is(createErr, certmanager.ErrCertificateAlreadyExist) {
				log.Printf("Certificate %s for %s already exists", secretName, event.key)
			} else {
				log.Printf("Failed to create certificate %s: %v", event.key, createErr)
			}
		} else {
			log.Printf("Certificate %s for %s created", secretName, event.key)
		}
	case watch.Deleted:
		if deleteErr := i.client.certmanager.Certificates.Delete(context.Background(), namespace, secretName); deleteErr != nil {
			log.Printf("Failed to delete certificate %s: %v", event.key, deleteErr)
		} else {
			log.Printf("Certificate %s for %s deleted", secretName, event.key)
		}
	}

	return true
}

type event struct {
	key       string
	eventType watch.EventType
	object    *interface{}
}

func routeInterfaceToMapSlice(input []interface{}) ([]map[string]interface{}, error) {
	var result []map[string]interface{}
	for _, item := range input {
		match, ok := item.(map[string]interface{})
		if !ok {
			return nil, fmt.Errorf("item is not of type map[string]interface{}: %v", item)
		}
		result = append(result, match)
	}
	return result, nil
}