const assert = require('node:assert/strict');
const sinon = require('sinon');
const testUtils = require('../../utils');
const models = require('../../../core/server/models/index');

const next_post = require('../../../core/frontend/helpers/prev_post');

async function createPost(data) {
    const post = testUtils.DataGenerator.forKnex.createPost(data);
    await models.Post.add(post, {context: {internal: true}});
    return post;
}

function buildMember(status, products = []) {
    return {
        uuid: '1234',
        email: 'test@example.com',
        name: 'John Doe',
        firstname: 'John',
        avatar_image: null,
        subscriptions: [],
        paid: status !== 'free',
        status: status,
        products
    };
}

describe('e2e {{#next_post}} helper', function () {
    let fn;
    let inverse;
    let publicPost, membersPost, paidPost, basicTierPost, publicPost2;

    before(async function () {
        await testUtils.startGhost({
            backend: true,
            frontend: false
        });

        publicPost = await createPost({
            slug: 'free-to-see',
            visibility: 'public',
            published_at: new Date(2020, 0, 1) // here to ensure sorting is not modified
        });

        membersPost = await createPost({
            slug: 'members-post',
            visibility: 'members',
            published_at: new Date(2020, 0, 2) // here to ensure sorting is not modified
        });

        paidPost = await createPost({
            slug: 'paid-to-see',
            visibility: 'paid',
            published_at: new Date(2020, 0, 3) // here to ensure sorting is not modified
        });

        const defaultTier = await models.Product.findOne({slug: 'default-product'});

        basicTierPost = await createPost({
            slug: 'tiers-post',
            visibility: 'tiers',
            tiers: [{
                id: defaultTier.get('id')
            }],
            published_at: new Date(2020, 0, 4) // here to ensure sorting is not modified
        });

        publicPost2 = await createPost({
            slug: 'free-to-see',
            visibility: 'public',
            published_at: new Date(2020, 0, 5) // here to ensure sorting is not modified
        });
    });

    // Assert fixtures are correct
    it('has valid fixtures', function () {
        assert.equal(publicPost.visibility, 'public');
        assert.equal(membersPost.visibility, 'members');
        assert.equal(paidPost.visibility, 'paid');
        assert.equal(basicTierPost.visibility, 'tiers');
        assert.equal(publicPost2.visibility, 'public');
    });

    beforeEach(function () {
        fn = sinon.spy();
        inverse = sinon.spy();
    });

    describe('{{access}} property', function () {
        describe('not authenticated member', function () {
            const member = null;
            const locals = {
                root: {
                    context: ['post']
                },
                member
            };
            let optionsData;

            beforeEach(function () {
                optionsData = {name: 'next_post', data: locals, fn, inverse};
            });

            it('next members post', async function () {
                await next_post
                    .call(publicPost, optionsData);

                sinon.assert.calledWith(fn.firstCall, sinon.match({id: membersPost.id, access: false}));
            });

            it('next paid post', async function () {
                await next_post
                    .call(membersPost, optionsData);

                sinon.assert.calledWith(fn.firstCall, sinon.match({id: paidPost.id, access: false}));
            });

            it('next tiers post', async function () {
                await next_post
                    .call(paidPost, optionsData);

                sinon.assert.calledWith(fn.firstCall, sinon.match({id: basicTierPost.id, access: false}));
            });

            it('next public post', async function () {
                await next_post
                    .call(basicTierPost, optionsData);

                sinon.assert.calledWith(fn.firstCall, sinon.match({id: publicPost2.id, access: true}));
            });
        });

        describe('free member', function () {
            const member = buildMember('free');
            const locals = {
                root: {
                    context: ['post']
                },
                member
            };
            let optionsData;

            beforeEach(function () {
                optionsData = {name: 'next_post', data: locals, fn, inverse};
            });

            it('next members post', async function () {
                await next_post
                    .call(publicPost, optionsData);

                sinon.assert.calledWith(fn.firstCall, sinon.match({id: membersPost.id, access: true}));
            });

            it('next paid post', async function () {
                await next_post
                    .call(membersPost, optionsData);

                sinon.assert.calledWith(fn.firstCall, sinon.match({id: paidPost.id, access: false}));
            });

            it('next tiers post', async function () {
                await next_post
                    .call(paidPost, optionsData);

                sinon.assert.calledWith(fn.firstCall, sinon.match({id: basicTierPost.id, access: false}));
            });

            it('next public post', async function () {
                await next_post
                    .call(basicTierPost, optionsData);

                sinon.assert.calledWith(fn.firstCall, sinon.match({id: publicPost2.id, access: true}));
            });
        });

        describe('paid member', function () {
            const member = buildMember('paid');
            const locals = {
                root: {
                    context: ['post']
                },
                member
            };
            let optionsData;

            beforeEach(function () {
                optionsData = {name: 'next_post', data: locals, fn, inverse};
            });

            it('next members post', async function () {
                await next_post
                    .call(publicPost, optionsData);

                sinon.assert.calledWith(fn.firstCall, sinon.match({id: membersPost.id, access: true}));
            });

            it('next paid post', async function () {
                await next_post
                    .call(membersPost, optionsData);

                sinon.assert.calledWith(fn.firstCall, sinon.match({id: paidPost.id, access: true}));
            });

            it('next tiers post', async function () {
                await next_post
                    .call(paidPost, optionsData);

                sinon.assert.calledWith(fn.firstCall, sinon.match({id: basicTierPost.id, access: false}));
            });

            it('next public post', async function () {
                await next_post
                    .call(basicTierPost, optionsData);

                sinon.assert.calledWith(fn.firstCall, sinon.match({id: publicPost2.id, access: true}));
            });
        });

        describe('tiers member', function () {
            const member = buildMember('tiers', [{
                name: 'Default Product',
                slug: 'default-product',
                type: 'paid',
                active: true
            }]);

            const locals = {
                root: {
                    context: ['post']
                },
                member
            };
            let optionsData;

            beforeEach(function () {
                optionsData = {name: 'next_post', data: locals, fn, inverse};
            });

            it('next members post', async function () {
                await next_post
                    .call(publicPost, optionsData);

                sinon.assert.calledWith(fn.firstCall, sinon.match({id: membersPost.id, access: true}));
            });

            it('next paid post', async function () {
                await next_post
                    .call(membersPost, optionsData);

                sinon.assert.calledWith(fn.firstCall, sinon.match({id: paidPost.id, access: true}));
            });

            it('next tiers post', async function () {
                await next_post
                    .call(paidPost, optionsData);

                sinon.assert.calledWith(fn.firstCall, sinon.match({id: basicTierPost.id, access: true}));
            });

            it('next public post', async function () {
                await next_post
                    .call(basicTierPost, optionsData);

                sinon.assert.calledWith(fn.firstCall, sinon.match({id: publicPost2.id, access: true}));
            });
        });

        describe('tiers member with different product', function () {
            const member = buildMember('tiers', [{
                name: 'Default Product',
                slug: 'pro-product',
                type: 'paid',
                active: true
            }]);

            const locals = {
                root: {
                    context: ['post']
                },
                member
            };
            let optionsData;

            beforeEach(function () {
                optionsData = {name: 'next_post', data: locals, fn, inverse};
            });

            it('next members post', async function () {
                await next_post
                    .call(publicPost, optionsData);

                sinon.assert.calledWith(fn.firstCall, sinon.match({id: membersPost.id, access: true}));
            });

            it('next paid post', async function () {
                await next_post
                    .call(membersPost, optionsData);

                sinon.assert.calledWith(fn.firstCall, sinon.match({id: paidPost.id, access: true}));
            });

            it('next tiers post', async function () {
                await next_post
                    .call(paidPost, optionsData);

                sinon.assert.calledWith(fn.firstCall, sinon.match({id: basicTierPost.id, access: false}));
            });

            it('next public post', async function () {
                await next_post
                    .call(basicTierPost, optionsData);

                sinon.assert.calledWith(fn.firstCall, sinon.match({id: publicPost2.id, access: true}));
            });
        });
    });
});
