import { QueueMessageTypes, QueueMessageType, LoggerPort } from '@humand-packages/common';
import { mock, MockProxy } from 'jest-mock-extended';
import { PerformanceReviewQueueService } from '../../../../src/api/modules/performanceReviews/business/services/performanceReviewQueueService';
import { PerformanceReviewCalibrationService } from '../../../../src/api/modules/performanceReviews/business/services/performanceReviewCalibrationService';
import { fakePositiveInteger } from '../../../common/common';

jest.mock('../../../../src/api/config/sequelize', () => ({
    getSequelize: jest.fn().mockReturnValue({
        transaction: jest.fn().mockImplementation((cb: () => Promise<unknown>) => cb()),
    }),
}));

describe('PerformanceReviewQueueService', () => {
    let service: PerformanceReviewQueueService;
    let mockLogger: MockProxy<LoggerPort>;
    let mockCalibrationService: MockProxy<PerformanceReviewCalibrationService>;

    beforeEach(() => {
        mockLogger = mock<LoggerPort>();
        mockCalibrationService = mock<PerformanceReviewCalibrationService>();
        service = new PerformanceReviewQueueService(mockLogger, mockCalibrationService);
    });

    afterEach(() => {
        jest.clearAllMocks();
    });

    describe('handleQueueMessage — error propagation (regression guard)', () => {
        it('should rethrow when calibrationService.syncCalibratedRulesForUser rejects (USER_ITEMS_CHANGE)', async () => {
            const instanceId = fakePositiveInteger();
            const userId = fakePositiveInteger();
            const message: QueueMessageType<QueueMessageTypes.USER_ITEMS_CHANGE> = {
                messageType: QueueMessageTypes.USER_ITEMS_CHANGE,
                messageBody: { instanceId, userId, addedItems: ['item1'], removedItems: [] } as any,
                receiptHandle: 'rh-1',
                messageId: 'msg-1',
            };
            const error = new Error('DB timeout');
            mockCalibrationService.syncCalibratedRulesForUser.mockRejectedValue(error);

            await expect(service.handleQueueMessage(message)).rejects.toThrow('DB timeout');
        });

        it('should rethrow when calibrationService.syncCalibratedRulesForUser rejects (SEGMENTATION_ITEM_USERS_CHANGE)', async () => {
            const instanceId = fakePositiveInteger();
            const userId = fakePositiveInteger();
            const message: QueueMessageType<QueueMessageTypes.SEGMENTATION_ITEM_USERS_CHANGE> = {
                messageType: QueueMessageTypes.SEGMENTATION_ITEM_USERS_CHANGE,
                messageBody: { instanceId, users: [userId] } as any,
                receiptHandle: 'rh-2',
                messageId: 'msg-2',
            };
            const error = new Error('Conflict');
            mockCalibrationService.syncCalibratedRulesForUser.mockRejectedValue(error);

            await expect(service.handleQueueMessage(message)).rejects.toThrow('Conflict');
        });
    });

    describe('handleQueueMessage — unknown messageType', () => {
        it('should NOT throw and should log warn for unknown messageType', async () => {
            const message = {
                messageType: 'UNKNOWN_TYPE' as QueueMessageTypes,
                messageBody: {} as any,
                receiptHandle: 'rh-3',
                messageId: 'msg-3',
            };

            await expect(service.handleQueueMessage(message)).resolves.toBeUndefined();
            expect(mockLogger.warn).toHaveBeenCalledWith(
                message,
                expect.stringContaining('No handler found for message type: UNKNOWN_TYPE'),
            );
            expect(mockCalibrationService.syncCalibratedRulesForUser).not.toHaveBeenCalled();
        });
    });

    describe('handleQueueMessage — routing', () => {
        const instanceId = fakePositiveInteger();
        const userId = fakePositiveInteger();

        it('USER_ITEMS_CHANGE with non-empty items calls syncCalibratedRulesForUser once', async () => {
            const message: QueueMessageType<QueueMessageTypes.USER_ITEMS_CHANGE> = {
                messageType: QueueMessageTypes.USER_ITEMS_CHANGE,
                messageBody: { instanceId, userId, addedItems: ['item1'], removedItems: [] } as any,
                receiptHandle: 'rh-4',
                messageId: 'msg-4',
            };
            mockCalibrationService.syncCalibratedRulesForUser.mockResolvedValue(undefined as any);

            await service.handleQueueMessage(message);

            expect(mockCalibrationService.syncCalibratedRulesForUser).toHaveBeenCalledTimes(1);
            expect(mockCalibrationService.syncCalibratedRulesForUser).toHaveBeenCalledWith(instanceId, userId);
        });

        it('USER_ITEMS_CHANGE with addedItems=[] and removedItems=[] is a no-op', async () => {
            const message: QueueMessageType<QueueMessageTypes.USER_ITEMS_CHANGE> = {
                messageType: QueueMessageTypes.USER_ITEMS_CHANGE,
                messageBody: { instanceId, userId, addedItems: [], removedItems: [] } as any,
                receiptHandle: 'rh-5',
                messageId: 'msg-5',
            };

            await service.handleQueueMessage(message);

            expect(mockCalibrationService.syncCalibratedRulesForUser).not.toHaveBeenCalled();
        });

        it('SEGMENTATION_ITEM_USERS_CHANGE with users=[] is a no-op', async () => {
            const message: QueueMessageType<QueueMessageTypes.SEGMENTATION_ITEM_USERS_CHANGE> = {
                messageType: QueueMessageTypes.SEGMENTATION_ITEM_USERS_CHANGE,
                messageBody: { instanceId, users: [] } as any,
                receiptHandle: 'rh-6',
                messageId: 'msg-6',
            };

            await service.handleQueueMessage(message);

            expect(mockCalibrationService.syncCalibratedRulesForUser).not.toHaveBeenCalled();
        });

        it('SEGMENTATION_ITEM_USERS_CHANGE with N users calls syncCalibratedRulesForUser N times', async () => {
            const users = [fakePositiveInteger(), fakePositiveInteger(), fakePositiveInteger()];
            const message: QueueMessageType<QueueMessageTypes.SEGMENTATION_ITEM_USERS_CHANGE> = {
                messageType: QueueMessageTypes.SEGMENTATION_ITEM_USERS_CHANGE,
                messageBody: { instanceId, users } as any,
                receiptHandle: 'rh-7',
                messageId: 'msg-7',
            };
            mockCalibrationService.syncCalibratedRulesForUser.mockResolvedValue(undefined as any);

            await service.handleQueueMessage(message);

            expect(mockCalibrationService.syncCalibratedRulesForUser).toHaveBeenCalledTimes(3);
            expect(mockCalibrationService.syncCalibratedRulesForUser).toHaveBeenNthCalledWith(1, instanceId, users[0]);
            expect(mockCalibrationService.syncCalibratedRulesForUser).toHaveBeenNthCalledWith(2, instanceId, users[1]);
            expect(mockCalibrationService.syncCalibratedRulesForUser).toHaveBeenNthCalledWith(3, instanceId, users[2]);
        });

        it('USER_CREATION is a stub — no calibration call', async () => {
            const message: QueueMessageType<QueueMessageTypes.USER_CREATION> = {
                messageType: QueueMessageTypes.USER_CREATION,
                messageBody: { instanceId, userId } as any,
                receiptHandle: 'rh-8',
                messageId: 'msg-8',
            };

            await service.handleQueueMessage(message);

            expect(mockCalibrationService.syncCalibratedRulesForUser).not.toHaveBeenCalled();
        });

        it('USER_DELETION is a stub — no calibration call', async () => {
            const message: QueueMessageType<QueueMessageTypes.USER_DELETION> = {
                messageType: QueueMessageTypes.USER_DELETION,
                messageBody: { instanceId, userId } as any,
                receiptHandle: 'rh-9',
                messageId: 'msg-9',
            };

            await service.handleQueueMessage(message);

            expect(mockCalibrationService.syncCalibratedRulesForUser).not.toHaveBeenCalled();
        });

        it('SEGMENTATION_ITEM_DELETION is a stub — no calibration call', async () => {
            const message: QueueMessageType<QueueMessageTypes.SEGMENTATION_ITEM_DELETION> = {
                messageType: QueueMessageTypes.SEGMENTATION_ITEM_DELETION,
                messageBody: { instanceId } as any,
                receiptHandle: 'rh-10',
                messageId: 'msg-10',
            };

            await service.handleQueueMessage(message);

            expect(mockCalibrationService.syncCalibratedRulesForUser).not.toHaveBeenCalled();
        });
    });

    describe('handleQueueMessage — transactions', () => {
        it('opens one transaction per user in SEGMENTATION_ITEM_USERS_CHANGE bulk case', async () => {
            const { getSequelize } = jest.requireMock('../../../../src/api/config/sequelize') as {
                getSequelize: jest.Mock;
            };
            const mockTransaction = getSequelize().transaction as jest.Mock;
            const users = [fakePositiveInteger(), fakePositiveInteger()];
            const message: QueueMessageType<QueueMessageTypes.SEGMENTATION_ITEM_USERS_CHANGE> = {
                messageType: QueueMessageTypes.SEGMENTATION_ITEM_USERS_CHANGE,
                messageBody: { instanceId: fakePositiveInteger(), users } as any,
                receiptHandle: 'rh-tx',
                messageId: 'msg-tx',
            };
            mockCalibrationService.syncCalibratedRulesForUser.mockResolvedValue(undefined as any);

            await service.handleQueueMessage(message);

            expect(mockTransaction).toHaveBeenCalledTimes(2);
        });
    });
});
