package engine

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

	"github.com/redis/rueidis"
	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/api"
	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/gitaly"
	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/gitlab"
	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/tool/cache"
	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/tool/metric"
	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/tool/redistool"
	otelmetric "go.opentelemetry.io/otel/metric"
	"go.opentelemetry.io/otel/trace"
	"go.starlark.net/starlark"
	tclient "go.temporal.io/sdk/client"
	tinterceptor "go.temporal.io/sdk/interceptor"
	tworker "go.temporal.io/sdk/worker"
)

const (
	defaultEntrypoint             = ".gitlab/autoflow/main.star"
	varGitLabURL                  = "gitlab_url"
	maxFileSize                   = 100 * 1024
	workflowRateLimiterKey        = "autoflow_wf_rl"
	workflowRateLimiterMetricName = "workflows_per_project_id_event_type"
)

type Engine struct {
	log       *slog.Logger
	tc        tclient.Client
	taskQueue string
	tw        tworker.Worker
	ws        *workflowState
	rl        *workflowRateLimiter
}

type Options struct {
	Log           *slog.Logger
	TC            tclient.Client
	GitalyPool    gitaly.PoolInterface
	GitLabClient  gitlab.ClientInterface
	RepoInfoCache *cache.CacheWithErr[RepoInfoCacheKey, *api.ProjectInfo]
	HTTPRT        http.RoundTripper
	TaskQueue     string
	// Must not have / at the end.
	GitLabURL             string
	RL                    *workflowRateLimiter
	RedisClient           rueidis.Client
	RedisKeyPrefix        string
	HandleProcessingError func(msg string, err error)
	Tracer                trace.Tracer
	Meter                 otelmetric.Meter
	// workflowsPerProjectIDEventTypePerMinute the amount of allowed workflows per project and event type per minute
	WorkflowsPerProjectIDEventTypePerMinute uint32
	WorkerInterceptors                      []tinterceptor.WorkerInterceptor
}

func NewEngine(opts Options) (*Engine, error) { //nolint: gocritic
	var al AllowLimiter
	al = redistool.NewTokenLimiter(
		opts.RedisClient,
		opts.RedisKeyPrefix+":"+workflowRateLimiterKey,
		uint64(opts.WorkflowsPerProjectIDEventTypePerMinute),
		func(ctx context.Context) redistool.RPCAPI {
			return &workflowRateLimiterAPI{
				requestCtx:            ctx,
				log:                   opts.Log,
				handleProcessingError: opts.HandleProcessingError,
			}
		},
	)
	al, err := metric.NewAllowLimiterInstrumentation(
		workflowRateLimiterMetricName,
		float64(opts.WorkflowsPerProjectIDEventTypePerMinute),
		"{workflow/project_id_and_event_type/m}",
		opts.Tracer,
		opts.Meter,
		al,
	)
	if err != nil {
		return nil, err
	}

	tw := tworker.New(opts.TC, opts.TaskQueue, tworker.Options{
		//MaxConcurrentActivityExecutionSize:      0,
		//WorkerActivitiesPerSecond:               0,
		//MaxConcurrentLocalActivityExecutionSize: 0,
		//WorkerLocalActivitiesPerSecond:          0,
		//TaskQueueActivitiesPerSecond:            0,
		//MaxConcurrentActivityTaskPollers:        0,
		//MaxConcurrentWorkflowTaskExecutionSize:  0,
		//MaxConcurrentWorkflowTaskPollers:        0,
		//EnableLoggingInReplay:                   false,
		//StickyScheduleToStartTimeout:            0,
		//BackgroundActivityContext:               nil,
		//WorkflowPanicPolicy:                     0,
		//WorkerStopTimeout:                       0,
		//EnableSessionWorker:                     false,
		//MaxConcurrentSessionExecutionSize:       0,
		//DisableWorkflowWorker:                   false,
		//LocalActivityWorkerOnly:                 false,
		//Identity:                                "",
		//DeadlockDetectionTimeout:                0,
		//MaxHeartbeatThrottleInterval:            0,
		//DefaultHeartbeatThrottleInterval:        0,
		Interceptors: opts.WorkerInterceptors,
		//OnFatalError:                            nil,
		//DisableEagerActivities:                  false,
		//MaxConcurrentEagerActivityExecutionSize: 0,
		//DisableRegistrationAliasing:             false,
		//BuildID:                                 "",
		//UseBuildIDForVersioning:                 false,
	})
	e := &Engine{
		log:       opts.Log,
		tc:        opts.TC,
		taskQueue: opts.TaskQueue,
		ws: &workflowState{
			activities: &activityState{
				source: &gitalySource{
					gitalyPool:    opts.GitalyPool,
					gitLabClient:  opts.GitLabClient,
					repoInfoCache: opts.RepoInfoCache,
					maxFileSize:   maxFileSize,
				},
				httpRT: opts.HTTPRT,
			},
			vars: starlark.StringDict{
				varGitLabURL: starlark.String(opts.GitLabURL),
			},
		},
		tw: tw,
		rl: &workflowRateLimiter{al: al},
	}
	tw.RegisterWorkflow(e.ws.HandleEventWorkflow)
	tw.RegisterWorkflow(e.ws.RunEventHandlerWorkflow)
	tw.RegisterActivity(e.ws.activities)
	return e, nil
}

func (e *Engine) Run(ctx context.Context) error {
	if err := e.tw.Start(); err != nil {
		return err
	}
	<-ctx.Done()
	e.tw.Stop()
	return nil
}

func (e *Engine) HandleEvent(ctx context.Context, req *HandleEventRequest) error {
	// 0. Check if event is allowed to be handled within the rate limit
	if !e.rl.Allow(ctx, req.FlowProject.Id, req.Event.Type) {
		return errRateLimited
	}

	// 1. Dispatch event to workflow engine
	wopts := tclient.StartWorkflowOptions{
		TaskQueue: e.taskQueue,
	}
	_, err := e.tc.ExecuteWorkflow(ctx, wopts, e.ws.HandleEventWorkflow, &HandleEventWorkflowInput{
		Event:       req.Event,
		FlowProject: req.FlowProject,
		Variables:   req.Variables,
		Entrypoint:  defaultEntrypoint,
	})
	if err != nil {
		return fmt.Errorf("failed to start workflow: %w", err)
	}

	return nil
}
