package engine

import (
	"encoding/json"
	"errors"
	"testing"

	"github.com/stretchr/testify/mock"
	"github.com/stretchr/testify/require"
	"github.com/stretchr/testify/suite"
	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/module/autoflow/flow"
	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/pkg/event"
	"go.temporal.io/sdk/temporal"
	"go.temporal.io/sdk/testsuite"
)

type WorkflowsUnitTestSuite struct {
	suite.Suite
	testsuite.WorkflowTestSuite

	env *testsuite.TestWorkflowEnvironment

	ws *workflowState
}

func (s *WorkflowsUnitTestSuite) SetupTest() {
	// setup workflow state
	s.ws = &workflowState{
		activities: &activityState{
			source: flow.NewTestingSource([]flow.ModuleSource{
				{
					SourceName: "",
					File:       defaultEntrypoint,
					Data: []byte(`
def handle_test_event(w, ev):
    pass


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

	s.env = s.NewTestWorkflowEnvironment()
	s.env.RegisterWorkflow(s.ws.HandleEventWorkflow)
	s.env.RegisterWorkflow(s.ws.RunEventHandlerWorkflow)
	s.env.RegisterActivity(s.ws.activities)
}

func (s *WorkflowsUnitTestSuite) AfterTest(suiteName, testName string) {
	s.env.AssertExpectations(s.T())
}

func TestWorkflowsUnitTestSuite(t *testing.T) {
	suite.Run(t, new(WorkflowsUnitTestSuite))
}

func (s *WorkflowsUnitTestSuite) TestWorkflows_HandleEventWorkflow() {
	s.env.ExecuteWorkflow(s.ws.HandleEventWorkflow, &HandleEventWorkflowInput{
		Event: newJSONEvent(s.T(), "com.gitlab.events.test_event", "data"),
		FlowProject: &event.Project{
			Id:       1,
			FullPath: "any-group/any-project",
		},
		Entrypoint: defaultEntrypoint,
	})

	s.True(s.env.IsWorkflowCompleted())

	err := s.env.GetWorkflowError()
	s.NoError(err)
}

func (s *WorkflowsUnitTestSuite) TestWorkflows_HandleEventWorkflow_LoadAllEventEventHandlersActivity_Error() {
	s.env.OnActivity(s.ws.activities.LoadAllEventHandlersActivity, mock.Anything, mock.Anything).
		Return(&LoadAllEventHandlersActivityOutput{}, errors.New("test error"))

	s.env.ExecuteWorkflow(s.ws.HandleEventWorkflow, &HandleEventWorkflowInput{
		Event: newJSONEvent(s.T(), "com.gitlab.events.test_event", "data"),
		FlowProject: &event.Project{
			Id:       1,
			FullPath: "any-group/any-project",
		},
		Entrypoint: defaultEntrypoint,
	})

	s.True(s.env.IsWorkflowCompleted())

	err := s.env.GetWorkflowError()
	s.Error(err)
	var applicationErr *temporal.ApplicationError
	s.ErrorAs(err, &applicationErr)
	s.Equal("test error", applicationErr.Error())
}

func (s *WorkflowsUnitTestSuite) TestWorkflows_HandleEventWorkflow_LoadAllEventEventHandlersActivity_NoHandlers() {
	s.env.OnActivity(s.ws.activities.LoadAllEventHandlersActivity, mock.Anything, mock.Anything).
		Return(&LoadAllEventHandlersActivityOutput{Handlers: nil}, nil)

	s.env.ExecuteWorkflow(s.ws.HandleEventWorkflow, &HandleEventWorkflowInput{
		Event: newJSONEvent(s.T(), "com.gitlab.events.test_event", "data"),
		FlowProject: &event.Project{
			Id:       1,
			FullPath: "any-group/any-project",
		},
		Entrypoint: defaultEntrypoint,
	})

	s.True(s.env.IsWorkflowCompleted())

	err := s.env.GetWorkflowError()
	s.NoError(err)

	var output *HandleEventWorkflowOutput
	s.env.GetWorkflowResult(&output)
	s.Nil(output)
}

func newJSONEvent(t *testing.T, typ string, data any) *event.CloudEvent {
	jsonData, err := json.Marshal(data)
	require.NoError(t, err)

	return &event.CloudEvent{
		Id:          "any",
		Source:      "WorkflowsTest",
		SpecVersion: "v1",
		Type:        typ,
		Attributes: map[string]*event.CloudEvent_CloudEventAttributeValue{
			attrDataContentType: {
				Attr: &event.CloudEvent_CloudEventAttributeValue_CeString{
					CeString: dataContentTypeJSON,
				},
			},
		},
		Data: &event.CloudEvent_BinaryData{
			BinaryData: jsonData,
		},
	}
}
