import { createNamespace, getNamespace } from 'cls-hooked';
import { QueueMessageTypes, QueueMessageType, LoggerPort, QueuePort, PromisesPort } from '@humand-packages/common';
import { Container } from 'typedi';
import { MockProxy, mock } from 'jest-mock-extended';
import { PerformanceReviewQueueConsumer } from '../../../../src/api/modules/performanceReviews/business/services/performanceReviewQueueConsumer';
import { DeferredExecutionPerformanceReviewsPort } from '../../../../src/api/modules/performanceReviews/business/ports/deferredExecutionPerformanceReviewsPort';
import { DeadLetterQueueRepositoryPort } from '../../../../src/api/modules/deferredExecution/business/ports/deadLetterQueueRepositoryPort';
import { fakePositiveInteger } from '../../../common/common';

type PerformanceReviewQueueConsumerTestable = PerformanceReviewQueueConsumer & {
    invokeQueueMessageHandlers(message: QueueMessageType<QueueMessageTypes>): Promise<unknown>;
    invokeDlqMessageHandlers(message: QueueMessageType<QueueMessageTypes>): Promise<unknown>;
};

const testNs = getNamespace('test-ns-pr-queue') || createNamespace('test-ns-pr-queue');
Container.set('GLOBAL_NAMESPACE', testNs);

jest.doMock('../../../../src/api/apiConfig/configSetup', () => ({
    getConfig: () => ({
        awsSqsPerformanceReviewDlqMessageAttempts: 3,
        awsSqsPerformanceReviewIntervalSeconds: 10,
        awsSqsPerformanceReviewMaxConcurrentMessages: 5,
        awsSqsPerformanceReviewMaxConcurrentTasks: 5,
        awsSqsPerformanceReviewMaxNumberMessages: 3,
        awsSqsPerformanceReviewQueueUrl: 'http://localhost:4566/000000000000/performance-review-queue',
        awsSqsPerformanceReviewQueueDlqUrl: 'http://localhost:4566/000000000000/performance-review-dlq',
        awsSqsPerformanceReviewVisibilityTimeout: 300,
        awsSqsPerformanceReviewWaitTimeSeconds: 5,
    }),
}));

describe('PerformanceReviewQueueConsumer', () => {
    let consumer: PerformanceReviewQueueConsumerTestable;
    let mockQueuePort: MockProxy<QueuePort>;
    let mockPromisesPort: MockProxy<PromisesPort>;
    let mockLogger: MockProxy<LoggerPort>;
    let mockDlqRepository: MockProxy<DeadLetterQueueRepositoryPort>;
    let mockPerformanceReviewsPort: MockProxy<DeferredExecutionPerformanceReviewsPort>;

    const receiptHandle = 'test-receipt-handle';
    const messageId = 'test-message-id';
    const instanceId = fakePositiveInteger();
    const userId = fakePositiveInteger();

    beforeEach(() => {
        mockQueuePort = mock<QueuePort>();
        mockPromisesPort = mock<PromisesPort>();
        mockLogger = mock<LoggerPort>();
        mockDlqRepository = mock<DeadLetterQueueRepositoryPort>();
        mockPerformanceReviewsPort = mock<DeferredExecutionPerformanceReviewsPort>();

        consumer = new PerformanceReviewQueueConsumer(
            mockQueuePort,
            mockPromisesPort,
            mockPerformanceReviewsPort,
            mockDlqRepository,
            mockLogger,
        ) as PerformanceReviewQueueConsumerTestable;
    });

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

    describe('invokeQueueMessageHandlers', () => {
        const knownTypes = [
            QueueMessageTypes.USER_ITEMS_CHANGE,
            QueueMessageTypes.USER_CREATION,
            QueueMessageTypes.USER_DELETION,
            QueueMessageTypes.SEGMENTATION_ITEM_USERS_CHANGE,
            QueueMessageTypes.SEGMENTATION_ITEM_DELETION,
        ] as const;

        it.each(knownTypes)('should route %s message to performanceReviewsPort.handleMessage', async (messageType) => {
            const message = {
                messageType,
                messageBody: { instanceId, userId } as any,
                receiptHandle,
                messageId,
            } as QueueMessageType<typeof messageType>;

            mockPerformanceReviewsPort.handleMessage.mockResolvedValue(undefined);

            await consumer.invokeQueueMessageHandlers(message);

            expect(mockPerformanceReviewsPort.handleMessage).toHaveBeenCalledWith(message);
            expect(mockQueuePort.delete).not.toHaveBeenCalled();
            expect(mockLogger.warn).not.toHaveBeenCalled();
        });

        it('should delete from main queue and warn for unknown messageType', async () => {
            const message = {
                messageType: 'UNKNOWN_TYPE' as QueueMessageTypes,
                messageBody: {} as any,
                receiptHandle,
                messageId,
            };

            mockQueuePort.delete.mockResolvedValue(undefined as any);

            await consumer.invokeQueueMessageHandlers(message);

            expect(mockQueuePort.delete).toHaveBeenCalledWith(
                'http://localhost:4566/000000000000/performance-review-queue',
                receiptHandle,
            );
            expect(mockLogger.warn).toHaveBeenCalledWith(
                message,
                expect.stringContaining('[PerformanceReviewQueueConsumer] Removing message from queue'),
            );
            expect(mockPerformanceReviewsPort.handleMessage).not.toHaveBeenCalled();
        });
    });

    describe('invokeDlqMessageHandlers', () => {
        const knownTypes = [
            QueueMessageTypes.USER_ITEMS_CHANGE,
            QueueMessageTypes.USER_CREATION,
            QueueMessageTypes.USER_DELETION,
            QueueMessageTypes.SEGMENTATION_ITEM_USERS_CHANGE,
            QueueMessageTypes.SEGMENTATION_ITEM_DELETION,
        ] as const;

        it.each(knownTypes)('should route %s message to same handler (retry path)', async (messageType) => {
            const message = {
                messageType,
                messageBody: { instanceId, userId } as any,
                receiptHandle,
                messageId,
            } as QueueMessageType<typeof messageType>;

            mockPerformanceReviewsPort.handleMessage.mockResolvedValue(undefined);

            await consumer.invokeDlqMessageHandlers(message);

            expect(mockPerformanceReviewsPort.handleMessage).toHaveBeenCalledWith(message);
            expect(mockQueuePort.delete).not.toHaveBeenCalled();
            expect(mockLogger.warn).not.toHaveBeenCalled();
        });

        it('should delete from DLQ URL (not main queue) and warn for unknown messageType', async () => {
            const message = {
                messageType: 'UNKNOWN_TYPE' as QueueMessageTypes,
                messageBody: {} as any,
                receiptHandle,
                messageId,
            };

            mockQueuePort.delete.mockResolvedValue(undefined as any);

            await consumer.invokeDlqMessageHandlers(message);

            expect(mockQueuePort.delete).toHaveBeenCalledWith(
                'http://localhost:4566/000000000000/performance-review-dlq',
                receiptHandle,
            );
            expect(mockLogger.warn).toHaveBeenCalledWith(
                message,
                expect.stringContaining('[PerformanceReviewQueueConsumer] Removing message from DLQ'),
            );
            expect(mockPerformanceReviewsPort.handleMessage).not.toHaveBeenCalled();
        });
    });
});
