package agent

import (
	"context"
	"errors"
	"testing"
	"time"

	"github.com/stretchr/testify/require"
	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/module/modagent"
	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/tool/testing/matcher"
	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/tool/testing/testlogger"
	"go.uber.org/mock/gomock"
)

type mockCM struct {
	enabled      bool
	debugLogging bool
}

var (
	_ modagent.Module[mockCM] = &Module[mockCM]{}
)

func TestModule_Run_NoStartWhenNotEnabled(t *testing.T) {
	// GIVEN
	mockProfilerRunner, m, _ := setup(t)

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	cfg := make(chan *mockCM, 1)
	profilerCfg := &mockCM{
		enabled: false,
	}
	cfg <- profilerCfg
	close(cfg)

	// THEN
	mockProfilerRunner.EXPECT().Start(gomock.Any()).Times(0)

	// WHEN
	err := m.Run(ctx, cfg)
	require.NoError(t, err)
}

func TestModule_Run_StartWhenEnabled(t *testing.T) {
	// GIVEN
	mockProfilerRunner, m, cfgAccessor := setup(t)

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	cfg := make(chan *mockCM, 1)

	profilerCfg := &mockCM{
		enabled: true,
	}
	cfg <- profilerCfg
	close(cfg)

	// THEN
	mockProfilerRunner.EXPECT().Start(matcher.ProtoEq(t, cfgAccessor(profilerCfg).ProfilerCfg)).Times(1)

	// WHEN
	err := m.Run(ctx, cfg)
	require.NoError(t, err)
}

func TestModule_Run_StartWhenEnabledAndNotStartedAgain(t *testing.T) {
	// GIVEN
	mockProfilerRunner, m, cfgAccessor := setup(t)

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	cfg := make(chan *mockCM, 2)

	profilerCfg1 := &mockCM{
		enabled: true,
	}
	profilerCfg2 := &mockCM{
		enabled:      true,
		debugLogging: true,
	}
	cfg <- profilerCfg1
	cfg <- profilerCfg2
	close(cfg)

	// THEN
	mockProfilerRunner.EXPECT().Start(matcher.ProtoEq(t, cfgAccessor(profilerCfg1).ProfilerCfg)).Times(1)
	mockProfilerRunner.EXPECT().Start(matcher.ProtoEq(t, cfgAccessor(profilerCfg2).ProfilerCfg)).Times(0)

	// WHEN
	err := m.Run(ctx, cfg)
	require.NoError(t, err)
}

func TestModule_Run_StartWhenEnabledAfterPreviousStartFailure(t *testing.T) {
	// GIVEN
	mockProfilerRunner, m, cfgAccessor := setup(t)
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	cfg := make(chan *mockCM, 2)

	profilerCfgFails := &mockCM{
		enabled: true,
	}
	profilerCfgSucceeds := &mockCM{
		enabled:      true,
		debugLogging: true,
	}
	cfg <- profilerCfgFails
	cfg <- profilerCfgSucceeds
	close(cfg)

	// THEN
	mockProfilerRunner.EXPECT().Start(matcher.ProtoEq(t, cfgAccessor(profilerCfgFails).ProfilerCfg)).Return(errors.New("dummy error"))
	mockProfilerRunner.EXPECT().Start(matcher.ProtoEq(t, cfgAccessor(profilerCfgSucceeds).ProfilerCfg)).Times(1)

	// WHEN
	err := m.Run(ctx, cfg)
	require.NoError(t, err)
}

func setup(t *testing.T) (*MockProfilerRunner, Module[*mockCM], func(cfg *mockCM) *ModuleCfg) {
	ctrl := gomock.NewController(t)
	mockProfilerRunner := NewMockProfilerRunner(ctrl)
	cfgAccessor := func(cfg *mockCM) *ModuleCfg {
		return &ModuleCfg{
			ProfilerCfg: ProfilerCfg{
				Enabled:      cfg.enabled,
				DebugLogging: cfg.debugLogging,
			},
		}
	}
	m := Module[*mockCM]{
		Log:         testlogger.New(t),
		Runner:      mockProfilerRunner,
		CfgAccessor: cfgAccessor,
	}
	return mockProfilerRunner, m, cfgAccessor
}
