package engine

import (
	"bytes"
	"context"
	"io"
	"net/http"
	"time"

	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/module/autoflow/flow"
	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/tool/errz"
	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/tool/logz"
	"go.temporal.io/sdk/activity"
)

// activityState holds shared state that is used by all Temporal activities.
type activityState struct {
	source flow.Source
	httpRT http.RoundTripper
}

type RepoInfoCacheKey struct {
	// TODO identity must be part of the cache key
	ProjectID string
}

func (a *activityState) LoadAllEventHandlersActivity(ctx context.Context, input *LoadAllEventHandlersActivityInput) (*LoadAllEventHandlersActivityOutput, error) {
	log := activity.GetLogger(ctx)
	runtime, err := flow.NewRuntimeFromSource(ctx, flow.RuntimeOptions{
		Source:      a.source,
		ProjectPath: input.FlowProject.FullPath,
		Entrypoint:  input.Entrypoint,
		Builtins: &flowBuiltins{
			printFunc: func(msg string) {
				log.Info(msg, logz.IsScriptPrint())
			},
			nowFunc: time.Now,
		},
	})
	if err != nil {
		// NOTE: we log our own log message because of
		// https://github.com/temporalio/sdk-go/issues/1808#issuecomment-2662368441
		log.Error("Failed to load runtime for workflow script", logz.Error(err))
		return nil, err
	}

	eventDict, err := EventToDict(input.Event)
	if err != nil {
		// NOTE: we log our own log message because of
		// https://github.com/temporalio/sdk-go/issues/1808#issuecomment-2662368441
		log.Error("Failed to convert event object to starlark dict", logz.Error(err))
		return nil, err
	}
	// FIXME: consider eventDict to be created if needed - that is, inject a callback func
	eh, err := runtime.EvaluateEventHandlerIDs(input.Event.Type, eventDict)
	if err != nil {
		// NOTE: we log our own log message because of
		// https://github.com/temporalio/sdk-go/issues/1808#issuecomment-2662368441
		log.Error("Failed to evaluate event handlers from workflow script", logz.Error(err))
		return nil, err
	}

	handlers := make([]*HandlerInfo, 0, len(eh))
	for _, h := range eh {
		handlers = append(handlers, &HandlerInfo{WorkflowId: h})
	}

	return &LoadAllEventHandlersActivityOutput{
		Handlers: handlers,
		Modules:  fromFlowModuleSources(runtime.LoadedModules()),
	}, nil
}

func (a *activityState) HTTPDoActivity(ctx context.Context, input *HttpDoActivityInput) (output *HttpDoActivityOutput, retErr error) {
	log := activity.GetLogger(ctx)

	header := valuesMapToHTTPHeader(input.Header)
	statusCode, status, header, data, err := HTTPDo(ctx, a.httpRT, input.Url, input.Method, input.Body, header)
	if err != nil {
		// NOTE: we log our own log message because of
		// https://github.com/temporalio/sdk-go/issues/1808#issuecomment-2662368441
		log.Error("Failed to perform HTTP request", logz.Error(err))
		return nil, err
	}
	return &HttpDoActivityOutput{
		StatusCode: uint32(statusCode), //nolint:gosec
		Status:     status,
		Header:     httpHeaderToValuesMap(header),
		Body:       data,
	}, nil
}

func HTTPDo(ctx context.Context, rt http.RoundTripper, url, method string, body []byte, header http.Header) (statusCode int, status string, respHeader http.Header, respBody []byte, retErr error) {
	var bodyReader io.Reader
	if len(body) > 0 {
		bodyReader = bytes.NewReader(body)
	}
	// TODO protect against server-side request forgery.
	req, err := http.NewRequestWithContext(ctx, method, url, bodyReader)
	if err != nil {
		return 0, "", nil, nil, err
	}
	req.Header = header
	resp, err := rt.RoundTrip(req) //nolint:bodyclose
	if err != nil {
		return 0, "", nil, nil, err
	}
	defer errz.SafeClose(resp.Body, &retErr)
	data, err := io.ReadAll(resp.Body)
	if err != nil {
		return 0, "", nil, nil, err
	}
	return resp.StatusCode, resp.Status, resp.Header, data, nil
}
