// Copyright (c) 2021 Gitpod GmbH. All rights reserved.
// Licensed under the GNU Affero General Public License (AGPL).
// See License.AGPL.txt in the project root for license information.

package manager

import (
	"context"

	"github.com/go-logr/logr"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/apimachinery/pkg/watch"
	ctrl "sigs.k8s.io/controller-runtime"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/event"
	"sigs.k8s.io/controller-runtime/pkg/predicate"
)

// PodReconciler reconciles a Pod object
type PodReconciler struct {
	client.Client
	Log    logr.Logger
	Scheme *runtime.Scheme

	Monitor *Monitor
	Pods    map[types.NamespacedName]corev1.Pod
}

// Reconcile performs a reconciliation of a pod
// For more details, check Reconcile and its Result here:
// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.7.0/pkg/reconcile
func (r *PodReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
	var pod corev1.Pod
	err := r.Client.Get(context.Background(), req.NamespacedName, &pod)
	if errors.IsNotFound(err) {
		// pod is gone - that's ok
		if pod, ok := r.Pods[req.NamespacedName]; ok {
			delete(r.Pods, req.NamespacedName)
			queue := pod.Annotations[workspaceIDAnnotation]
			if queue == "" {
				return ctrl.Result{}, nil
			}
			r.Monitor.eventpool.Add(queue, watch.Event{
				Type:   watch.Deleted,
				Object: &pod,
			})
		}
		return ctrl.Result{}, nil
	}
	r.Pods[req.NamespacedName] = pod

	queue := pod.Annotations[workspaceIDAnnotation]
	if queue == "" {
		return ctrl.Result{}, nil
	}

	r.Monitor.eventpool.Add(queue, watch.Event{
		Type:   watch.Modified,
		Object: &pod,
	})

	return ctrl.Result{}, nil
}

// SetupWithManager sets up the controller with the Manager.
func (r *PodReconciler) SetupWithManager(mgr ctrl.Manager) error {
	return ctrl.NewControllerManagedBy(mgr).
		WithEventFilter(
			predicate.Funcs{
				UpdateFunc: func(e event.UpdateEvent) bool {
					_, ok := e.ObjectNew.GetAnnotations()[workspaceIDAnnotation]
					return ok
				},
				CreateFunc: func(e event.CreateEvent) bool {
					_, ok := e.Object.GetAnnotations()[workspaceIDAnnotation]
					return ok
				},
				DeleteFunc: func(e event.DeleteEvent) bool {
					_, ok := e.Object.GetAnnotations()[workspaceIDAnnotation]
					return ok
				},
			}).
		For(&corev1.Pod{}).
		Complete(r)
}
