package actions

import (
	"context"
	"errors"
	"fmt"

	"github.com/dop251/goja_nodejs/require"
	"github.com/sirupsen/logrus"

	"github.com/zitadel/zitadel/internal/query"
	"github.com/zitadel/zitadel/internal/zerrors"
)

type Config struct {
	HTTP HTTPConfig
}

var ErrHalt = errors.New("interrupt")

type jsAction func(fields, fields) error

const (
	actionStartedMessage   = "action run started"
	actionSucceededMessage = "action run succeeded"
)

func actionFailedMessage(err error) string {
	return fmt.Sprintf("action run failed: %s", err.Error())
}

func Run(ctx context.Context, ctxParam contextFields, apiParam apiFields, script, name string, opts ...Option) (err error) {
	config := newRunConfig(ctx, append(opts, withLogger(ctx))...)
	if config.functionTimeout == 0 {
		return zerrors.ThrowInternal(nil, "ACTIO-uCpCx", "Errrors.Internal")
	}

	remaining := logstoreService.Limit(ctx, config.instanceID)
	config.cutTimeouts(remaining)

	config.logger.Log(actionStartedMessage)
	if remaining != nil && *remaining == 0 {
		return zerrors.ThrowResourceExhausted(nil, "ACTIO-f19Ii", "Errors.Quota.Execution.Exhausted")
	}

	defer func() {
		if err != nil {
			config.logger.log(actionFailedMessage(err), logrus.ErrorLevel, true)
		} else {
			config.logger.log(actionSucceededMessage, logrus.InfoLevel, true)
		}
		if config.allowedToFail {
			err = nil
		}
	}()

	if err := executeScript(config, ctxParam, apiParam, script); err != nil {
		return err
	}

	var fn jsAction
	jsFn := config.vm.Get(name)
	if jsFn == nil {
		return errors.New("function not found")
	}
	if err := config.vm.ExportTo(jsFn, &fn); err != nil {
		return err
	}

	t := config.StartFunction()
	defer func() {
		t.Stop()
	}()

	return executeFn(config, fn)
}

func executeScript(config *runConfig, ctxParam contextFields, apiParam apiFields, script string) (err error) {
	t := config.StartScript()
	defer func() {
		t.Stop()
	}()

	if ctxParam != nil {
		ctxParam(config.ctxParam)
	}
	if apiParam != nil {
		apiParam(config.apiParam)
	}

	registry := new(require.Registry)
	registry.Enable(config.vm)

	for name, loader := range config.modules {
		registry.RegisterNativeModule(name, loader)
	}
	// overload error if function panics
	defer func() {
		r := recover()
		if r != nil {
			err = r.(error)
			return
		}
	}()

	_, err = config.vm.RunString(script)
	return err
}

func executeFn(config *runConfig, fn jsAction) (err error) {
	defer func() {
		r := recover()
		if r == nil {
			return
		}
		var ok bool
		if err, ok = r.(error); ok {
			return
		}

		e, ok := r.(string)
		if ok {
			err = errors.New(e)
			return
		}
		err = fmt.Errorf("unknown error occurred: %v", r)
	}()

	if err = fn(config.ctxParam.fields, config.apiParam.fields); err != nil {
		return err
	}
	return nil
}

func ActionToOptions(a *query.Action) []Option {
	opts := make([]Option, 0, 1)
	if a.AllowedToFail {
		opts = append(opts, WithAllowedToFail())
	}
	return opts
}