const should = require('should');
const ObjectID = require('bson-objectid').default;
const errors = require('../../../../lib/domain/errors');
const Offer = require('../../../../lib/domain/models/Offer');
const OfferName = require('../../../../lib/domain/models/OfferName');
const OfferCode = require('../../../../lib/domain/models/OfferCode');

function createUniqueChecker(dupe) {
    return {
        async isUniqueCode(code) {
            return code.value !== dupe;
        },
        async isUniqueName(name) {
            return name.value !== dupe;
        }
    };
}

const mockUniqueChecker = createUniqueChecker('dupe');

describe('Offer', function () {
    describe('Offer#create factory', function () {
        it('Creates a valid instance of an Offer', async function () {
            const offer = await Offer.create({
                name: 'My Offer',
                code: 'offer-code',
                display_title: 'My Offer Title',
                display_description: 'My Offer Description',
                cadence: 'month',
                type: 'fixed',
                amount: 1000,
                duration: 'forever',
                currency: 'USD',
                tier: {
                    id: ObjectID()
                }
            }, mockUniqueChecker);
            should.ok(
                offer instanceof Offer,
                'Offer.create should return an instance of Offer'
            );
        });

        it('Creates a valid instance of a trial Offer', async function () {
            const offer = await Offer.create({
                name: 'My Trial Offer',
                code: 'offer-code-trial',
                display_title: 'My Offer Title',
                display_description: 'My Offer Description',
                cadence: 'month',
                type: 'trial',
                amount: 10,
                duration: 'trial',
                currency: 'USD',
                tier: {
                    id: ObjectID()
                }
            }, mockUniqueChecker);
            should.ok(
                offer instanceof Offer,
                'Offer.create should return an instance of Offer'
            );
        });

        it('Throws an error if the duration for trial offer is not right', async function () {
            await Offer.create({
                name: 'My Trial Offer',
                code: 'trial-test',
                display_title: 'My Offer Title',
                display_description: 'My Offer Description',
                cadence: 'month',
                type: 'trial',
                amount: 10,
                duration: 'forever',
                currency: 'USD',
                tier: {
                    id: ObjectID()
                }
            }, mockUniqueChecker).then(() => {
                should.fail('Expected an error');
            }, (err) => {
                should.ok(err);
            });
        });

        it('Throws an error if the code is not unique', async function () {
            await Offer.create({
                name: 'My Offer',
                code: 'dupe',
                display_title: 'My Offer Title',
                display_description: 'My Offer Description',
                cadence: 'month',
                type: 'fixed',
                amount: 1000,
                duration: 'forever',
                currency: 'USD',
                tier: {
                    id: ObjectID()
                }
            }, mockUniqueChecker).then(() => {
                should.fail('Expected an error');
            }, (err) => {
                should.ok(err);
            });
        });

        it('Throws an error if the name is not unique', async function () {
            await Offer.create({
                name: 'dupe',
                code: 'offer-code',
                display_title: 'My Offer Title',
                display_description: 'My Offer Description',
                cadence: 'month',
                type: 'fixed',
                amount: 1000,
                duration: 'forever',
                currency: 'USD',
                tier: {
                    id: ObjectID()
                }
            }, mockUniqueChecker).then(() => {
                should.fail('Expected an error');
            }, (err) => {
                should.ok(err);
            });
        });

        it('Wraps the input values in value objects', async function () {
            const data = {
                name: 'My Offer',
                code: 'offer-code',
                display_title: 'My Offer Title',
                display_description: 'My Offer Description',
                cadence: 'month',
                type: 'fixed',
                amount: 1000,
                duration: 'forever',
                currency: 'USD',
                tier: {
                    id: ObjectID()
                }
            };

            const offer = await Offer.create(data, mockUniqueChecker);

            should.ok(offer.name.equals(OfferName.create(data.name)));
        });

        it('Errors if the repeating duration is applied to the year cadence', async function () {
            const data = {
                name: 'My Offer',
                code: 'offer-code',
                display_title: 'My Offer Title',
                display_description: 'My Offer Description',
                cadence: 'year',
                type: 'fixed',
                amount: 1000,
                duration: 'repeating',
                duration_in_months: 12,
                currency: 'USD',
                tier: {
                    id: ObjectID()
                }
            };

            try {
                await Offer.create(data, mockUniqueChecker);
                should.fail();
            } catch (err) {
                should.ok(err instanceof errors.InvalidOfferDuration);
            }
        });

        it('Has a currency of null if the type is percent', async function () {
            const data = {
                name: 'My Offer',
                code: 'offer-code',
                display_title: 'My Offer Title',
                display_description: 'My Offer Description',
                cadence: 'year',
                type: 'percent',
                amount: 20,
                duration: 'once',
                currency: 'USD',
                tier: {
                    id: ObjectID()
                }
            };

            const offer = await Offer.create(data, mockUniqueChecker);

            should.equal(offer.currency, null);
        });

        it('Has a currency of null if the type is trial', async function () {
            const data = {
                name: 'My Trial Offer',
                code: 'offer-code-trial',
                display_title: 'My Offer Title',
                display_description: 'My Offer Description',
                cadence: 'year',
                type: 'trial',
                amount: 20,
                duration: 'trial',
                currency: 'USD',
                tier: {
                    id: ObjectID()
                }
            };

            const offer = await Offer.create(data, mockUniqueChecker);

            should.equal(offer.currency, null);
        });

        it('Can handle ObjectID, string and no id', async function () {
            const data = {
                name: 'My Offer',
                code: 'offer-code',
                display_title: 'My Offer Title',
                display_description: 'My Offer Description',
                cadence: 'year',
                type: 'percent',
                amount: 20,
                duration: 'once',
                currency: 'USD',
                tier: {
                    id: ObjectID()
                }
            };

            await Offer.create({...data, id: ObjectID()}, mockUniqueChecker);
            await Offer.create({...data, id: ObjectID().toHexString()}, mockUniqueChecker);
            await Offer.create({...data, id: undefined}, mockUniqueChecker);
        });

        it('Does not accept a redemptionCount for new Offers', async function () {
            const data = {
                name: 'My Offer',
                code: 'offer-code',
                display_title: 'My Offer Title',
                display_description: 'My Offer Description',
                cadence: 'month',
                type: 'fixed',
                amount: 1000,
                duration: 'repeating',
                duration_in_months: 12,
                currency: 'USD',
                tier: {
                    id: ObjectID()
                }
            };

            await Offer.create(data, mockUniqueChecker);

            await Offer.create({...data, redemptionCount: 2}, mockUniqueChecker).then(() => {
                should.fail('Expected an error');
            }, (err) => {
                should.ok(err);
            });
        });

        it('Sets createdAt for new Offers', async function () {
            const data = {
                name: 'My Offer',
                code: 'offer-code',
                display_title: 'My Offer Title',
                display_description: 'My Offer Description',
                cadence: 'month',
                type: 'fixed',
                amount: 1000,
                duration: 'forever',
                currency: 'USD',
                tier: {
                    id: ObjectID()
                }
            };

            const offer = await Offer.create(data, mockUniqueChecker);

            should.equal(typeof offer.createdAt, 'string');
        });
    });

    describe('#updateCode', function () {
        it('Requires the code to be unique if it has changed', async function () {
            const data = {
                name: 'My Offer',
                code: 'offer-code',
                display_title: 'My Offer Title',
                display_description: 'My Offer Description',
                cadence: 'month',
                type: 'fixed',
                amount: 1000,
                duration: 'repeating',
                duration_in_months: 12,
                currency: 'USD',
                tier: {
                    id: ObjectID()
                }
            };

            const offer = await Offer.create(data, mockUniqueChecker);

            await offer.updateCode(OfferCode.create('dupe'), mockUniqueChecker).then(() => {
                should.fail('Expected an error');
            }, (err) => {
                should.ok(err);
            });

            const offer2 = await Offer.create({...data, code: 'dupe'}, createUniqueChecker());

            await offer2.updateCode(OfferCode.create('dupe'), mockUniqueChecker);
        });

        it('Does not allow code to be changed twice', async function () {
            const data = {
                name: 'My Offer',
                code: 'offer-code',
                display_title: 'My Offer Title',
                display_description: 'My Offer Description',
                cadence: 'month',
                type: 'fixed',
                amount: 1000,
                duration: 'repeating',
                duration_in_months: 12,
                currency: 'USD',
                tier: {
                    id: ObjectID()
                }
            };

            const offer = await Offer.create(data, mockUniqueChecker);

            await offer.updateCode(OfferCode.create('changed'), mockUniqueChecker);
            await offer.updateCode(OfferCode.create('changed-again'), mockUniqueChecker).then(() => {
                should.fail('Expected an error');
            }, (err) => {
                should.ok(err);
            });
        });
    });

    describe('#updateName', function () {
        it('Requires the name to be unique if it has changed', async function () {
            const data = {
                name: 'My Offer',
                code: 'offer-code',
                display_title: 'My Offer Title',
                display_description: 'My Offer Description',
                cadence: 'month',
                type: 'fixed',
                amount: 1000,
                duration: 'repeating',
                duration_in_months: 12,
                currency: 'USD',
                tier: {
                    id: ObjectID()
                }
            };

            const offer = await Offer.create(data, mockUniqueChecker);

            await offer.updateName(OfferName.create('Unique!'), mockUniqueChecker);

            await offer.updateName(OfferName.create('dupe'), mockUniqueChecker).then(() => {
                should.fail('Expected an error');
            }, (err) => {
                should.ok(err);
            });

            const offer2 = await Offer.create({...data, name: 'dupe'}, createUniqueChecker());

            await offer2.updateName(OfferName.create('dupe'), mockUniqueChecker);
        });
    });

    describe('Properties', function () {
        it('Exposes getters for its properties', async function () {
            const data = {
                name: 'My Offer',
                code: 'offer-code',
                display_title: 'My Offer Title',
                display_description: 'My Offer Description',
                cadence: 'month',
                type: 'fixed',
                amount: 1000,
                duration: 'repeating',
                duration_in_months: 12,
                currency: 'USD',
                tier: {
                    id: ObjectID()
                }
            };

            const offer = await Offer.create(data, mockUniqueChecker);

            should.exist(offer.id);
            should.exist(offer.name);
            should.exist(offer.code);
            should.exist(offer.currency);
            should.exist(offer.duration);
            should.exist(offer.status);
            should.exist(offer.redemptionCount);
            should.exist(offer.displayTitle);
            should.exist(offer.displayDescription);
            should.exist(offer.tier);
            should.exist(offer.cadence);
            should.exist(offer.type);
            should.exist(offer.amount);
            should.exist(offer.isNew);
        });
    });
});
