const _ = require('lodash');
const {mobiledocToLexical} = require('@tryghost/kg-converters');
const models = require('../../../core/server/models');
const {agentProvider, fixtureManager, mockManager, matchers} = require('../../utils/e2e-framework');
const {anyArray, anyBoolean, anyContentVersion, anyEtag, anyLocationFor, anyObject, anyObjectId, anyISODateTime, anyString, anyUuid} = matchers;

const tierSnapshot = {
    id: anyObjectId,
    created_at: anyISODateTime,
    updated_at: anyISODateTime
};

const matchPageShallowIncludes = {
    id: anyObjectId,
    uuid: anyUuid,
    comment_id: anyString,
    url: anyString,
    authors: anyArray,
    primary_author: anyObject,
    tags: anyArray,
    primary_tag: anyObject,
    tiers: Array(2).fill(tierSnapshot),
    created_at: anyISODateTime,
    updated_at: anyISODateTime,
    published_at: anyISODateTime,
    show_title_and_feature_image: anyBoolean
};

describe('Pages API', function () {
    let agent;

    before(async function () {
        mockManager.mockLabsEnabled('collectionsCard');
        agent = await agentProvider.getAdminAPIAgent();
        await fixtureManager.init('posts');
        await agent.loginAsOwner();
    });

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

    describe('Read', function () {
        it('Re-renders html when null', async function () {
            // "queue" an existing page for re-render as happens when a published page is updated/destroyed
            const page = await models.Post.findOne({slug: 'static-page-test'});
            // NOTE: re-rendering only occurs for lexical pages
            const lexical = mobiledocToLexical(page.get('mobiledoc'));
            await models.Base.knex.raw('UPDATE posts set html=NULL, mobiledoc=NULL, lexical=? WHERE id=?', [lexical, page.id]);

            await agent
                .get(`/pages/${page.id}/?formats=mobiledoc,lexical,html`)
                .expectStatus(200)
                .matchBodySnapshot({
                    pages: [Object.assign({}, matchPageShallowIncludes)]
                });
        });
    });

    describe('Browse', function () {
        it('Re-renders html when null', async function () {
            // convert inserted pages to lexical and set html=null so we can test re-render
            const pages = await models.Post.where('type', 'page').fetchAll();
            for (const page of pages) {
                if (!page.get('mobiledoc')) {
                    continue;
                }
                const lexical = mobiledocToLexical(page.get('mobiledoc'));
                await models.Base.knex.raw('UPDATE posts set html=NULL, mobiledoc=NULL, lexical=? WHERE id=?', [lexical, page.id]);
            }

            await agent
                .get('/pages/?formats=mobiledoc,lexical,html')
                .expectStatus(200)
                .matchBodySnapshot({
                    pages: Array(pages.length).fill(Object.assign({}, matchPageShallowIncludes))
                });
        });
    });

    describe('Create', function () {
        it('Can create a page with html', async function () {
            const page = {
                title: 'HTML test',
                html: '<p>Testing page creation with html</p>'
            };

            await agent
                .post('/pages/?source=html&formats=mobiledoc,lexical,html')
                .body({pages: [page]})
                .expectStatus(201)
                .matchBodySnapshot({
                    pages: [Object.assign({}, matchPageShallowIncludes, {published_at: null})]
                })
                .matchHeaderSnapshot({
                    'content-version': anyContentVersion,
                    etag: anyEtag,
                    location: anyLocationFor('pages')
                });
        });
    });

    describe('Update', function () {
        it('Can modify show_title_and_feature_image property', async function () {
            const page = {
                title: 'Test Page',
                status: 'draft'
            };

            const {body: pageBody} = await agent
                .post('/pages/?formats=mobiledoc,lexical,html', {
                    headers: {
                        'content-type': 'application/json'
                    }
                })
                .body({pages: [page]})
                .expectStatus(201);

            const [pageResponse] = pageBody.pages;

            await agent
                .put(`/pages/${pageResponse.id}/?formats=mobiledoc,lexical,html`)
                .body({
                    pages: [{
                        id: pageResponse.id,
                        show_title_and_feature_image: false, // default is true
                        updated_at: pageResponse.updated_at // satisfy collision detection
                    }]
                })
                .expectStatus(200)
                .matchBodySnapshot({
                    pages: [Object.assign({}, matchPageShallowIncludes, {
                        published_at: null,
                        show_title_and_feature_image: false
                    })]
                })
                .matchHeaderSnapshot({
                    'content-version': anyContentVersion,
                    etag: anyEtag,
                    'x-cache-invalidate': anyString
                });
        });

        it('Works with latest collection card', async function () {
            const initialLexical = {
                root: {
                    children: [
                        {
                            type: 'collection',
                            version: 1,
                            collection: 'latest',
                            postCount: 3,
                            layout: 'grid',
                            columns: 3,
                            header: 'Latest'
                        }
                    ],
                    direction: null,
                    format: '',
                    indent: 0,
                    type: 'root',
                    version: 1
                }
            };

            const updatedLexical = _.cloneDeep(initialLexical);
            updatedLexical.root.children.push({
                children: [
                    {
                        detail: 0,
                        format: 0,
                        mode: 'normal',
                        style: '',
                        text: 'Testing',
                        type: 'text',
                        version: 1
                    }
                ],
                direction: 'ltr',
                format: '',
                indent: 0,
                type: 'paragraph',
                version: 1
            });

            const page = {
                title: 'Latest Collection Card Test',
                status: 'draft',
                lexical: JSON.stringify(initialLexical)
            };

            const {body: createBody} = await agent
                .post('/pages/?formats=mobiledoc,lexical,html', {
                    headers: {
                        'content-type': 'application/json'
                    }
                })
                .body({pages: [page]})
                .expectStatus(201);

            const [createResponse] = createBody.pages;

            // does not match body snapshot as we mostly only care about the request succeeding.
            // matching body snapshots is tricky because collection cards have dynamic content,
            // most notably the post dates which are always changing.
            await agent
                .put(`/pages/${createResponse.id}/?formats=mobiledoc,lexical,html`)
                .body({
                    pages: [{
                        id: createResponse.id,
                        lexical: JSON.stringify(updatedLexical),
                        updated_at: createResponse.updated_at // satisfy collision detection
                    }]
                })
                .expectStatus(200);
        });

        it('Works with featured collection card', async function () {
            const initialLexical = {
                root: {
                    children: [
                        {
                            type: 'collection',
                            version: 1,
                            collection: 'featured',
                            postCount: 3,
                            layout: 'grid',
                            columns: 3,
                            header: 'Featured'
                        }
                    ],
                    direction: null,
                    format: '',
                    indent: 0,
                    type: 'root',
                    version: 1
                }
            };

            const updatedLexical = _.cloneDeep(initialLexical);
            updatedLexical.root.children.push({
                children: [
                    {
                        detail: 0,
                        format: 0,
                        mode: 'normal',
                        style: '',
                        text: 'Testing',
                        type: 'text',
                        version: 1
                    }
                ],
                direction: 'ltr',
                format: '',
                indent: 0,
                type: 'paragraph',
                version: 1
            });

            const page = {
                title: 'Latest Collection Card Test',
                status: 'draft',
                lexical: JSON.stringify(initialLexical)
            };

            const {body: createBody} = await agent
                .post('/pages/?formats=mobiledoc,lexical,html', {
                    headers: {
                        'content-type': 'application/json'
                    }
                })
                .body({pages: [page]})
                .expectStatus(201);

            const [createResponse] = createBody.pages;

            await agent
                .put(`/pages/${createResponse.id}/?formats=mobiledoc,lexical,html`)
                .body({
                    pages: [{
                        id: createResponse.id,
                        lexical: JSON.stringify(updatedLexical),
                        updated_at: createResponse.updated_at // satisfy collision detection
                    }]
                })
                .expectStatus(200);
        });
    });

    describe('Copy', function () {
        it('Can copy a page', async function () {
            const page = {
                title: 'Test Page',
                status: 'published'
            };

            const {body: pageBody} = await agent
                .post('/pages/?formats=mobiledoc,lexical,html', {
                    headers: {
                        'content-type': 'application/json'
                    }
                })
                .body({pages: [page]})
                .expectStatus(201)
                .matchBodySnapshot({
                    pages: [Object.assign({}, matchPageShallowIncludes)]
                });

            const [pageResponse] = pageBody.pages;

            await agent
                .post(`/pages/${pageResponse.id}/copy?formats=mobiledoc,lexical`)
                .expectStatus(201)
                .matchBodySnapshot({
                    pages: [Object.assign({}, matchPageShallowIncludes, {published_at: null})]
                })
                .matchHeaderSnapshot({
                    'content-version': anyContentVersion,
                    etag: anyEtag,
                    location: anyLocationFor('pages')
                });
        });
    });

    describe('Convert', function () {
        it('can convert a mobiledoc page to lexical', async function () {
            const mobiledoc = JSON.stringify({
                version: '0.3.1',
                ghostVersion: '4.0',
                markups: [],
                atoms: [],
                cards: [],
                sections: [
                    [1, 'p', [
                        [0, [], 0, 'This is some great content.']
                    ]]
                ]
            });
            const expectedLexical = JSON.stringify({
                root: {
                    children: [
                        {
                            children: [
                                {
                                    detail: 0,
                                    format: 0,
                                    mode: 'normal',
                                    style: '',
                                    text: 'This is some great content.',
                                    type: 'text',
                                    version: 1
                                }
                            ],
                            direction: 'ltr',
                            format: '',
                            indent: 0,
                            type: 'paragraph',
                            version: 1
                        }
                    ],
                    direction: 'ltr',
                    format: '',
                    indent: 0,
                    type: 'root',
                    version: 1
                }
            });
            const pageData = {
                title: 'Test Post',
                status: 'published',
                mobiledoc: mobiledoc,
                lexical: null
            };

            const {body: pageBody} = await agent
                .post('/pages/?formats=mobiledoc,lexical,html', {
                    headers: {
                        'content-type': 'application/json'
                    }
                })
                .body({pages: [pageData]})
                .expectStatus(201);

            const [pageResponse] = pageBody.pages;

            const convertedResponse = await agent
                .put(`/pages/${pageResponse.id}/?formats=mobiledoc,lexical,html&convert_to_lexical=true`)
                .body({pages: [Object.assign({}, pageResponse)]})
                .expectStatus(200)
                .matchBodySnapshot({
                    pages: [Object.assign({}, matchPageShallowIncludes, {lexical: expectedLexical, mobiledoc: null})]
                })
                .matchHeaderSnapshot({
                    'content-version': anyContentVersion,
                    etag: anyEtag
                });

            // rerunning the conversion against a converted post should not change it
            const convertedPage = convertedResponse.body.pages[0];
            const expectedConvertedLexical = convertedPage.lexical;
            await agent
                .put(`/pages/${pageResponse.id}/?formats=mobiledoc,lexical,html&convert_to_lexical=true`)
                .body({pages: [Object.assign({}, convertedPage)]})
                .expectStatus(200)
                .matchBodySnapshot({
                    pages: [Object.assign({}, matchPageShallowIncludes, {lexical: expectedConvertedLexical, mobiledoc: null})]
                })
                .matchHeaderSnapshot({
                    'content-version': anyContentVersion,
                    etag: anyEtag
                });
        });
    });
});
