package agent

import (
	"context"
	"fmt"
	"log/slog"

	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/module/flux"
	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/tool/errz"
	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/tool/logz"
	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/tool/prototool"
	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/tool/syncz"
	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/pkg/agentcfg"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/client-go/informers"
	"k8s.io/client-go/tools/cache"
)

type module struct {
	log    *slog.Logger
	runner reconciliationRunner
}

type reconciliationRunner interface {
	run(ctx context.Context, cfg *agentcfg.FluxCF) error
}

type fluxReconciliationRunner struct {
	informersFactory  func() (informers.GenericInformer, informers.GenericInformer, cache.Indexer)
	clientFactory     clientFactory
	controllerFactory controllerFactory
}

func (m *module) Run(ctx context.Context, cfg <-chan *agentcfg.AgentConfiguration) error {
	wh := syncz.NewProtoWorkerHolder[*agentcfg.FluxCF](
		func(config *agentcfg.FluxCF) syncz.Worker {
			if !*config.Enabled {
				return syncz.WorkerFunc(func(ctx context.Context) {
					// nop worker
				})
			}
			return syncz.WorkerFunc(func(ctx context.Context) {
				err := m.runner.run(ctx, config)
				if err != nil && !errz.ContextDone(err) {
					m.log.Error("Failed to run module", logz.Error(err))
				}
			})
		},
	)
	defer wh.StopAndWait()

	for config := range cfg {
		wh.ApplyConfig(ctx, config.Flux)
	}
	return nil
}

func (r *fluxReconciliationRunner) run(ctx context.Context, cfg *agentcfg.FluxCF) error {
	var wg wait.Group
	defer wg.Wait()

	runCtx, cancel := context.WithCancel(ctx)
	defer cancel()

	gitRepositoryInformer, receiverInformer, receiverIndexer := r.informersFactory()

	cl, err := r.clientFactory(runCtx, cfg.WebhookReceiverUrl, receiverIndexer)
	if err != nil {
		return fmt.Errorf("unable to create receiver: %w", err)
	}
	c, err := r.controllerFactory(runCtx, gitRepositoryInformer, receiverInformer, cl)
	if err != nil {
		return fmt.Errorf("unable to start controller: %w", err)
	}

	wg.StartWithContext(runCtx, gitRepositoryInformer.Informer().RunWithContext)
	wg.StartWithContext(runCtx, receiverInformer.Informer().RunWithContext)
	wg.StartWithContext(runCtx, cl.RunProjectReconciliation)

	c.Run(runCtx)
	return nil
}

func (m *module) DefaultAndValidateConfiguration(cfg *agentcfg.AgentConfiguration) error {
	prototool.NotNil(&cfg.Flux)
	prototool.DefaultVal(&cfg.Flux.WebhookReceiverUrl, defaultServiceAPIBaseURL)
	prototool.DefaultValPtr(&cfg.Flux.Enabled, true)
	return nil
}

func (m *module) Name() string {
	return flux.ModuleName
}
