package task

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

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/mock"

	"github.com/tidbcloud/pov-service/internal/common/constant"
	"github.com/tidbcloud/pov-service/internal/common/options"
	"github.com/tidbcloud/pov-service/internal/common/params"
	"github.com/tidbcloud/pov-service/internal/model"
	"github.com/tidbcloud/pov-service/internal/repo"
	"github.com/tidbcloud/pov-service/internal/service"
)

// MockWorkloadGateway is a mock implementation of gateway.Workload
type MockWorkloadGateway struct {
	mock.Mock
}

func (m *MockWorkloadGateway) ListWorkloads(params.ListWorkloads) ([]model.Workload, error) {
	args := m.Called()
	return args.Get(0).([]model.Workload), args.Error(1)
}

func (m *MockWorkloadGateway) TriggerCreateCluster(ctx context.Context, params model.WorkloadParams) (model.WorkloadTaskBase, error) {
	args := m.Called(ctx, params)
	return args.Get(0).(model.WorkloadTaskBase), args.Error(1)
}

func (m *MockWorkloadGateway) PollCreateClusterTask(
	ctx context.Context,
	tidbCluster *model.TiDBCluster, // create cluster may be a multi-step process, so we'll need the intermediate result
	task model.WorkloadTaskBase) (model.WorkloadTaskBase, model.CreateClusterResult, error) {
	args := m.Called(ctx, tidbCluster, task)
	return args.Get(0).(model.WorkloadTaskBase), args.Get(1).(model.CreateClusterResult), args.Error(2)
}

func (m *MockWorkloadGateway) TriggerPrepareData(
	ctx context.Context,
	tidbCluster model.TiDBCluster,
	param params.RunWorkload) (model.WorkloadTaskBase, error) {
	args := m.Called(ctx, tidbCluster, param)
	return args.Get(0).(model.WorkloadTaskBase), args.Error(1)
}

func (m *MockWorkloadGateway) PollPrepareDataTask(ctx context.Context,
	tidbCluster model.TiDBCluster,
	task model.WorkloadTaskBase) (model.WorkloadTaskBase, model.PrepareDataResult, error) {
	args := m.Called(ctx, tidbCluster, task)
	return args.Get(0).(model.WorkloadTaskBase), args.Get(1).(model.PrepareDataResult), args.Error(2)
}

func (m *MockWorkloadGateway) TriggerRunTraffic(ctx context.Context,
	tidbCluster model.TiDBCluster,
	prepareResult model.PrepareDataResult, params model.WorkloadParams) (model.WorkloadTaskBase, error) {
	args := m.Called(ctx, prepareResult, params)
	return args.Get(0).(model.WorkloadTaskBase), args.Error(1)
}

func (m *MockWorkloadGateway) PollRunTrafficTask(ctx context.Context, task model.WorkloadTaskBase) (model.WorkloadTaskBase, error) {
	args := m.Called(ctx, task)
	return args.Get(0).(model.WorkloadTaskBase), args.Error(1)
}

func (m *MockWorkloadGateway) CancelTask(ctx context.Context, task model.WorkloadTaskBase) error {
	args := m.Called(ctx, task)
	return args.Error(0)
}

func (m *MockWorkloadGateway) ReleaseCluster(
	ctx context.Context,
	cluster model.TiDBCluster,
) error {
	args := m.Called(ctx, cluster)
	return args.Error(0)
}

func (m *MockWorkloadGateway) ScaleCluster(ctx context.Context, cluster model.TiDBCluster, replicas int) error {
	args := m.Called(ctx, cluster, replicas)
	return args.Error(0)
}

func (m *MockWorkloadGateway) UpdateClusterRU(ctx context.Context, cluster model.TiDBCluster, minRU, maxRU int64) error {
	args := m.Called(ctx, cluster, minRU, maxRU)
	return args.Error(0)
}

func (m *MockWorkloadGateway) TriggerRecreateTraffic(
	ctx context.Context,
	oldTrafficID int64,
	newparams model.WorkloadParams) (model.WorkloadTaskBase, error) {
	args := m.Called(ctx, oldTrafficID, newparams)
	return args.Get(0).(model.WorkloadTaskBase), args.Error(1)
}

func (m *MockWorkloadGateway) SampleDBs(
	ctx context.Context,
	cluster model.TiDBCluster,
	options model.SampleDBOptions) (model.DBSampleResult, error) {
	args := m.Called(ctx, cluster, options)
	return args.Get(0).(model.DBSampleResult), args.Error(1)
}

func (m *MockWorkloadGateway) SampleData(
	ctx context.Context,
	cluster model.TiDBCluster,
	options model.SampleDataOptions) (model.TableQueryResult, error) {
	args := m.Called(ctx, cluster, options)
	return args.Get(0).(model.TableQueryResult), args.Error(1)
}

func (m *MockWorkloadGateway) OverProvision(
	ctx context.Context,
	cluster model.TiDBCluster,
) error {
	return nil
}

func (m *MockWorkloadGateway) DeleteOverProvision(
	ctx context.Context,
	cluster model.TiDBCluster,
) error {
	return nil
}

func (m *MockWorkloadGateway) ExecuteSQL(
	ctx context.Context,
	cluster model.TiDBCluster,
	sql string,
) error {
	return nil
}

func (m *MockWorkloadGateway) InjectFailure(
	ctx context.Context,
	cluster model.TiDBCluster,
	duration string,
) error {
	return nil
}

func (m *MockWorkloadGateway) RollbackFailure(
	ctx context.Context,
	cluster model.TiDBCluster,
) error {
	return nil
}

// MockTx is a mock implementation of repo.Tx
type MockTx struct {
	mock.Mock
}

func (m *MockTx) Commit() error {
	args := m.Called()
	return args.Error(0)
}

func (m *MockTx) Rollback() error {
	args := m.Called()
	return args.Error(0)
}

// MockWorkloadTestRepo is a mock implementation of repo.WorkloadTest
type MockWorkloadTestRepo struct {
	mock.Mock
}

func (m *MockWorkloadTestRepo) Create(ctx context.Context, workload model.WorkloadTest) (model.WorkloadTest, error) {
	args := m.Called(ctx, workload)
	return args.Get(0).(model.WorkloadTest), args.Error(1)
}

func (m *MockWorkloadTestRepo) Update(ctx context.Context, workload model.WorkloadTest, opts ...options.TableStatement) (model.WorkloadTest, error) {
	args := m.Called(ctx, workload)
	return args.Get(0).(model.WorkloadTest), args.Error(1)
}

func (m *MockWorkloadTestRepo) Get(ctx context.Context, ID string) (model.WorkloadTest, error) {
	args := m.Called(ctx, ID)
	return args.Get(0).(model.WorkloadTest), args.Error(1)
}

func (m *MockWorkloadTestRepo) ListActive(ctx context.Context) ([]model.WorkloadTest, error) {
	args := m.Called(ctx)
	return args.Get(0).([]model.WorkloadTest), args.Error(1)
}

func (m *MockWorkloadTestRepo) List(ctx context.Context, opts options.ListOption[options.WorkloadTestRefOption]) ([]model.WorkloadTest, error) {
	args := m.Called(ctx)
	return args.Get(0).([]model.WorkloadTest), args.Error(1)
}

func (m *MockWorkloadTestRepo) Count(ctx context.Context) (map[int64]int, error) {
	args := m.Called(ctx)
	return args.Get(0).(map[int64]int), args.Error(1)
}

func (m *MockWorkloadTestRepo) BeginTx(ctx context.Context) (repo.Tx, error) {
	args := m.Called(ctx)
	return args.Get(0).(repo.Tx), args.Error(1)
}

func (m *MockWorkloadTestRepo) GetAndLockForUpdateInTx(ctx context.Context, tx repo.Tx, ID string, opts ...options.TableStatement,
) (model.WorkloadTest, error) {
	args := m.Called(ctx, tx, ID, opts)
	return args.Get(0).(model.WorkloadTest), args.Error(1)
}

func (m *MockWorkloadTestRepo) UpdateInTx(ctx context.Context, tx repo.Tx, workload model.WorkloadTest) error {
	args := m.Called(ctx, tx, workload)
	return args.Error(0)
}

type MockHook struct {
	mock.Mock
}

func (m *MockHook) Match(
	from model.WorkloadTestPhase,
	to model.WorkloadTestPhase,
	test model.WorkloadTest) bool {
	args := m.Called(from, to, test)
	return args.Get(0).(bool)
}

func (m *MockHook) Do(
	ctx context.Context,
	test model.WorkloadTest,
) error {
	args := m.Called(ctx, test)
	return args.Error(0)
}

func (m *MockHook) GetName() string {
	return "MockHook"
}

// Test ProcessorManager functionality
func TestProcessorManager_ProcessAll(t *testing.T) {
	// Arrange
	ctx := context.Background()
	mockRepo := new(MockWorkloadTestRepo)
	mockGateway := new(MockWorkloadGateway)
	mockSvc := new(service.MockWorkloadService)
	mockTx := new(MockTx)

	pm := NewProcessorManager(
		ProcessorManagerParams{
			Db:        mockRepo,
			AuditRepo: nil,
			Gateway:   mockGateway,
			Svc:       mockSvc,
			EmailCli:  nil,
		})

	// Create test data
	test1 := model.WorkloadTest{
		ID:   "test-1",
		Name: "Test 1",
		ExecBase: model.ExecBase{
			Status: model.ExecStatusPending,
		},
		Phase:  model.WorkloadTestPhasePending,
		IsAuto: true,
	}

	tests := []model.WorkloadTest{test1}

	// Mock expectations
	mockRepo.On("ListActive", ctx).Return(tests, nil)

	// Mock transaction operations
	mockRepo.On("BeginTx", ctx).Return(mockTx, nil)
	mockRepo.On("GetAndLockForUpdateInTx", ctx, mockTx, "test-1").Return(test1, nil)
	mockRepo.On("UpdateInTx", ctx, mockTx, mock.AnythingOfType("model.WorkloadTest")).Return(nil)
	mockTx.On("Commit").Return(nil)
	mockTx.On("Rollback").Return(nil) // For defer cleanup

	// Act
	err := pm.ProcessAll(ctx)

	// Assert
	assert.NoError(t, err)

	// Verify all expectations were met
	mockRepo.AssertExpectations(t)
	mockTx.AssertExpectations(t)
}

func TestProcessorManager_ProcessAll_DatabaseError(t *testing.T) {
	// Arrange
	ctx := context.Background()
	mockRepo := new(MockWorkloadTestRepo)
	mockGateway := new(MockWorkloadGateway)
	mockSvc := new(service.MockWorkloadService)

	pm := NewProcessorManager(
		ProcessorManagerParams{
			Db:        mockRepo,
			AuditRepo: nil,
			Gateway:   mockGateway,
			Svc:       mockSvc,
			EmailCli:  nil,
		})

	// Mock database error
	expectedError := errors.New("database connection failed")
	mockRepo.On("ListActive", ctx).Return([]model.WorkloadTest{}, expectedError)

	// Act
	err := pm.ProcessAll(ctx)

	// Assert
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "failed to list active tests")

	// Verify all expectations were met
	mockRepo.AssertExpectations(t)
}

func TestProcessorManager_EmptyActiveList(t *testing.T) {
	// Arrange
	ctx := context.Background()
	mockRepo := new(MockWorkloadTestRepo)
	mockGateway := new(MockWorkloadGateway)
	mockSvc := new(service.MockWorkloadService)

	pm := NewProcessorManager(
		ProcessorManagerParams{
			Db:        mockRepo,
			AuditRepo: nil,
			Gateway:   mockGateway,
			Svc:       mockSvc,
			EmailCli:  nil,
		})

	// Mock empty list
	mockRepo.On("ListActive", ctx).Return([]model.WorkloadTest{}, nil)

	// Act
	err := pm.ProcessAll(ctx)

	// Assert
	assert.NoError(t, err)

	// Verify all expectations were met
	mockRepo.AssertExpectations(t)
}

// Test individual processors
func TestTestStartProcessor_CanProcess(t *testing.T) {
	processor := &TestStartProcessor{}

	// Test case 1: Can process pending auto test
	test1 := model.WorkloadTest{
		ExecBase: model.ExecBase{Status: model.ExecStatusPending},
		Phase:    model.WorkloadTestPhasePending,
		IsAuto:   true,
	}
	assert.True(t, processor.CanProcess(test1, model.WorkloadTaskPhasePending))

	// Test case 2: Cannot process non-auto test
	test2 := model.WorkloadTest{
		ExecBase: model.ExecBase{Status: model.ExecStatusPending},
		Phase:    model.WorkloadTestPhasePending,
		IsAuto:   false,
	}
	assert.False(t, processor.CanProcess(test2, model.WorkloadTaskPhasePending))

	// Test case 3: Cannot process running test
	test3 := model.WorkloadTest{
		ExecBase: model.ExecBase{Status: model.ExecStatusRunning},
		Phase:    model.WorkloadTestPhasePending,
		IsAuto:   true,
	}
	assert.False(t, processor.CanProcess(test3, model.WorkloadTaskPhasePending))
}

func TestCreateClusterTriggerProcessor_CanProcess(t *testing.T) {
	processor := &CreateClusterTriggerProcessor{}

	// Test case 1: Can process create cluster phase with pending task
	test1 := model.WorkloadTest{
		ExecBase: model.ExecBase{Status: model.ExecStatusRunning},
		Phase:    model.WorkloadTestPhaseCreateCluster,
	}
	assert.True(t, processor.CanProcess(test1, model.WorkloadTaskPhasePending))

	// Test case 2: Cannot process different phase
	test2 := model.WorkloadTest{
		ExecBase: model.ExecBase{Status: model.ExecStatusRunning},
		Phase:    model.WorkloadTestPhasePrepareData,
	}
	assert.False(t, processor.CanProcess(test2, model.WorkloadTaskPhasePending))

	// Test case 3: Cannot process non-pending task
	test3 := model.WorkloadTest{
		ExecBase: model.ExecBase{Status: model.ExecStatusRunning},
		Phase:    model.WorkloadTestPhaseCreateCluster,
	}
	assert.False(t, processor.CanProcess(test3, model.WorkloadTaskPhaseRunning))
}

func TestPrepareDataTriggerProcessor_CanProcess(t *testing.T) {
	processor := &PrepareDataTriggerProcessor{}

	// Test case 1: Can process prepare data phase with pending task
	test1 := model.WorkloadTest{
		ExecBase: model.ExecBase{Status: model.ExecStatusRunning},
		Phase:    model.WorkloadTestPhasePrepareData,
	}
	assert.True(t, processor.CanProcess(test1, model.WorkloadTaskPhasePending))

	// Test case 2: Cannot process different phase
	test2 := model.WorkloadTest{
		ExecBase: model.ExecBase{Status: model.ExecStatusRunning},
		Phase:    model.WorkloadTestPhaseCreateCluster,
	}
	assert.False(t, processor.CanProcess(test2, model.WorkloadTaskPhasePending))
}

func TestRunTrafficTriggerProcessor_CanProcess(t *testing.T) {
	processor := &RunTrafficTriggerProcessor{}

	// Test case 1: Can process run workload phase with pending task
	test1 := model.WorkloadTest{
		ExecBase: model.ExecBase{Status: model.ExecStatusRunning},
		Phase:    model.WorkloadTestPhaseRunWorkload,
	}
	assert.True(t, processor.CanProcess(test1, model.WorkloadTaskPhasePending))

	// Test case 2: Cannot process different phase
	test2 := model.WorkloadTest{
		ExecBase: model.ExecBase{Status: model.ExecStatusRunning},
		Phase:    model.WorkloadTestPhasePrepareData,
	}
	assert.False(t, processor.CanProcess(test2, model.WorkloadTaskPhasePending))
}

func TestCreateClusterRunningProcessor_CanProcess(t *testing.T) {
	processor := &CreateClusterRunningProcessor{}

	// Test case 1: Can process create cluster phase with running task
	test1 := model.WorkloadTest{
		ExecBase: model.ExecBase{Status: model.ExecStatusRunning},
		Phase:    model.WorkloadTestPhaseCreateCluster,
	}
	assert.True(t, processor.CanProcess(test1, model.WorkloadTaskPhaseRunning))

	// Test case 2: Cannot process different phase
	test2 := model.WorkloadTest{
		ExecBase: model.ExecBase{Status: model.ExecStatusRunning},
		Phase:    model.WorkloadTestPhasePrepareData,
	}
	assert.False(t, processor.CanProcess(test2, model.WorkloadTaskPhaseRunning))

	// Test case 3: Cannot process pending task phase
	test3 := model.WorkloadTest{
		ExecBase: model.ExecBase{Status: model.ExecStatusRunning},
		Phase:    model.WorkloadTestPhaseCreateCluster,
	}
	assert.False(t, processor.CanProcess(test3, model.WorkloadTaskPhasePending))
}

func TestPrepareDataRunningProcessor_CanProcess(t *testing.T) {
	processor := &PrepareDataRunningProcessor{}

	// Test case 1: Can process prepare data phase with running task
	test1 := model.WorkloadTest{
		ExecBase: model.ExecBase{Status: model.ExecStatusRunning},
		Phase:    model.WorkloadTestPhasePrepareData,
	}
	assert.True(t, processor.CanProcess(test1, model.WorkloadTaskPhaseRunning))

	// Test case 2: Cannot process different phase
	test2 := model.WorkloadTest{
		ExecBase: model.ExecBase{Status: model.ExecStatusRunning},
		Phase:    model.WorkloadTestPhaseCreateCluster,
	}
	assert.False(t, processor.CanProcess(test2, model.WorkloadTaskPhaseRunning))
}

func TestRunTrafficRunningProcessor_CanProcess(t *testing.T) {
	processor := &RunTrafficRunningProcessor{}

	// Test case 1: Can process run workload phase with running task
	test1 := model.WorkloadTest{
		ExecBase: model.ExecBase{Status: model.ExecStatusRunning},
		Phase:    model.WorkloadTestPhaseRunWorkload,
	}
	assert.True(t, processor.CanProcess(test1, model.WorkloadTaskPhaseRunning))

	// Test case 2: Cannot process different phase
	test2 := model.WorkloadTest{
		ExecBase: model.ExecBase{Status: model.ExecStatusRunning},
		Phase:    model.WorkloadTestPhasePrepareData,
	}
	assert.False(t, processor.CanProcess(test2, model.WorkloadTaskPhaseRunning))
}

func TestPhaseTransitionProcessor_CanProcess(t *testing.T) {
	processor := &PhaseTransitionProcessor{}

	// Test case 1: Can process running test with completed task
	test1 := model.WorkloadTest{
		ExecBase: model.ExecBase{Status: model.ExecStatusRunning},
		Phase:    model.WorkloadTestPhaseCreateCluster,
	}
	assert.True(t, processor.CanProcess(test1, model.WorkloadTaskPhaseCompleted))

	// Test case 2: Can process running test with failed task
	test2 := model.WorkloadTest{
		ExecBase: model.ExecBase{Status: model.ExecStatusRunning},
		Phase:    model.WorkloadTestPhasePrepareData,
	}
	assert.True(t, processor.CanProcess(test2, model.WorkloadTaskPhaseFailed))

	// Test case 3: Cannot process pending test
	test3 := model.WorkloadTest{
		ExecBase: model.ExecBase{Status: model.ExecStatusPending},
		Phase:    model.WorkloadTestPhaseCreateCluster,
	}
	assert.False(t, processor.CanProcess(test3, model.WorkloadTaskPhaseCompleted))

	// Test case 4: Cannot process non-terminal task phase
	test4 := model.WorkloadTest{
		ExecBase: model.ExecBase{Status: model.ExecStatusRunning},
		Phase:    model.WorkloadTestPhaseCreateCluster,
	}
	assert.False(t, processor.CanProcess(test4, model.WorkloadTaskPhaseRunning))

	// Test case 5: Cannot process non-terminal task phase
	test5 := model.WorkloadTest{
		ExecBase: model.ExecBase{Status: model.ExecStatusStopping},
		Phase:    model.WorkloadTestPhaseCreateCluster,
	}
	assert.False(t, processor.CanProcess(test5, model.WorkloadTaskPhaseRunning))

}

func TestStopTestProcessor_CanProcess(t *testing.T) {
	processor := &StopTestProcessor{}

	// Test case 1: Can process stopping test with running task
	test1 := model.WorkloadTest{
		ExecBase: model.ExecBase{Status: model.ExecStatusStopping},
	}
	assert.True(t, processor.CanProcess(test1, model.WorkloadTaskPhaseRunning))

	// Test case 2: Can process stopping test with pending task
	test2 := model.WorkloadTest{
		ExecBase: model.ExecBase{Status: model.ExecStatusStopping},
	}
	assert.True(t, processor.CanProcess(test2, model.WorkloadTaskPhasePending))

	// Test case 3: Cannot process non-stopping test
	test3 := model.WorkloadTest{
		ExecBase: model.ExecBase{Status: model.ExecStatusRunning},
	}
	assert.False(t, processor.CanProcess(test3, model.WorkloadTaskPhaseRunning))

	// Test case 4: Cannot process stopping test with completed task
	test4 := model.WorkloadTest{
		ExecBase: model.ExecBase{Status: model.ExecStatusStopping},
	}
	assert.False(t, processor.CanProcess(test4, model.WorkloadTaskPhaseCompleted))
}

func TestStopTestProcessor_Process_PendingTask(t *testing.T) {
	// Arrange
	ctx := context.Background()
	mockGateway := new(MockWorkloadGateway)
	manager := &ProcessorManager{gateway: mockGateway}
	processor := NewStopTestProcessor(manager)

	test := model.WorkloadTest{
		ID: "test-id",
		ExecBase: model.ExecBase{
			Status: model.ExecStatusStopping,
		},
		Phase: model.WorkloadTestPhaseCreateCluster,
		Tasks: []model.WorkloadTask[any]{
			{
				WorkloadTaskBase: model.WorkloadTaskBase{
					Typ:   model.WorkloadTaskTypeCreateCluster,
					Phase: model.WorkloadTaskPhasePending,
				},
			},
		},
	}

	// Act
	updatedTest, err := processor.Process(ctx, test)

	// Assert
	assert.NoError(t, err)
	assert.Equal(t, model.WorkloadTaskPhaseStopped, updatedTest.CurrentPhaseTask().Phase)
	mockGateway.AssertExpectations(t)
}

func TestStopTestProcessor_Process_RunningTask_Success(t *testing.T) {
	// Arrange
	ctx := context.Background()
	mockGateway := new(MockWorkloadGateway)
	manager := &ProcessorManager{gateway: mockGateway}
	processor := NewStopTestProcessor(manager)

	test := model.WorkloadTest{
		ID:       "test-id",
		ExecBase: model.ExecBase{Status: model.ExecStatusStopping},
		Phase:    model.WorkloadTestPhaseCreateCluster,
		Tasks: []model.WorkloadTask[any]{
			{
				WorkloadTaskBase: model.WorkloadTaskBase{
					Typ:   model.WorkloadTaskTypeCreateCluster,
					Phase: model.WorkloadTaskPhaseRunning,
				},
			},
		},
	}

	mockGateway.On("CancelTask", ctx, mock.AnythingOfType("model.WorkloadTaskBase")).Return(nil)

	// Act
	updatedTest, err := processor.Process(ctx, test)

	// Assert
	assert.NoError(t, err)
	assert.Equal(t, model.WorkloadTaskPhaseStopping, updatedTest.CurrentPhaseTask().Phase)
	mockGateway.AssertExpectations(t)
}

func TestStopTestProcessor_Process_RunningTask_CancelFailed_RetryLessThanMax(t *testing.T) {
	// Arrange
	ctx := context.Background()
	mockGateway := new(MockWorkloadGateway)
	manager := &ProcessorManager{gateway: mockGateway}
	processor := NewStopTestProcessor(manager)

	test := model.WorkloadTest{
		ID:       "test-id",
		ExecBase: model.ExecBase{Status: model.ExecStatusStopping},
		Phase:    model.WorkloadTestPhaseCreateCluster,
		Tasks: []model.WorkloadTask[any]{
			{
				WorkloadTaskBase: model.WorkloadTaskBase{
					Typ:   model.WorkloadTaskTypeCreateCluster,
					Phase: model.WorkloadTaskPhaseRunning,
				},
			},
		},
	}

	mockGateway.On("CancelTask", ctx, mock.AnythingOfType("model.WorkloadTaskBase")).Return(errors.New("cancel failed"))

	// Act
	updatedTest, err := processor.Process(ctx, test)

	// Assert
	assert.NoError(t, err)
	assert.Equal(t, model.WorkloadTaskPhaseRunning, updatedTest.CurrentPhaseTask().Phase)
	assert.Equal(t, 1, processor.testStopFailCount[test.ID])
	mockGateway.AssertExpectations(t)
}

func TestStopTestProcessor_Process_RunningTask_CancelFailed_RetryExceedMax(t *testing.T) {
	// Arrange
	ctx := context.Background()
	mockGateway := new(MockWorkloadGateway)
	manager := &ProcessorManager{gateway: mockGateway}
	processor := NewStopTestProcessor(manager)
	processor.retryCount = 1 // Set retry count to 1 for this test

	test := model.WorkloadTest{
		ID:       "test-id",
		ExecBase: model.ExecBase{Status: model.ExecStatusStopping},
		Phase:    model.WorkloadTestPhaseCreateCluster,
		Tasks: []model.WorkloadTask[any]{
			{
				WorkloadTaskBase: model.WorkloadTaskBase{
					Typ:   model.WorkloadTaskTypeCreateCluster,
					Phase: model.WorkloadTaskPhaseRunning,
				},
			},
		},
	}

	mockGateway.On("CancelTask", ctx, mock.AnythingOfType("model.WorkloadTaskBase")).Return(errors.New("cancel failed"))

	// First call, should retry
	updatedTest, err := processor.Process(ctx, test)
	assert.NoError(t, err) // Should not return error, but update test status
	assert.Equal(t, model.WorkloadTaskPhaseRunning, updatedTest.CurrentPhaseTask().Phase)

	// Second call, should exceed max retry and set to failed
	updatedTest, err = processor.Process(ctx, test)
	assert.NoError(t, err) // Should not return error, but update test status
	assert.Equal(t, model.WorkloadTaskPhaseFailed, updatedTest.CurrentPhaseTask().Phase)
	assert.NotContains(t, processor.testStopFailCount, test.ID) // Should be removed after exceeding retry
	mockGateway.AssertExpectations(t)
}

func TestStopTestRunningProcessor_CanProcess(t *testing.T) {
	processor := &StopTestRunningProcessor{}

	// Test case 1: Can process stopping test with stopping task
	test1 := model.WorkloadTest{
		ExecBase: model.ExecBase{
			Status: model.ExecStatusStopping,
		},
		Phase: model.WorkloadTestPhaseCreateCluster,
	}
	assert.True(t, processor.CanProcess(test1, model.WorkloadTaskPhaseStopping))

	// Test case 2: Cannot process non-stopping test
	test2 := model.WorkloadTest{
		ExecBase: model.ExecBase{Status: model.ExecStatusRunning},
		Phase:    model.WorkloadTestPhaseCreateCluster,
	}
	assert.False(t, processor.CanProcess(test2, model.WorkloadTaskPhaseStopping))

	// Test case 3: Cannot process stopping test with non-stopping task phase
	test3 := model.WorkloadTest{
		ExecBase: model.ExecBase{Status: model.ExecStatusStopping},
		Phase:    model.WorkloadTestPhaseCreateCluster,
	}
	assert.False(t, processor.CanProcess(test3, model.WorkloadTaskPhaseRunning))
}

func TestStopTestRunningProcessor_Process_PollStopped(t *testing.T) {
	// Arrange
	ctx := context.Background()
	mockGateway := new(MockWorkloadGateway)
	manager := &ProcessorManager{gateway: mockGateway}
	processor := NewStopTestRunningProcessor(manager)

	test := model.WorkloadTest{
		ID:       "test-id",
		ExecBase: model.ExecBase{Status: model.ExecStatusStopping},
		Phase:    model.WorkloadTestPhaseCreateCluster,
		Tasks: []model.WorkloadTask[any]{
			{
				WorkloadTaskBase: model.WorkloadTaskBase{
					Phase: model.WorkloadTaskPhaseStopping,
					Typ:   model.WorkloadTaskTypeCreateCluster,
				},
			},
		},
	}

	mockGateway.On("PollCreateClusterTask", ctx, mock.AnythingOfType("model.WorkloadTaskBase")).Return(model.WorkloadTaskBase{ExecBase: model.ExecBase{Status: model.ExecStatusStopped}}, model.CreateClusterResult{}, nil)

	// Act
	updatedTest, err := processor.Process(ctx, test)

	// Assert
	assert.NoError(t, err)
	assert.Equal(t, model.WorkloadTaskPhaseStopped, updatedTest.CurrentPhaseTask().Phase)
	mockGateway.AssertExpectations(t)
}

func TestStopTestRunningProcessor_Process_PollRunning(t *testing.T) {
	// Arrange
	ctx := context.Background()
	mockGateway := new(MockWorkloadGateway)
	manager := &ProcessorManager{gateway: mockGateway}
	processor := NewStopTestRunningProcessor(manager)

	test := model.WorkloadTest{
		ID:       "test-id",
		ExecBase: model.ExecBase{Status: model.ExecStatusStopping},
		Phase:    model.WorkloadTestPhaseCreateCluster,
		Tasks: []model.WorkloadTask[any]{
			{
				WorkloadTaskBase: model.WorkloadTaskBase{
					Phase: model.WorkloadTaskPhaseStopping,
					Typ:   model.WorkloadTaskTypeCreateCluster,
				},
			},
		},
	}

	mockGateway.On(
		"PollCreateClusterTask", ctx, mock.AnythingOfType("model.WorkloadTaskBase"),
	).Return(
		model.WorkloadTaskBase{
			ExecBase: model.ExecBase{Status: model.ExecStatusRunning}},
		model.CreateClusterResult{},
		nil)

	// Act
	updatedTest, err := processor.Process(ctx, test)

	// Assert
	assert.NoError(t, err)
	assert.Equal(t, model.WorkloadTaskPhaseStopping, updatedTest.CurrentPhaseTask().Phase)
	mockGateway.AssertExpectations(t)
}

func TestRunTrafficProcessor_Process_RunningTask_Recreate(t *testing.T) {
	// Arrange
	ctx := context.Background()
	mockGateway := new(MockWorkloadGateway)
	manager := &ProcessorManager{gateway: mockGateway}
	processor := NewRunTrafficRunningProcessor(manager)

	startedAt := time.Now().Add(-5 * time.Minute)
	test := model.WorkloadTest{
		ID:       "test-id",
		ExecBase: model.ExecBase{Status: model.ExecStatusRunning},
		Phase:    model.WorkloadTestPhaseRunWorkload,
		Params: model.WorkloadParams{
			{
				Name:  string(constant.Duration),
				Value: "10",
			},
		},
		Tasks: []model.WorkloadTask[any]{
			{
				WorkloadTaskBase: model.WorkloadTaskBase{
					Phase: model.WorkloadTaskPhaseUpdating,
					Typ:   model.WorkloadTaskTypeRunTraffic,
					ExecBase: model.ExecBase{
						Status:    model.ExecStatusRunning,
						StartedAt: &startedAt,
					},
					RelatedServiceID: "1",
				},
			},
		},
	}

	mockGateway.On("TriggerRecreateTraffic",
		ctx,
		mock.AnythingOfType("int64"),
		mock.AnythingOfType("model.WorkloadParams"),
	).Return(
		model.WorkloadTaskBase{
			Typ: model.WorkloadTaskTypeRunTraffic,
			ExecBase: model.ExecBase{
				Status: model.ExecStatusRunning}},
		nil)

	// Act
	updatedTest, err := processor.Process(ctx, test)

	// Assert
	assert.NoError(t, err)
	assert.Equal(t, model.WorkloadTaskPhaseRunning, updatedTest.CurrentPhaseTask().Phase)
	mockGateway.AssertExpectations(t)

}

func TestPhaseTransitionProcessor_Process(t *testing.T) {
	ctx := context.Background()
	mockGateway := new(MockWorkloadGateway)
	mockHook := new(MockHook)
	manager := &ProcessorManager{gateway: mockGateway}
	processor := NewPhaseTransitionProcessor(
		manager,
		mockHook,
	)

	mockHook.On("Match",
		mock.AnythingOfType("model.WorkloadTestPhase"),
		mock.AnythingOfType("model.WorkloadTestPhase"),
		mock.AnythingOfType("model.WorkloadTest"),
	).Return(true)

	mockHook.On("Do",
		ctx,
		mock.AnythingOfType("model.WorkloadTest"),
	).Return(nil)

	test := model.WorkloadTest{
		ID:       "test-id",
		ExecBase: model.ExecBase{Status: model.ExecStatusRunning},
		Phase:    model.WorkloadTestPhaseRunWorkload,
		Params: model.WorkloadParams{
			{
				Name:  string(constant.Duration),
				Value: "10",
			},
		},
		Tasks: []model.WorkloadTask[any]{
			{
				WorkloadTaskBase: model.WorkloadTaskBase{
					Phase: model.WorkloadTaskPhaseCompleted,
					Typ:   model.WorkloadTaskTypeRunTraffic,
					ExecBase: model.ExecBase{
						Status: model.ExecStatusRunning,
					},
					RelatedServiceID: "1",
				},
			},
		},
	}
	_, err := processor.Process(ctx, test)
	assert.NoError(t, err)
	time.Sleep(10 * time.Second)
	// assert.Equal(t, model.WorkloadTaskPhaseCompleted, test.CurrentPhaseTask().Phase)
}
