const assert = require('assert/strict');
const EmailEventProcessor = require('../../../../../core/server/services/email-service/EmailEventProcessor');
const {createDb, createPrometheusClient} = require('./utils');
const sinon = require('sinon');

describe('Email Event Processor', function () {
    let eventProcessor;
    let eventStorage;
    let db;
    let domainEvents;
    let prometheusClient;
    beforeEach(function () {
        db = createDb({first: {
            emailId: 'fetched-email-id',
            member_id: 'member-id',
            id: 'email-recipient-id'
        }});
        prometheusClient = createPrometheusClient();
        domainEvents = {
            dispatch: sinon.stub()
        };

        eventStorage = {
            handleDelivered: sinon.stub(),
            handleOpened: sinon.stub(),
            handlePermanentFailed: sinon.stub(),
            handleTemporaryFailed: sinon.stub(),
            handleComplained: sinon.stub(),
            handleUnsubscribed: sinon.stub()
        };

        eventProcessor = new EmailEventProcessor({
            db,
            domainEvents,
            eventStorage,
            prometheusClient
        });
    });

    afterEach(function () {
        sinon.restore();
    });

    describe('getEmailId', function () {
        let reuseProcessor;

        it('Fetches from database', async function () {
            const emailId = await eventProcessor.getEmailId('provider-id');
            assert.equal(emailId, 'fetched-email-id');
            reuseProcessor = eventProcessor;
        });
        it('Returns from memory', async function () {
            sinon.stub(db, 'first').rejects('Should not be called');
            const emailId = await reuseProcessor.getEmailId('provider-id');
            assert.equal(emailId, 'fetched-email-id');
        });
    });

    describe('getRecipient', function () {
        it('Returns undefined if both providerId and emailId are missing', async function () {
            const recipient = await eventProcessor.getRecipient({});
            assert.equal(recipient, undefined);
        });

        it('Uses emailId to query recipient', async function () {
            const recipient = await eventProcessor.getRecipient({emailId: 'my-id', email: 'example@example.com'});
            assert.deepEqual(recipient, {
                emailRecipientId: 'email-recipient-id',
                memberId: 'member-id',
                emailId: 'my-id'
            });
        });

        it('Uses providerId to query recipient', async function () {
            const recipient = await eventProcessor.getRecipient({providerId: 'provider-id', email: 'example@example.com'});
            assert.deepEqual(recipient, {
                emailRecipientId: 'email-recipient-id',
                memberId: 'member-id',
                emailId: 'fetched-email-id'
            });
        });

        it('Returns undefined if no email found for provider', async function () {
            sinon.stub(db, 'first').resolves(null);
            const recipient = await eventProcessor.getRecipient({providerId: 'provider-id', email: 'example@example.com'});
            assert.equal(recipient, undefined);
        });

        it('Returns undefined if no recipient found for email', async function () {
            sinon.stub(db, 'first').resolves(null);
            const recipient = await eventProcessor.getRecipient({emailId: 'email-id', email: 'example@example.com'});
            assert.equal(recipient, undefined);
        });
    });

    describe('handle events', function () {
        it('handleDelivered', async function () {
            const recipient = await eventProcessor.handleDelivered({emailId: 'email-id', email: 'example@example.com'}, new Date());
            assert.deepEqual(recipient, {
                emailRecipientId: 'email-recipient-id',
                memberId: 'member-id',
                emailId: 'email-id'
            });
            assert.equal(eventStorage.handleDelivered.callCount, 1);
            const event = eventStorage.handleDelivered.firstCall.args[0];
            assert.equal(event.email, 'example@example.com');
            assert.equal(event.constructor.name, 'EmailDeliveredEvent');
        });

        it('handleOpened', async function () {
            const recipient = await eventProcessor.handleOpened({emailId: 'email-id', email: 'example@example.com'}, new Date());
            assert.deepEqual(recipient, {
                emailRecipientId: 'email-recipient-id',
                memberId: 'member-id',
                emailId: 'email-id'
            });
            assert.equal(eventStorage.handleOpened.callCount, 1);
            const event = eventStorage.handleOpened.firstCall.args[0];
            assert.equal(event.email, 'example@example.com');
            assert.equal(event.constructor.name, 'EmailOpenedEvent');
        });

        it('handleTemporaryFailed', async function () {
            const recipient = await eventProcessor.handleTemporaryFailed({emailId: 'email-id', email: 'example@example.com'}, new Date());
            assert.deepEqual(recipient, {
                emailRecipientId: 'email-recipient-id',
                memberId: 'member-id',
                emailId: 'email-id'
            });
            assert.equal(eventStorage.handleTemporaryFailed.callCount, 1);
            const event = eventStorage.handleTemporaryFailed.firstCall.args[0];
            assert.equal(event.email, 'example@example.com');
            assert.equal(event.constructor.name, 'EmailTemporaryBouncedEvent');
        });

        it('handlePermanentFailed', async function () {
            const recipient = await eventProcessor.handlePermanentFailed({emailId: 'email-id', email: 'example@example.com'}, new Date());
            assert.deepEqual(recipient, {
                emailRecipientId: 'email-recipient-id',
                memberId: 'member-id',
                emailId: 'email-id'
            });
            assert.equal(eventStorage.handlePermanentFailed.callCount, 1);
            const event = eventStorage.handlePermanentFailed.firstCall.args[0];
            assert.equal(event.email, 'example@example.com');
            assert.equal(event.constructor.name, 'EmailBouncedEvent');
        });

        it('handleUnsubscribed', async function () {
            const recipient = await eventProcessor.handleUnsubscribed({emailId: 'email-id', email: 'example@example.com'}, new Date());
            assert.deepEqual(recipient, {
                emailRecipientId: 'email-recipient-id',
                memberId: 'member-id',
                emailId: 'email-id'
            });
            assert.equal(eventStorage.handleUnsubscribed.callCount, 1);
            const event = eventStorage.handleUnsubscribed.firstCall.args[0];
            assert.equal(event.email, 'example@example.com');
            assert.equal(event.constructor.name, 'EmailUnsubscribedEvent');
        });

        it('handleComplained', async function () {
            const recipient = await eventProcessor.handleComplained({emailId: 'email-id', email: 'example@example.com'}, new Date());
            assert.deepEqual(recipient, {
                emailRecipientId: 'email-recipient-id',
                memberId: 'member-id',
                emailId: 'email-id'
            });
            assert.equal(eventStorage.handleComplained.callCount, 1);
            const event = eventStorage.handleComplained.firstCall.args[0];
            assert.equal(event.email, 'example@example.com');
            assert.equal(event.constructor.name, 'SpamComplaintEvent');
        });
    });

    describe('recordEventProcessed', function () {
        it('records the event processed metric', function () {
            const incStub = sinon.stub();
            prometheusClient = createPrometheusClient({incStub});
            eventProcessor = new EmailEventProcessor({
                db,
                domainEvents,
                eventStorage,
                prometheusClient
            });
            eventProcessor.recordEventProcessed('delivered');
            assert(incStub.calledOnce);
        });

        it('does not throw if recording the event metric fails', function () {
            prometheusClient = createPrometheusClient({incStub: sinon.stub().throws()});
            eventProcessor = new EmailEventProcessor({
                db,
                domainEvents,
                eventStorage,
                prometheusClient
            });
            assert.doesNotThrow(() => eventProcessor.recordEventProcessed('delivered'));
        });
    });
});
