package service

import (
	"context"
	"testing"
	"time"

	"github.com/stretchr/testify/mock"

	coremodel "security-center-backend/internal/model"
	"security-center-backend/internal/modules/caasm/mocks"
	caasmmodel "security-center-backend/internal/modules/caasm/model"
)

func TestZombieReaperMarksTimedOutJobsFailedWithoutCleanupHooks(t *testing.T) {
	ctx := context.Background()
	jobRepo := new(mocks.MockSyncJobRepository)
	reaper := NewZombieReaper(jobRepo, nil)

	runningJob := caasmmodel.SyncJob{
		Base:   coremodel.Base{ID: 88},
		Status: caasmmodel.SyncJobRunning,
	}
	queuedJob := caasmmodel.SyncJob{
		Base:   coremodel.Base{ID: 89},
		Status: caasmmodel.SyncJobQueued,
	}

	jobRepo.On("FindStaleJobs", ctx, caasmmodel.SyncJobRunning, mock.AnythingOfType("time.Time")).
		Return([]caasmmodel.SyncJob{runningJob}, nil).Once()
	jobRepo.On("FindStaleJobs", ctx, caasmmodel.SyncJobQueued, mock.AnythingOfType("time.Time")).
		Return([]caasmmodel.SyncJob{queuedJob}, nil).Once()
	jobRepo.On("FindByID", ctx, uint64(88)).Return(&runningJob, nil).Once()
	jobRepo.On("FindByID", ctx, uint64(89)).Return(&queuedJob, nil).Once()
	jobRepo.On("UpdateFields", ctx, uint64(88), mock.MatchedBy(func(fields map[string]interface{}) bool {
		status, ok := fields["status"].(caasmmodel.SyncJobStatus)
		_, hasFinishedAt := fields["finished_at"].(time.Time)
		message, hasError := fields["error_message"].(string)
		return ok && hasFinishedAt && hasError && status == caasmmodel.SyncJobFailed && message != ""
	})).Return(nil).Once()
	jobRepo.On("UpdateFields", ctx, uint64(89), mock.MatchedBy(func(fields map[string]interface{}) bool {
		status, ok := fields["status"].(caasmmodel.SyncJobStatus)
		_, hasFinishedAt := fields["finished_at"].(time.Time)
		message, hasError := fields["error_message"].(string)
		return ok && hasFinishedAt && hasError && status == caasmmodel.SyncJobFailed && message != ""
	})).Return(nil).Once()

	reaper.reap(ctx)

	jobRepo.AssertExpectations(t)
	jobRepo.AssertNumberOfCalls(t, "UpdateFields", 2)
}

func TestZombieReaperUsesConfiguredTimeouts(t *testing.T) {
	ctx := context.Background()
	jobRepo := new(mocks.MockSyncJobRepository)
	reaper := NewZombieReaper(jobRepo, func(context.Context) (SyncJobTimeouts, error) {
		return SyncJobTimeouts{
			RunningTimeout: 60 * time.Minute,
			QueuedTimeout:  30 * time.Minute,
		}, nil
	})

	runningJob := caasmmodel.SyncJob{Base: coremodel.Base{ID: 188}, Status: caasmmodel.SyncJobRunning}
	queuedJob := caasmmodel.SyncJob{Base: coremodel.Base{ID: 189}, Status: caasmmodel.SyncJobQueued}

	jobRepo.On("FindStaleJobs", ctx, caasmmodel.SyncJobRunning, mock.AnythingOfType("time.Time")).
		Return([]caasmmodel.SyncJob{runningJob}, nil).Once()
	jobRepo.On("FindStaleJobs", ctx, caasmmodel.SyncJobQueued, mock.AnythingOfType("time.Time")).
		Return([]caasmmodel.SyncJob{queuedJob}, nil).Once()
	jobRepo.On("FindByID", ctx, uint64(188)).Return(&runningJob, nil).Once()
	jobRepo.On("FindByID", ctx, uint64(189)).Return(&queuedJob, nil).Once()
	jobRepo.On("UpdateFields", ctx, uint64(188), mock.MatchedBy(func(fields map[string]interface{}) bool {
		return fields["error_message"] == "Job timed out (exceeded 60 minutes)"
	})).Return(nil).Once()
	jobRepo.On("UpdateFields", ctx, uint64(189), mock.MatchedBy(func(fields map[string]interface{}) bool {
		return fields["error_message"] == "Job timed out in queue (exceeded 30 minutes)"
	})).Return(nil).Once()

	reaper.reap(ctx)

	jobRepo.AssertExpectations(t)
}
