package flow

import (
	"context"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"go.starlark.net/starlark"
	"go.uber.org/mock/gomock"
)

func TestRuntime_EvaluateHandlerIDs_ForSingleEventHandler_AnonymousHandler(t *testing.T) {
	// GIVEN
	ctrl := gomock.NewController(t)
	builtins := NewMockBuiltins(ctrl)

	runtime := newTestRuntime(t, builtins, []ModuleSource{
		{
			SourceName: "",
			File:       ".gitlab/autoflow/main.star",
			Data: []byte(`
def handle_test_event(w, ev):
    pass


on_event(
    type="com.gitlab.events.test_event",
    handler=handle_test_event,
)
`),
		},
	})

	// WHEN
	handlers, err := runtime.EvaluateEventHandlerIDs("com.gitlab.events.test_event", nil)
	require.NoError(t, err)

	// THEN
	assert.Len(t, handlers, 1)
	assert.Empty(t, handlers[0])
}

func TestRuntime_EvaluateHandlerIDs_ForSingleEventHandler_StaticNamedHandler(t *testing.T) {
	// GIVEN
	ctrl := gomock.NewController(t)
	builtins := NewMockBuiltins(ctrl)

	runtime := newTestRuntime(t, builtins, []ModuleSource{
		{
			SourceName: "",
			File:       ".gitlab/autoflow/main.star",
			Data: []byte(`
def handle_test_event(w, ev):
    pass


on_event(
    type="com.gitlab.events.test_event",
    handler=handle_test_event,
	workflow_id="some-static-name",
)
`),
		},
	})

	// WHEN
	handlers, err := runtime.EvaluateEventHandlerIDs("com.gitlab.events.test_event", nil)
	require.NoError(t, err)

	// THEN
	assert.Len(t, handlers, 1)
	assert.Equal(t, "some-static-name", handlers[0])
}

func TestRuntime_EvaluateHandlerIDs_ForSingleEventHandler_DynamicallyNamedHandler(t *testing.T) {
	// GIVEN
	ctrl := gomock.NewController(t)
	builtins := NewMockBuiltins(ctrl)

	runtime := newTestRuntime(t, builtins, []ModuleSource{
		{
			SourceName: "",
			File:       ".gitlab/autoflow/main.star",
			Data: []byte(`
def handle_test_event(w, ev):
    pass

def construct_handler_name(ev):
	return "some-dynamic-name"

on_event(
    type="com.gitlab.events.test_event",
    handler=handle_test_event,
	workflow_id=construct_handler_name,
)
`),
		},
	})

	// WHEN
	handlers, err := runtime.EvaluateEventHandlerIDs("com.gitlab.events.test_event", nil)
	require.NoError(t, err)

	// THEN
	assert.Len(t, handlers, 1)
	assert.Equal(t, "some-dynamic-name", handlers[0])
}

func TestRuntime_EvaluateHandlerIDs_ForSingleEventHandler_DynamicallyNamedHandler_BasedOnEvent(t *testing.T) {
	// GIVEN
	ctrl := gomock.NewController(t)
	builtins := NewMockBuiltins(ctrl)

	runtime := newTestRuntime(t, builtins, []ModuleSource{
		{
			SourceName: "",
			File:       ".gitlab/autoflow/main.star",
			Data: []byte(`
def handle_test_event(w, ev):
    pass

def construct_handler_name(ev):
	return ev["event-name"]

on_event(
    type="com.gitlab.events.test_event",
    handler=handle_test_event,
	workflow_id=construct_handler_name,
)
`),
		},
	})

	event := starlark.NewDict(1)
	event.SetKey(starlark.String("event-name"), starlark.String("some-dynamic-name"))

	// WHEN
	handlers, err := runtime.EvaluateEventHandlerIDs("com.gitlab.events.test_event", event)
	require.NoError(t, err)

	// THEN
	assert.Len(t, handlers, 1)
	assert.Equal(t, "some-dynamic-name", handlers[0])
}

func TestRuntime_EvaluateHandlerIDs_ForMultipleEventHandlers(t *testing.T) {
	// GIVEN
	ctrl := gomock.NewController(t)
	builtins := NewMockBuiltins(ctrl)

	runtime := newTestRuntime(t, builtins, []ModuleSource{
		{
			SourceName: "",
			File:       ".gitlab/autoflow/main.star",
			Data: []byte(`
def handle_test_event_1(w, ev):
    pass


def handle_test_event_2(w, ev):
    pass


on_event(
    type="com.gitlab.events.test_event",
    handler=handle_test_event_1,
)

on_event(
    type="com.gitlab.events.test_event",
    handler=handle_test_event_2,
)
`),
		},
	})

	// WHEN
	handlers, err := runtime.EvaluateEventHandlerIDs("com.gitlab.events.test_event", nil)
	require.NoError(t, err)

	// THEN
	assert.Len(t, handlers, 2)
}

func TestRuntime_EvaluateHandlerIDs_DynamicHandlers(t *testing.T) {
	// GIVEN
	ctrl := gomock.NewController(t)
	builtins := NewMockBuiltins(ctrl)

	runtime := newTestRuntime(t, builtins, []ModuleSource{
		{
			SourceName: "",
			File:       ".gitlab/autoflow/main.star",
			Data: []byte(`
def handle_test_event(w, ev):
	pass


def init_handlers():
	on_event(
		type="com.gitlab.events.test_event",
		handler=handle_test_event,
	)

	on_event(
		type="com.gitlab.events.test_event",
		handler=handle_test_event,
	)

init_handlers()
`),
		},
	})

	// WHEN
	handlers, err := runtime.EvaluateEventHandlerIDs("com.gitlab.events.test_event", nil)
	require.NoError(t, err)

	// THEN
	assert.Len(t, handlers, 2)
}

func TestRuntime_ExecuteBasicFlowScript(t *testing.T) {
	// GIVEN
	ctrl := gomock.NewController(t)
	builtins := NewMockBuiltins(ctrl)

	runtime := newTestRuntime(t, builtins, []ModuleSource{
		{
			SourceName: "",
			File:       ".gitlab/autoflow/main.star",
			Data: []byte(`
def handle_test_event(w, ev):
    print("handle_test_event")


on_event(
    type="com.gitlab.events.test_event",
    handler=handle_test_event,
)
`),
		},
	})

	builtins.EXPECT().Print("handle_test_event")

	// WHEN
	err := runtime.Execute("com.gitlab.events.test_event", 0, nil, nil, nil)

	// THEN
	assert.NoError(t, err)
}

func newTestRuntime(t *testing.T, builtins Builtins, modules []ModuleSource) *Runtime {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	runtime, err := NewRuntimeFromSource(ctx, RuntimeOptions{
		Entrypoint: ".gitlab/autoflow/main.star",
		Builtins:   builtins,
		Source: &preloadedSource{
			modules: modules,
		},
	})
	require.NoError(t, err)

	return runtime
}
