const {agentProvider, fixtureManager, matchers} = require('../../utils/e2e-framework');
const {anyContentVersion, anyEtag, anyObjectId, anyLocationFor, anyErrorId, anyISODateTime} = matchers;
const should = require('should');
const models = require('../../../core/server/models');
const sinon = require('sinon');
const logging = require('@tryghost/logging');

let agent;

async function getPaidProduct() {
    return await models.Product.findOne({type: 'paid'});
}

async function getFreeProduct() {
    return await models.Product.findOne({type: 'free'});
}

describe('Offers API', function () {
    let defaultTier;
    let savedOffer;
    let trialOffer;

    before(async function () {
        agent = await agentProvider.getAdminAPIAgent();
        await fixtureManager.init();
        await agent.loginAsOwner();
        defaultTier = await getPaidProduct();
    });

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

    it('Has no initial offers', async function () {
        await agent
            .get(`offers/`)
            .expectStatus(200)
            .matchHeaderSnapshot({
                'content-version': anyContentVersion,
                etag: anyEtag
            })
            .matchBodySnapshot();
    });

    it('Can add a new offer', async function () {
        const newOffer = {
            name: 'Black Friday',
            code: 'black-friday',
            display_title: 'Black Friday Sale!',
            display_description: '10% off on yearly plan',
            type: 'percent',
            cadence: 'year',
            amount: 12,
            duration: 'once',
            duration_in_months: null,
            currency_restriction: false,
            currency: null,
            status: 'active',
            redemption_count: 0,
            redemption_type: 'signup',
            tier: {
                id: defaultTier.id
            }
        };

        const {body} = await agent
            .post(`offers/`)
            .body({offers: [newOffer]})
            .expectStatus(200)
            .matchHeaderSnapshot({
                'content-version': anyContentVersion,
                etag: anyEtag,
                location: anyLocationFor('offers')
            })
            .matchBodySnapshot({
                offers: [{
                    id: anyObjectId,
                    tier: {
                        id: anyObjectId
                    },
                    created_at: anyISODateTime
                }]
            });
        savedOffer = body.offers[0];
    });

    it('Can add a new offer with minimal fields', async function () {
        const newOffer = {
            name: 'Easter Sales',
            code: 'easter',
            cadence: 'month',
            amount: 50,
            duration: 'once',
            type: 'percent',
            tier: {
                id: defaultTier.id
            }
        };

        await agent
            .post(`offers/`)
            .body({offers: [newOffer]})
            .expectStatus(200)
            .matchHeaderSnapshot({
                'content-version': anyContentVersion,
                etag: anyEtag,
                location: anyLocationFor('offers')
            })
            .matchBodySnapshot({
                offers: [{
                    id: anyObjectId,
                    tier: {
                        id: anyObjectId
                    },
                    created_at: anyISODateTime
                }]
            });
    });

    it('Slugifies offer codes', async function () {
        const newOffer = {
            name: 'Summer Sale',
            code: 'Summer sale',
            cadence: 'year',
            amount: 20,
            duration: 'once',
            type: 'percent',
            tier: {
                id: defaultTier.id
            }
        };

        await agent
            .post(`offers/`)
            .body({offers: [newOffer]})
            .expectStatus(200)
            .matchHeaderSnapshot({
                'content-version': anyContentVersion,
                etag: anyEtag,
                location: anyLocationFor('offers')
            })
            .matchBodySnapshot({
                offers: [{
                    id: anyObjectId,
                    tier: {
                        id: anyObjectId
                    },
                    created_at: anyISODateTime
                }]
            })
            .expect(({body}) => {
                body.offers[0].code.should.eql('summer-sale');
            });
    });

    it('Can add a fixed offer', async function () {
        const newOffer = {
            name: 'Fourth of July Sales',
            code: '4th',
            cadence: 'year',
            amount: 100,
            duration: 'once',
            type: 'fixed',
            currency: 'USD',
            tier: {
                id: defaultTier.id
            }
        };

        await agent
            .post(`offers/`)
            .body({offers: [newOffer]})
            .expectStatus(200)
            .matchHeaderSnapshot({
                'content-version': anyContentVersion,
                etag: anyEtag,
                location: anyLocationFor('offers')
            })
            .matchBodySnapshot({
                offers: [{
                    id: anyObjectId,
                    tier: {
                        id: anyObjectId
                    },
                    created_at: anyISODateTime
                }]
            });
    });

    it('Can add a trial offer', async function () {
        const newOffer = {
            name: 'Fourth of July Sales trial',
            code: '4th-trial',
            cadence: 'year',
            amount: 20,
            duration: 'trial',
            type: 'trial',
            currency: 'USD',
            tier: {
                id: defaultTier.id
            }
        };

        const {body} = await agent
            .post(`offers/`)
            .body({offers: [newOffer]})
            .expectStatus(200)
            .matchHeaderSnapshot({
                'content-version': anyContentVersion,
                etag: anyEtag,
                location: anyLocationFor('offers')
            })
            .matchBodySnapshot({
                offers: [{
                    id: anyObjectId,
                    tier: {
                        id: anyObjectId
                    },
                    created_at: anyISODateTime
                }]
            });
        trialOffer = body.offers[0];
    });

    it('Cannot create offer with same code', async function () {
        sinon.stub(logging, 'error');

        const newOffer = {
            name: 'Fourth of July',
            code: '4th',
            cadence: 'year',
            amount: 200,
            duration: 'once',
            type: 'fixed',
            currency: 'USD',
            tier: {
                id: defaultTier.id
            }
        };

        await agent
            .post(`offers/`)
            .body({offers: [newOffer]})
            .expectStatus(400)
            .matchHeaderSnapshot({
                'content-version': anyContentVersion,
                etag: anyEtag
            })
            .matchBodySnapshot({
                errors: [{
                    id: anyErrorId
                }]
            });
    });

    it('Cannot create offer with same slugified code', async function () {
        sinon.stub(logging, 'error');

        const newOffer = {
            name: 'Another Black Friday Sale',
            code: 'black friday',
            cadence: 'year',
            amount: 200,
            duration: 'once',
            type: 'fixed',
            currency: 'USD',
            tier: {
                id: defaultTier.id
            }
        };

        await agent
            .post(`offers/`)
            .body({offers: [newOffer]})
            .expectStatus(400)
            .matchHeaderSnapshot({
                'content-version': anyContentVersion,
                etag: anyEtag
            })
            .matchBodySnapshot({
                errors: [{
                    id: anyErrorId
                }]
            });
    });

    it('Cannot create offer with same name', async function () {
        sinon.stub(logging, 'error');

        const newOffer = {
            name: 'Fourth of July Sales',
            code: 'july4',
            cadence: 'year',
            amount: 150,
            duration: 'once',
            type: 'fixed',
            currency: 'USD',
            tier: {
                id: defaultTier.id
            }
        };

        await agent
            .post(`offers/`)
            .body({offers: [newOffer]})
            .expectStatus(400)
            .matchHeaderSnapshot({
                'content-version': anyContentVersion,
                etag: anyEtag
            })
            .matchBodySnapshot({
                errors: [{
                    id: anyErrorId
                }]
            });
    });

    it('Can browse', async function () {
        await agent
            .get(`offers/`)
            .expectStatus(200)
            .matchHeaderSnapshot({
                'content-version': anyContentVersion,
                etag: anyEtag
            })
            .matchBodySnapshot({
                offers: new Array(5).fill({
                    id: anyObjectId,
                    tier: {
                        id: anyObjectId
                    },
                    created_at: anyISODateTime
                })
            });
    });

    it('Can get a single offer', async function () {
        await agent
            .get(`offers/${savedOffer.id}/`)
            .expectStatus(200)
            .matchHeaderSnapshot({
                'content-version': anyContentVersion,
                etag: anyEtag
            })
            .matchBodySnapshot({
                offers: new Array(1).fill({
                    id: anyObjectId,
                    tier: {
                        id: anyObjectId
                    },
                    created_at: anyISODateTime
                })
            });
    });

    it('Can get a trial offer', async function () {
        await agent
            .get(`offers/${trialOffer.id}/`)
            .expectStatus(200)
            .matchHeaderSnapshot({
                'content-version': anyContentVersion,
                etag: anyEtag
            })
            .matchBodySnapshot({
                offers: new Array(1).fill({
                    id: anyObjectId,
                    type: 'trial',
                    duration: 'trial',
                    tier: {
                        id: anyObjectId
                    },
                    created_at: anyISODateTime
                })
            });
    });

    it('Can edit an offer', async function () {
        // We can change all fields except discount related fields
        let updatedOffer = {
            name: 'Cyber Monday',
            code: 'cyber monday',
            display_title: 'Cyber Monday Sale!',
            display_description: '10% off on yearly plan, only today'
        };

        await agent
            .put(`offers/${savedOffer.id}/`)
            .body({offers: [updatedOffer]})
            .expectStatus(200)
            .matchHeaderSnapshot({
                'content-version': anyContentVersion,
                etag: anyEtag
            })
            .matchBodySnapshot({
                offers: new Array(1).fill({
                    id: anyObjectId,
                    tier: {
                        id: anyObjectId
                    },
                    created_at: anyISODateTime
                })
            })
            .expect(({body}) => {
                // Test if all the changes were applied, and that the code has been slugified
                body.offers[0].should.match({...updatedOffer, code: 'cyber-monday'});
            });
    });

    it('Cannot update offer code to one that exists', async function () {
        sinon.stub(logging, 'error');

        // We can change all fields except discount related fields
        let updatedOffer = {
            code: '4th'
        };

        await agent
            .put(`offers/${savedOffer.id}/`)
            .body({offers: [updatedOffer]})
            .expectStatus(400)
            .matchHeaderSnapshot({
                'content-version': anyContentVersion,
                etag: anyEtag
            })
            .matchBodySnapshot({
                errors: new Array(1).fill({
                    id: anyErrorId
                })
            });
    });

    it('Cannot update offer code to one that exists after it is slugified', async function () {
        sinon.stub(logging, 'error');

        // We can change all fields except discount related fields
        let updatedOffer = {
            code: 'Summer sale'
        };

        await agent
            .put(`offers/${savedOffer.id}/`)
            .body({offers: [updatedOffer]})
            .expectStatus(400)
            .matchHeaderSnapshot({
                'content-version': anyContentVersion,
                etag: anyEtag
            })
            .matchBodySnapshot({
                errors: new Array(1).fill({
                    id: anyErrorId
                })
            });
    });

    it('Cannot update offer name to one that exists', async function () {
        sinon.stub(logging, 'error');

        // We can change all fields except discount related fields
        let updatedOffer = {
            name: 'Easter Sales'
        };

        await agent
            .put(`offers/${savedOffer.id}/`)
            .body({offers: [updatedOffer]})
            .expectStatus(400)
            .matchHeaderSnapshot({
                'content-version': anyContentVersion,
                etag: anyEtag
            })
            .matchBodySnapshot({
                errors: new Array(1).fill({
                    id: anyErrorId
                })
            });
    });

    it('Can archive an offer', async function () {
        // We can change all fields except discount related fields
        let updatedOffer = {
            status: 'archived'
        };

        await agent
            .put(`offers/${savedOffer.id}/`)
            .body({offers: [updatedOffer]})
            .expectStatus(200)
            .matchHeaderSnapshot({
                'content-version': anyContentVersion,
                etag: anyEtag
            })
            .matchBodySnapshot({
                offers: new Array(1).fill({
                    id: anyObjectId,
                    tier: {
                        id: anyObjectId
                    },
                    created_at: anyISODateTime
                })
            })
            .expect(({body}) => {
                body.offers[0].should.match(updatedOffer);
            });
    });

    it('Can browse archived', async function () {
        const filter = encodeURIComponent(`status:archived`);
        await agent
            .get(`offers/?filter=${filter}`)
            .expectStatus(200)
            .matchHeaderSnapshot({
                'content-version': anyContentVersion,
                etag: anyEtag
            })
            .matchBodySnapshot({
                offers: new Array(1).fill({
                    id: anyObjectId,
                    tier: {
                        id: anyObjectId
                    },
                    created_at: anyISODateTime
                })
            });
    });

    it('Can browse active', async function () {
        const filter = encodeURIComponent(`status:active`);
        await agent
            .get(`offers/?filter=${filter}`)
            .expectStatus(200)
            .matchHeaderSnapshot({
                'content-version': anyContentVersion,
                etag: anyEtag
            })
            .matchBodySnapshot({
                offers: new Array(4).fill({
                    id: anyObjectId,
                    tier: {
                        id: anyObjectId
                    },
                    created_at: anyISODateTime
                })
            });
    });

    it('Cannot update offer cadence', async function () {
        // We can change all fields except discount related fields
        let updatedOffer = {
            cadence: 'month'
        };

        await agent
            .put(`offers/${savedOffer.id}/`)
            .body({offers: [updatedOffer]})
            .expectStatus(200)
            .matchHeaderSnapshot({
                'content-version': anyContentVersion,
                etag: anyEtag
            })
            .matchBodySnapshot({
                offers: new Array(1).fill({
                    id: anyObjectId,
                    tier: {
                        id: anyObjectId
                    },
                    created_at: anyISODateTime
                })
            })
            .expect(({body}) => {
                body.offers[0].cadence.should.eql('year');
            });
    });

    it('Cannot update offer amount', async function () {
        // We can change all fields except discount related fields
        let updatedOffer = {
            amount: 20
        };

        // No validation errors are thrown at the moment
        await agent
            .put(`offers/${savedOffer.id}/`)
            .body({offers: [updatedOffer]})
            .expectStatus(200)
            .matchHeaderSnapshot({
                'content-version': anyContentVersion,
                etag: anyEtag
            })
            .matchBodySnapshot({
                offers: new Array(1).fill({
                    id: anyObjectId,
                    tier: {
                        id: anyObjectId
                    },
                    created_at: anyISODateTime
                })
            })
            .expect(({body}) => {
                body.offers[0].amount.should.eql(12);
            });
    });

    it('Cannot update offer tier', async function () {
        // We can change all fields except discount related fields
        const freeTier = await getFreeProduct();
        let updatedOffer = {
            tier: {
                id: freeTier.id
            }
        };

        // No validation errors are thrown at the moment
        await agent
            .put(`offers/${savedOffer.id}/`)
            .body({offers: [updatedOffer]})
            .expectStatus(200)
            .matchHeaderSnapshot({
                'content-version': anyContentVersion,
                etag: anyEtag
            })
            .matchBodySnapshot({
                offers: new Array(1).fill({
                    id: anyObjectId,
                    tier: {
                        id: anyObjectId
                    },
                    created_at: anyISODateTime
                })
            })
            .expect(({body}) => {
                body.offers[0].tier.id.should.eql(defaultTier.id);
            });
    });
});
