package agentk

import (
	"context"
	"time"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/wait"
	coordinationv1 "k8s.io/client-go/kubernetes/typed/coordination/v1"
	"k8s.io/client-go/tools/leaderelection"
	"k8s.io/client-go/tools/leaderelection/resourcelock"
)

type leaseLeaderElector struct {
	// Namespace is the namespace of the Lease lock object.
	namespace string
	// name returns name of the Lease lock object or an error if context signals done.
	name func(context.Context) (string, error)

	// Identity is the unique string identifying a lease holder across
	// all participants in an election.
	identity string

	coordinationClient coordinationv1.CoordinationV1Interface
	eventRecorder      resourcelock.EventRecorder
}

// Run runs the leader election
// Run triggers the onStartedLeading function when this instance acquired the lease.
// Run triggers the onStoppedLeading function when:
// 1. it had the lease acquired and lost it again
// 2. ctx is canceled, no matter if it held the lease or not
// The (2) bullet point above is due to the outrageous behavior of the Client-go leaderelection implementation
// that ALWAYS calls the OnStoppedLeading callback NO MATTER if it held the lease or not when its ctx is canceled.
func (l *leaseLeaderElector) Run(ctx context.Context, onStartedLeading, onStoppedLeading func()) {
	name, err := l.name(ctx)
	if err != nil {
		// NOTE: we need to call onStoppedLeading explicitly here to guarantee a consistent interface for the caller
		// of this Run method - that is expecting that a onStoppedLeading callback is triggered whenever the
		// context is canceled and therefore the leader election is asked to be stopped.
		onStoppedLeading()
		return // ctx done
	}
	elector, err := leaderelection.NewLeaderElector(leaderelection.LeaderElectionConfig{
		Lock: &resourcelock.LeaseLock{
			LeaseMeta: metav1.ObjectMeta{
				Namespace: l.namespace,
				Name:      name,
			},
			Client: l.coordinationClient,
			LockConfig: resourcelock.ResourceLockConfig{
				Identity:      l.identity,
				EventRecorder: l.eventRecorder,
			},
		},
		LeaseDuration: 15 * time.Second,
		RenewDeadline: 10 * time.Second,
		RetryPeriod:   2 * time.Second,
		Callbacks: leaderelection.LeaderCallbacks{
			OnStartedLeading: func(ctx context.Context) { onStartedLeading() },
			OnStoppedLeading: onStoppedLeading,
		},
		ReleaseOnCancel: true,
		Name:            "module-runner",
	})
	if err != nil {
		// This can only happen if config is incorrect. It is hard-coded here, so should never happen.
		panic(err)
	}
	wait.UntilWithContext(ctx, elector.Run, time.Millisecond)
}
