package event

import (
	"context"
	"strconv"

	"code.cloudfoundry.org/eirini/events"
	"code.cloudfoundry.org/eirini/k8s/stset"
	"code.cloudfoundry.org/lager"
	"github.com/pkg/errors"
	corev1 "k8s.io/api/core/v1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/reconcile"
)

const (
	CrashLoopBackOff           = "CrashLoopBackOff"
	CreateContainerConfigError = "CreateContainerConfigError"
)

//counterfeiter:generate . CrashEventGenerator
//counterfeiter:generate . CrashEmitter
//counterfeiter:generate -o eventfakes/fake_controller_runtime_client.go sigs.k8s.io/controller-runtime/pkg/client.Client

type CrashEventGenerator interface {
	Generate(context.Context, *corev1.Pod, lager.Logger) (events.CrashEvent, bool)
}

type CrashEmitter interface {
	Emit(events.CrashEvent) error
}

type CrashReconciler struct {
	logger         lager.Logger
	client         client.Client
	eventGenerator CrashEventGenerator
	crashEmitter   CrashEmitter
}

func NewCrashReconciler(
	logger lager.Logger,
	client client.Client,
	eventGenerator CrashEventGenerator,
	crashEmitter CrashEmitter,
) *CrashReconciler {
	return &CrashReconciler{
		logger:         logger,
		client:         client,
		eventGenerator: eventGenerator,
		crashEmitter:   crashEmitter,
	}
}

func (c *CrashReconciler) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) {
	logger := c.logger.Session("reconcile-pod-crash",
		lager.Data{
			"name":      request.NamespacedName.Name,
			"namespace": request.NamespacedName.Namespace,
		})

	pod := &corev1.Pod{}

	err := c.client.Get(ctx, request.NamespacedName, pod)
	if err != nil {
		if apierrors.IsNotFound(err) {
			logger.Info("pod-not-found", lager.Data{"error": err})

			return reconcile.Result{}, nil
		}

		logger.Error("failed-to-get-pod", err)

		return reconcile.Result{}, errors.Wrap(err, "failed to get pod")
	}

	event, send := c.eventGenerator.Generate(ctx, pod, c.logger)
	if !send {
		logger.Debug("not-sending-event")

		return reconcile.Result{}, nil
	}

	if strconv.FormatInt(event.CrashTimestamp, 10) == pod.Annotations[stset.AnnotationLastReportedAppCrash] { // nolint:gomnd
		logger.Debug("event-already-sent")

		return reconcile.Result{}, nil
	}

	logger.Info("generated-event", lager.Data{"event": event})

	err = c.crashEmitter.Emit(event)
	if err != nil {
		logger.Error("failed-to-emit-event", err)

		return reconcile.Result{}, errors.Wrap(err, "failed to emit event")
	}

	logger.Info("emitted-event")

	newPod := pod.DeepCopy()
	if newPod.Annotations == nil {
		newPod.Annotations = map[string]string{}
	}

	newPod.Annotations[stset.AnnotationLastReportedAppCrash] = strconv.FormatInt(event.CrashTimestamp, 10) // nolint:gomnd

	if err = c.client.Patch(ctx, newPod, client.MergeFrom(pod)); err != nil {
		logger.Error("failed-to-set-last-crash-time-on-pod", err)
	}

	return reconcile.Result{}, nil
}
