package service

import (
	"context"
	"testing"
	"time"

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

	"security-center-backend/internal/config"
	coremodel "security-center-backend/internal/model"
	"security-center-backend/internal/modules/caasm/mocks"
	caasmmodel "security-center-backend/internal/modules/caasm/model"
	"security-center-backend/internal/modules/caasm/queue"
	"security-center-backend/internal/pkg/logger"
)

type runtimeConfigRepoStub struct {
	cfg *caasmmodel.Config
}

func (s *runtimeConfigRepoStub) Get(context.Context) (*caasmmodel.Config, error) {
	return s.cfg, nil
}

func (s *runtimeConfigRepoStub) Upsert(_ context.Context, data caasmmodel.Config) error {
	s.cfg = &data
	return nil
}

type runtimeQueueStub struct{}

func (runtimeQueueStub) Enqueue(context.Context, *queue.SyncJobPayload) error { return nil }
func (runtimeQueueStub) Acknowledge(context.Context, string) error            { return nil }
func (runtimeQueueStub) Fail(context.Context, *queue.SyncJobPayload, string, error) error {
	return nil
}
func (runtimeQueueStub) GetQueueDepth(context.Context) (int64, error)     { return 0, nil }
func (runtimeQueueStub) GetDLQDepth(context.Context) (int64, error)       { return 0, nil }
func (runtimeQueueStub) CleanupStuckJobs(context.Context) error           { return nil }
func (runtimeQueueStub) ExistsActiveAccount(context.Context, uint64) bool { return false }
func (runtimeQueueStub) Dequeue(context.Context, string) (*queue.SyncJobPayload, string, error) {
	time.Sleep(2 * time.Millisecond)
	return nil, "", nil
}

func TestCollectorSchedulerSkipsWhenRuntimeCapReached(t *testing.T) {
	accountRepo := new(mocks.MockCloudAccountRepository)
	jobsRepo := new(mocks.MockSyncJobRepository)

	accountRepo.On("FindDueForSync", mock.Anything).Return([]caasmmodel.CloudAccount{
		{Base: coremodel.Base{ID: 1001}, Provider: caasmmodel.CloudProviderAWS},
	}, nil)
	jobsRepo.On("CountActiveJobs", mock.Anything).Return(int64(2), nil)

	scheduler := NewCollectorScheduler(
		accountRepo,
		nil,
		jobsRepo,
		nil,
		logger.New(config.LogConfig{Level: "error", Format: "text"}),
		time.Hour,
		func(ctx context.Context) (CoordinationCap, error) {
			return CoordinationCap{
				Config: caasmmodel.ThrottlingConfig{MaxConcurrency: 2},
				Source: "global_default",
			}, nil
		},
	)

	scheduler.runSync()

	accountRepo.AssertExpectations(t)
	jobsRepo.AssertExpectations(t)
}

func TestRuntimeControlStatusReflectsPressureCap(t *testing.T) {
	jobsRepo := new(mocks.MockSyncJobRepository)
	jobsRepo.On("CountActiveJobs", mock.Anything).Return(int64(2), nil)

	runtime := NewRuntimeControl(
		nil,
		5,
		nil,
		nil,
		jobsRepo,
		func(ctx context.Context) (CoordinationCap, error) {
			return CoordinationCap{
				Config: caasmmodel.ThrottlingConfig{MaxConcurrency: 2},
				Source: "global_default",
			}, nil
		},
		logger.New(config.LogConfig{Level: "error", Format: "text"}),
	)

	status := runtime.Status()
	assert.Equal(t, 2, status.WorkerLimit)
	assert.Equal(t, 2, status.RuntimePressureCap)
	assert.Equal(t, int64(2), status.ActiveJobCount)
	assert.Equal(t, "at_capacity", status.PressureState)
	assert.Equal(t, "db_backed", status.CoordinationMode)
	assert.Equal(t, "global_default", status.RuntimeLimitSource)
	jobsRepo.AssertExpectations(t)
}

func TestCollectorSchedulerSkipsWhenKillSwitchEnabled(t *testing.T) {
	accountRepo := new(mocks.MockCloudAccountRepository)
	jobsRepo := new(mocks.MockSyncJobRepository)
	cloudAcctSvc := NewCloudAccountService(accountRepo, jobsRepo, nil)
	cloudAcctSvc.SetConfigService(NewConfigService(&runtimeConfigRepoStub{
		cfg: &caasmmodel.Config{
			Data: coremodel.JSONB{
				"sync_settings": map[string]interface{}{
					"mode":                "queue",
					"kill_switch_enabled": true,
				},
			},
		},
	}))

	scheduler := NewCollectorScheduler(
		accountRepo,
		cloudAcctSvc,
		jobsRepo,
		nil,
		logger.New(config.LogConfig{Level: "error", Format: "text"}),
		time.Hour,
		nil,
	)

	scheduler.runSync()

	accountRepo.AssertNotCalled(t, "FindDueForSync", mock.Anything)
}

func TestRuntimeControlStartBlockedByKillSwitch(t *testing.T) {
	jobsRepo := new(mocks.MockSyncJobRepository)
	jobsRepo.On("CountActiveJobs", mock.Anything).Return(int64(0), nil)

	runtime := NewRuntimeControl(
		nil,
		5,
		nil,
		&CollectorScheduler{},
		jobsRepo,
		nil,
		logger.New(config.LogConfig{Level: "error", Format: "text"}),
	)
	runtime.SetKillSwitchProvider(func(context.Context) (bool, error) {
		return true, nil
	})

	status, err := runtime.Start()
	assert.Error(t, err)
	assert.True(t, status.KillSwitchEnabled)
	assert.False(t, status.WorkersRunning)
	assert.False(t, status.SchedulerRunning)
}

func TestRuntimeControlStartWorkersDoesNotStartScheduler(t *testing.T) {
	scheduler := NewCollectorScheduler(
		nil,
		nil,
		nil,
		nil,
		logger.New(config.LogConfig{Level: "error", Format: "text"}),
		time.Hour,
		nil,
	)
	runtime := NewRuntimeControl(
		runtimeQueueStub{},
		2,
		func(context.Context, *queue.SyncJobPayload) error { return nil },
		scheduler,
		nil,
		nil,
		logger.New(config.LogConfig{Level: "error", Format: "text"}),
	)

	status, err := runtime.StartWorkers()
	assert.NoError(t, err)
	assert.True(t, status.WorkersRunning)
	assert.False(t, status.SchedulerRunning)

	stopStatus := runtime.StopWorkers()
	assert.False(t, stopStatus.WorkersRunning)
	assert.False(t, stopStatus.SchedulerRunning)
}

func TestRuntimeControlStartWorkersCanRestartStoppedPool(t *testing.T) {
	scheduler := NewCollectorScheduler(
		nil,
		nil,
		nil,
		nil,
		logger.New(config.LogConfig{Level: "error", Format: "text"}),
		time.Hour,
		nil,
	)
	runtime := NewRuntimeControl(
		runtimeQueueStub{},
		2,
		func(context.Context, *queue.SyncJobPayload) error { return nil },
		scheduler,
		nil,
		nil,
		logger.New(config.LogConfig{Level: "error", Format: "text"}),
	)

	firstStatus, err := runtime.StartWorkers()
	assert.NoError(t, err)
	assert.True(t, firstStatus.WorkersRunning)

	stoppedStatus := runtime.StopWorkers()
	assert.False(t, stoppedStatus.WorkersRunning)

	secondStatus, err := runtime.StartWorkers()
	assert.NoError(t, err)
	assert.True(t, secondStatus.WorkersRunning)
	assert.False(t, secondStatus.SchedulerRunning)

	runtime.Stop()
}

func TestRuntimeControlStopSchedulerPreservesWorkers(t *testing.T) {
	scheduler := NewCollectorScheduler(
		nil,
		nil,
		nil,
		nil,
		logger.New(config.LogConfig{Level: "error", Format: "text"}),
		time.Hour,
		nil,
	)
	runtime := NewRuntimeControl(
		runtimeQueueStub{},
		1,
		func(context.Context, *queue.SyncJobPayload) error { return nil },
		scheduler,
		nil,
		nil,
		logger.New(config.LogConfig{Level: "error", Format: "text"}),
	)

	_, err := runtime.StartWorkers()
	assert.NoError(t, err)
	_, err = runtime.StartScheduler()
	assert.NoError(t, err)

	status := runtime.StopScheduler()
	assert.True(t, status.WorkersRunning)
	assert.False(t, status.SchedulerRunning)

	runtime.Stop()
}

func TestRuntimeControlStartWorkersBlockedByKillSwitch(t *testing.T) {
	scheduler := NewCollectorScheduler(
		nil,
		nil,
		nil,
		nil,
		logger.New(config.LogConfig{Level: "error", Format: "text"}),
		time.Hour,
		nil,
	)
	runtime := NewRuntimeControl(
		runtimeQueueStub{},
		1,
		func(context.Context, *queue.SyncJobPayload) error { return nil },
		scheduler,
		nil,
		nil,
		logger.New(config.LogConfig{Level: "error", Format: "text"}),
	)
	runtime.SetKillSwitchProvider(func(context.Context) (bool, error) {
		return true, nil
	})

	status, err := runtime.StartWorkers()
	assert.Error(t, err)
	assert.True(t, status.KillSwitchEnabled)
	assert.False(t, status.WorkersRunning)
	assert.False(t, status.SchedulerRunning)
}

func TestRuntimeControlStartSchedulerBlockedByKillSwitch(t *testing.T) {
	scheduler := NewCollectorScheduler(
		nil,
		nil,
		nil,
		nil,
		logger.New(config.LogConfig{Level: "error", Format: "text"}),
		time.Hour,
		nil,
	)
	runtime := NewRuntimeControl(
		runtimeQueueStub{},
		1,
		func(context.Context, *queue.SyncJobPayload) error { return nil },
		scheduler,
		nil,
		nil,
		logger.New(config.LogConfig{Level: "error", Format: "text"}),
	)
	runtime.SetKillSwitchProvider(func(context.Context) (bool, error) {
		return true, nil
	})

	status, err := runtime.StartScheduler()
	assert.Error(t, err)
	assert.True(t, status.KillSwitchEnabled)
	assert.False(t, status.SchedulerRunning)
}

func TestCollectorSchedulerSkipsTriggerWhenRepositoryReportsActiveJob(t *testing.T) {
	accountRepo := new(mocks.MockCloudAccountRepository)
	jobsRepo := new(mocks.MockSyncJobRepository)
	cloudAcctSvc := NewCloudAccountService(accountRepo, jobsRepo, nil)
	cloudAcctSvc.SetConfigService(NewConfigService(&runtimeConfigRepoStub{
		cfg: &caasmmodel.Config{Data: coremodel.JSONB{}},
	}))

	accountRepo.On("FindDueForSync", mock.Anything).Return([]caasmmodel.CloudAccount{
		{Base: coremodel.Base{ID: 1002}, Provider: caasmmodel.CloudProviderAWS},
	}, nil).Once()
	jobsRepo.On("CountActiveJobs", mock.Anything).Return(int64(0), nil).Once()
	jobsRepo.On("FindActiveJobForAccount", mock.Anything, uint64(1002)).Return(&caasmmodel.SyncJob{
		Base:   coremodel.Base{ID: 77},
		Status: caasmmodel.SyncJobRunning,
	}, nil).Once()

	scheduler := NewCollectorScheduler(
		accountRepo,
		cloudAcctSvc,
		jobsRepo,
		nil,
		logger.New(config.LogConfig{Level: "error", Format: "text"}),
		time.Hour,
		func(context.Context) (CoordinationCap, error) {
			return CoordinationCap{
				Config: caasmmodel.ThrottlingConfig{MaxConcurrency: 2},
				Source: "global_default",
			}, nil
		},
	)

	scheduler.runSync()

	accountRepo.AssertNotCalled(t, "FindByID", mock.Anything, mock.Anything)
	accountRepo.AssertExpectations(t)
	jobsRepo.AssertExpectations(t)
}
