package controller

import (
	"context"

	datautils "github.com/kyverno/kyverno/pkg/utils/data"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/apimachinery/pkg/util/sets"
	"k8s.io/apimachinery/pkg/watch"
)

type CreateClient[T metav1.Object] interface {
	Create(context.Context, T, metav1.CreateOptions) (T, error)
}

type GetClient[T metav1.Object] interface {
	Get(context.Context, string, metav1.GetOptions) (T, error)
}

type UpdateClient[T metav1.Object] interface {
	Update(context.Context, T, metav1.UpdateOptions) (T, error)
}

type DeleteClient interface {
	Delete(context.Context, string, metav1.DeleteOptions) error
}

type PatchClient[T metav1.Object] interface {
	Patch(context.Context, string, types.PatchType, []byte, metav1.PatchOptions, ...string) (T, error)
}

type ObjectClient[T metav1.Object] interface {
	CreateClient[T]
	GetClient[T]
	UpdateClient[T]
	DeleteClient
	PatchClient[T]
}

type DeleteCollectionClient interface {
	DeleteCollection(context.Context, metav1.DeleteOptions, metav1.ListOptions) error
}

type WatchClient interface {
	Watch(context.Context, metav1.ListOptions) (watch.Interface, error)
}

type ListClient[T any] interface {
	List(context.Context, metav1.ListOptions) (T, error)
}

type StatusClient[T metav1.Object] interface {
	UpdateStatus(context.Context, T, metav1.UpdateOptions) (T, error)
}

type ObjectListClient[T metav1.Object, L any] interface {
	ObjectClient[T]
	ListClient[L]
}

type ObjectStatusClient[T metav1.Object] interface {
	ObjectClient[T]
	StatusClient[T]
}

type ObjectListStatusClient[T metav1.Object, L any] interface {
	ObjectClient[T]
	ListClient[L]
	StatusClient[T]
}

type Object[T any] interface {
	*T
	metav1.Object
	DeepCopy() *T
}

type Getter[T any] interface {
	Get(string) (T, error)
}

type Setter[T any] interface {
	Create(context.Context, T, metav1.CreateOptions) (T, error)
	Update(context.Context, T, metav1.UpdateOptions) (T, error)
}

type Deleter interface {
	Delete(context.Context, string, metav1.DeleteOptions) error
}

func GetOrNew[T any, R Object[T], G Getter[R]](name string, getter G) (R, error) {
	obj, err := getter.Get(name)
	if err != nil {
		if apierrors.IsNotFound(err) {
			obj = new(T)
			obj.SetName(name)
		} else {
			return nil, err
		}
	}
	return obj, nil
}

func CreateOrUpdate[T any, R Object[T], G Getter[R], S Setter[R]](ctx context.Context, name string, getter G, setter S, build func(R) error) (R, error) {
	if obj, err := GetOrNew[T, R](name, getter); err != nil {
		return nil, err
	} else {
		mutated := obj.DeepCopy()
		if err := build(mutated); err != nil {
			return nil, err
		} else {
			if obj.GetResourceVersion() == "" {
				return setter.Create(ctx, mutated, metav1.CreateOptions{})
			} else {
				if datautils.DeepEqual(obj, mutated) {
					return mutated, nil
				} else {
					return setter.Update(ctx, mutated, metav1.UpdateOptions{})
				}
			}
		}
	}
}

type DeepCopy[T any] interface {
	DeepCopy() T
}

func Update[T interface {
	metav1.Object
	DeepCopy[T]
}, S UpdateClient[T]](ctx context.Context, obj T, setter S, build func(T) error,
) (T, error) {
	mutated := obj.DeepCopy()
	if err := build(mutated); err != nil {
		var d T
		return d, err
	} else {
		if datautils.DeepEqual(obj, mutated) {
			return mutated, nil
		} else {
			return setter.Update(ctx, mutated, metav1.UpdateOptions{})
		}
	}
}

func UpdateStatus[T interface {
	metav1.Object
	DeepCopy[T]
}, S StatusClient[T]](ctx context.Context, obj T, setter S, build func(T) error,
) (T, error) {
	mutated := obj.DeepCopy()
	if err := build(mutated); err != nil {
		var d T
		return d, err
	} else {
		if datautils.DeepEqual(obj, mutated) {
			return mutated, nil
		} else {
			return setter.UpdateStatus(ctx, mutated, metav1.UpdateOptions{})
		}
	}
}

func Cleanup[T any, R Object[T]](ctx context.Context, actual []R, expected []R, deleter Deleter) error {
	keep := sets.New[string]()
	for _, obj := range expected {
		keep.Insert(obj.GetName())
	}
	for _, obj := range actual {
		if !keep.Has(obj.GetName()) {
			if err := deleter.Delete(ctx, obj.GetName(), metav1.DeleteOptions{}); err != nil && !apierrors.IsNotFound(err) {
				return err
			}
		}
	}
	return nil
}
