package webhookconfig

import (
	"context"
	"fmt"
	"time"

	"github.com/go-logr/logr"
	"github.com/kyverno/kyverno/pkg/config"
	"github.com/kyverno/kyverno/pkg/event"
	"github.com/pkg/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	coordinationv1 "k8s.io/client-go/kubernetes/typed/coordination/v1"
)

var leaseName string = "kyverno"
var leaseNamespace string = config.KyvernoNamespace

const (
	annWebhookStatus   string = "kyverno.io/webhookActive"
	annLastRequestTime string = "kyverno.io/last-request-time"
)

//statusControl controls the webhook status
type statusControl struct {
	eventGen    event.Interface
	log         logr.Logger
	leaseClient coordinationv1.LeaseInterface
}

//success ...
func (vc statusControl) success() error {
	return vc.setStatus("true")
}

//failure ...
func (vc statusControl) failure() error {
	return vc.setStatus("false")
}

// NewStatusControl creates a new webhook status control
func newStatusControl(leaseClient coordinationv1.LeaseInterface, eventGen event.Interface, log logr.Logger) *statusControl {
	return &statusControl{
		eventGen:    eventGen,
		log:         log,
		leaseClient: leaseClient,
	}
}

func (vc statusControl) setStatus(status string) error {
	logger := vc.log.WithValues("name", leaseName, "namespace", leaseNamespace)
	var ann map[string]string
	var err error

	lease, err := vc.leaseClient.Get(context.TODO(), "kyverno", metav1.GetOptions{})
	if err != nil {
		vc.log.WithName("UpdateLastRequestTimestmap").Error(err, "Lease 'kyverno' not found. Starting clean-up...")
		return err
	}

	ann = lease.GetAnnotations()
	if ann == nil {
		ann = map[string]string{}
		ann[annWebhookStatus] = status
	}

	leaseStatus, ok := ann[annWebhookStatus]
	if ok {
		if leaseStatus == status {
			logger.V(4).Info(fmt.Sprintf("annotation %s already set to '%s'", annWebhookStatus, status))
			return nil
		}
	}

	ann[annWebhookStatus] = status
	lease.SetAnnotations(ann)

	_, err = vc.leaseClient.Update(context.TODO(), lease, metav1.UpdateOptions{})
	if err != nil {
		return errors.Wrapf(err, "key %s, val %s", annWebhookStatus, status)
	}

	logger.Info("updated lease annotation", "key", annWebhookStatus, "val", status)

	// create event on kyverno deployment
	createStatusUpdateEvent(status, vc.eventGen)
	return nil
}

func createStatusUpdateEvent(status string, eventGen event.Interface) {
	e := event.Info{}
	e.Kind = "Lease"
	e.Namespace = leaseNamespace
	e.Name = leaseName
	e.Reason = "Update"
	e.Message = fmt.Sprintf("admission control webhook active status changed to %s", status)
	eventGen.Add(e)
}

func (vc statusControl) UpdateLastRequestTimestmap(new time.Time) error {

	lease, err := vc.leaseClient.Get(context.TODO(), leaseName, metav1.GetOptions{})
	if err != nil {
		vc.log.WithName("UpdateLastRequestTimestmap").Error(err, "Lease 'kyverno' not found. Starting clean-up...")
		return err
	}

	//add label to lease
	label := lease.GetLabels()
	if len(label) == 0 {
		label = make(map[string]string)
		label["app.kubernetes.io/name"] = "kyverno"
	}
	lease.SetLabels(label)

	annotation := lease.GetAnnotations()
	if annotation == nil {
		annotation = make(map[string]string)
	}

	t, err := new.MarshalText()
	if err != nil {
		return errors.Wrap(err, "failed to marshal timestamp")
	}

	annotation[annLastRequestTime] = string(t)
	lease.SetAnnotations(annotation)

	//update annotations in lease
	_, err = vc.leaseClient.Update(context.TODO(), lease, metav1.UpdateOptions{})
	if err != nil {
		return errors.Wrapf(err, "failed to update annotation %s for deployment %s in namespace %s", annLastRequestTime, lease.GetName(), lease.GetNamespace())
	}

	return nil
}
