package manager

import (
	"context"
	"fmt"

	"golang.org/x/sync/errgroup"
	"k8s.io/client-go/rest"
	"sigs.k8s.io/controller-runtime/pkg/cluster"
	"sigs.k8s.io/controller-runtime/pkg/manager"
)

type Manager interface {
	InfraCluster() cluster.Cluster
	manager.Manager
}

type Options struct {
	Manager      manager.Options
	InfraCluster cluster.Options
}

type ControllerManager struct {
	manager.Manager
	infraMgr manager.Manager
}

func New(cfg *rest.Config, infraConfig *rest.Config, opts Options) (Manager, error) {
	m, err := manager.New(cfg, opts.Manager)
	if err != nil {
		return nil, fmt.Errorf("can't create manager: %w", err)
	}

	infraMgr, err := manager.New(infraConfig, manager.Options{
		Scheme:                opts.InfraCluster.Scheme,
		MapperProvider:        opts.InfraCluster.MapperProvider,
		Logger:                opts.InfraCluster.Logger,
		SyncPeriod:            opts.InfraCluster.SyncPeriod,
		Namespace:             opts.InfraCluster.Namespace,
		NewCache:              opts.InfraCluster.NewCache,
		NewClient:             opts.InfraCluster.NewClient,
		ClientDisableCacheFor: opts.InfraCluster.ClientDisableCacheFor,
		DryRunClient:          opts.InfraCluster.DryRunClient,
		LeaderElection:        false,
		MetricsBindAddress:    "0",
	})
	if err != nil {
		return nil, fmt.Errorf("can't init infra manager: %w", err)
	}

	return &ControllerManager{
		Manager:  m,
		infraMgr: infraMgr,
	}, nil
}

func (m *ControllerManager) InfraCluster() cluster.Cluster {
	return m.infraMgr
}

func (m *ControllerManager) InfraMgr() manager.Manager {
	return m.infraMgr
}

func (m *ControllerManager) Start(ctx context.Context) error {
	ctx2, cancel2 := context.WithCancel(ctx)
	defer cancel2()

	group, errCtx := errgroup.WithContext(ctx2)
	group.Go(func() error {
		<-m.Manager.Elected()
		return m.infraMgr.Start(errCtx)
	})

	if err := m.Manager.Start(errCtx); err != nil { // if infraMgr start failed, this manager should also be canceled.
		return fmt.Errorf("can't start manager: %w", err)
	}

	cancel2() // if m.Manager.Start returned (with LeaderElection), cancel the infraMgr
	return group.Wait()
}
