const Controller = require('libflitter/controller/Controller')
const PageModel = require('../../../models/api/Page.model')
const Node = require('../../../models/api/Node.model')

/*
 * Page Controller
 * -------------------------------------------------------------
 * Put some description here!
 */
class Page extends Controller {
    static get services() {
        return [...super.services, 'models']
    }

    async get_page_versions(req, res, next) {
        const User = this.models.get('auth:User')
        const PageId = req.params.PageId
        const user = req.user

        let page = await PageModel.findOne({UUID: PageId})
        if ( !page ) return res.status(404).message('Page not found with that ID.').api({})

        if ( !(await page.is_accessible_by(user)) ) return req.security.deny()

        const versions = page.version_archive.map(version_data => {
            return {
                current_version: Number(version_data.version_num) === Number(page.version_num),
                version_num: version_data.version_num,
                version_user_id: version_data.version_user_id,
                version_message: version_data.version_message,
                version_UUID: version_data.version_UUID,
                version_create_date: version_data.version_create_date,
            }
        })

        for ( const version of versions ) {
            version.user_display = (await User.findById(version.version_user_id))?.uid || 'Unknown User'
        }

        return res.api(versions.reverse())
    }

    async get_page(req, res) {
        const PageId = req.params.PageId
        const user = req.user

        let page = await PageModel.findOne({UUID: PageId})
        if ( !page ) return res.status(404).message('Page not found with that ID.').api({})

        if ( !(await page.is_accessible_by(user)) ) return req.security.deny()
        const level = await page.access_level_for(req.user)

        const version_num = req.body.version || req.query.version
        if ( version_num ) {
            if ( page.has_version(version_num) ) {
                page = await page.as_version(version_num)
            }
        }

        const data = page.toJSON()
        delete data.version_archive
        data.level = level
        data._id = page.id
        return res.api(data)
    }

    async save_page(req, res) {
        const PageId = req.params.PageId

        let page;
        if ( PageId ) {
            page = await PageModel.findOne({UUID: PageId})
            if ( !page ) return res.status(404).message('Page not found with that ID.').api({})
            if ( !(await page.is_accessible_by(req.user, 'update')) ) return res.security.deny()
        } else {
            page = new PageModel
            page.CreatedUserId = req.user.id
            page.OrgUserId = req.user._id
        }

        let save_message

        if ( !req.body.Name ) return res.status(400).message('Missing required: Name').api({})

        if ( page.Name !== req.body.Name ) {
            page.Name = req.body.Name
            save_message = `Changed page name to ${page.Name}`
        }

        if ( 'IsPublic' in req.body ) {
            if ( Boolean(page.IsPublic) !== Boolean(req.body.IsPublic) ) {
                save_message = `Made page ${req.body.IsPublic ? 'public' : 'private'}`
            }

            page.IsPublic = !!req.body.IsPublic
        }

        if ( 'IsVisibleInMenu' in req.body ) {
            if ( Boolean(page.IsVisibleInMenu) !== Boolean(req.body.IsVisibleInMenu) ) {
                save_message = `Made page ${req.body.IsVisibleInMenu ? 'visible' : 'hidden'} in menu`
            }

            page.IsVisibleInMenu = !!req.body.IsVisibleInMenu
        }

        /*let parent;
        if ( !req.body.ParentId ) return res.status(400).message('Missing required: ParentId').api({})
        else {
            parent = await PageModel.findOne({UUID: req.body.ParentId})
            if ( !parent ) return res.status(404).message('Parent page not found with that ID.').api({})
            if ( !(await parent.is_accessible_by(req.user, 'update')) ) return req.security.kickout()
        }*/ // TODO re-implement this to account for sharing

        page.UpdatedAt = new Date
        page.UpdateUserId = req.user._id
        if ( save_message ) {
            await page.version_save(save_message, req.user.id)
        } else {
            await page.save()
        }
        return res.api(page)
    }

    async get_nodes(req, res) {
        const PageId = req.params.PageId

        let page;
        if ( PageId ) {
            page = await PageModel.findOne({UUID: PageId})
            if ( !page ) return res.status(404).message('Page not found with that ID.').api({})
            if ( !(await page.is_accessible_by(req.user)) ) return req.security.deny()
        }

        const version_num = req.body.version || req.query.version
        if ( version_num ) {
            if ( page.has_version(version_num) ) {
                page = await page.as_version(version_num)
            }
        }

        const nodes = await page.nodes
        const assoc_nodes = {}
        nodes.forEach(node => {
            assoc_nodes[node.UUID] = node
        })

        const return_nodes = []
        for ( const uuid of page.NodeIds ) {
            return_nodes.push(assoc_nodes[uuid])
        }
        return res.api(return_nodes)
    }

    async save_node_to_page(req, res) {
        const PageId = req.params.PageId

        const page = await PageModel.findOne({UUID: PageId})
        if ( !page ) return res.status(404).message('Page not found with that ID.').api({})
        if ( !(await page.is_accessible_by(req.user, 'update')) ) return res.security.deny()

        const nodes = await Node.find({PageId: page.UUID})
        const assoc_nodes = {}
        nodes.forEach(node => {
            assoc_nodes[node.UUID] = node
        })

        if ( !req.body.nodeData ) {
            return res.status(400).message('Missing nodeData body').api()
        }

        const node = req.body.nodeData;
        if ( node.UUID && assoc_nodes[node.UUID] ) {
            assoc_nodes[node.UUID].update_from_raw(node)
            assoc_nodes[node.UUID].UpdatedAt = new Date
            assoc_nodes[node.UUID].UpdateUserId = req.user._id
            await assoc_nodes[node.UUID].version_save(`Updated in page`, req.user.id)
            return res.api(assoc_nodes[node.UUID])
        } else {
            const node_obj = new Node({
                Type: node.Type,
                Value: node.Value,
                PageId,
                CreatedUserId: req.user._id,
                UpdateUserId: req.user._id,
            })

            if ( node.UUID ) {
                const existingUUID = await Node.findOne({ UUID: node.UUID })
                if ( !existingUUID ) {
                    node_obj.UUID = node.UUID
                }
            }

            await node_obj.version_save(`Added to page "${page.Name}"`)

            page.NodeIds.push(node_obj.UUID);
            await page.version_save(`Added ${node_obj.Type.split('_')[0]} node to page`, req.user.id);
            return res.api(node_obj)
        }
    }

    async save_nodes(req, res) {
        const PageId = req.params.PageId

        let page;
        if ( PageId ) {
            page = await PageModel.findOne({UUID: PageId})
            if ( !page ) return res.status(404).message('Page not found with that ID.').api({})
            if ( !(await page.is_accessible_by(req.user, 'update')) ) return req.security.deny()
        }

        const nodes = await Node.find({PageId: page.UUID})
        const assoc_nodes = {}
        nodes.forEach(node => {
            assoc_nodes[node.UUID] = node
        })

        if ( !Array.isArray(req.body) ) return res.status(400).message('Invalid request body. Should be array of nodes.').api({})

        const updated_node_ids = []
        for ( let node of req.body ) {
            if ( node.UUID && Object.keys(assoc_nodes).includes(node.UUID) ) updated_node_ids.push(node.UUID)
        }

        const updated_nodes = []
        for ( let node of req.body ) {
            if ( node.UUID && assoc_nodes[node.UUID] ) {
                assoc_nodes[node.UUID].update_from_raw(node)
                assoc_nodes[node.UUID].UpdatedAt = new Date
                assoc_nodes[node.UUID].UpdateUserId = req.user._id
                updated_nodes.push(assoc_nodes[node.UUID])
            } else {
                const node_obj = new Node({
                    Type: node.Type,
                    Value: node.Value,
                    PageId,
                    CreatedUserId: req.user._id,
                    UpdateUserId: req.user._id,
                })

                updated_nodes.push(node_obj)
            }
        }

        // console.log('updated nodes', updated_nodes)

        let delete_nodes = nodes
        for ( const node of updated_nodes ) {
            await node.version_save(`Contents saved in page "${page.Name}"`, req.user.id)
            delete_nodes = delete_nodes.filter(n => {
                return n.UUID !== node.UUID
            })
        }

        for ( const node of delete_nodes ) {
            await node.delete()
        }

        page.NodeIds = updated_nodes.map(x => x.UUID)
        await page.version_save('Saved page contents', req.user.id)
        res.api(updated_nodes)
    }

    async create_top_level(req, res) {
        if ( !req.body.name ) {
            return res.status(400).message('Missing required field: name').api({})
        }

        const root_page = await req.user.get_root_page()

        const new_page = new PageModel({
            Name: req.body.name,
            OrgUserId: req.user._id,
            ParentId: root_page.UUID,
            CreatedUserId: req.user.id,
            UpdateUserId: req.user.id
        })

        if ( req.body.UUID ) {
            const existingUUID = await PageModel.findOne({UUID: req.body.UUID})
            if ( !existingUUID ) {
                new_page.UUID = req.body.UUID
            }
        }

        await new_page.version_save('Created new top-level page', req.user.id)

        root_page.ChildPageIds.push(new_page.UUID)
        await root_page.version_save(`Added new top-level page "${new_page.Name}"`, req.user.id)

        req.user.allow(`page:${new_page.UUID}`)
        await req.user.save()

        if ( !req.body.omit_starter ) {
            const starter_node = new Node({
                Type: 'paragraph',
                Value: {
                    Value: 'Double-click to edit...',
                },
                PageId: new_page.UUID,
                CreatedUserId: req.user.id,
                UpdateUserId: req.user.id
            })

            await starter_node.version_save(`Added to page "${new_page.Name}"`, req.user.id)
            new_page.NodeIds.push(starter_node.UUID)
            await new_page.version_save('Added paragraph node to page', req.user.id)
        }

        return res.api(new_page)
    }

    async create_child(req, res) {
        if ( !req.body.name ) {
            return res.status(400).message('Missing required field: name').api({})
        }

        if ( !req.body.parentId ) {
            return res.status(400).message('Missing required field: parentId').api({})
        }

        // Get the parent
        const parent = await PageModel.findOne({UUID: req.body.parentId})
        if ( !parent ) {
            return res.status(404).message('Unable to find parent with that ID.').api({})
        } else if ( !(await parent.is_accessible_by(req.user, 'manage')) ) {
            return res.security.deny()
        }

        const new_page = new PageModel({
            Name: req.body.name,
            OrgUserId: req.user._id,
            ParentId: parent.UUID,
            CreatedUserId: req.user.id,
            UpdateUserId: req.user.id,
        })

        if ( req.body.UUID ) {
            const existingUUID = await PageModel.findOne({UUID: req.body.UUID})
            if ( !existingUUID ) {
                new_page.UUID = req.body.UUID
            }
        }

        await new_page.version_save(`Created new page as child of "${parent.Name}"`, req.user.id)

        parent.ChildPageIds.push(new_page.UUID)
        await parent.version_save(`Created new child page "${new_page.Name}"`, req.user.id)

        req.user.allow(`page:${new_page.UUID}`)
        await req.user.save()

        if ( !req.body.omit_starter ) {
            const starter_node = new Node({
                Type: 'paragraph',
                Value: {
                    Value: 'Click to edit...',
                },
                PageId: new_page.UUID,
                CreatedUserId: req.user.id,
                UpdateUserId: req.user.id
            })

            await starter_node.version_save(`Added to page "${new_page.Name}"`, req.user.id)
            new_page.NodeIds.push(starter_node.UUID)
            await new_page.version_save('Added paragraph node to page', req.user.id)
        }

        return res.api(new_page)
    }

    async delete_page(req, res) {
        const PageId = req.params.PageId

        let page;
        if ( PageId ) {
            page = await PageModel.findOne({UUID: PageId})
            if ( !page ) return res.status(404).message('Page not found with that ID.').api({})
            if ( !(await page.is_accessible_by(req.user, 'manage')) ) return req.security.deny()
            if ( page.ParentId === '0' ) return req.security.kickout()
        }

        page.Active = false
        page.DeletedAt = new Date
        await page.version_save('Deleted page', req.user.id)
        return res.api({})
    }
}

module.exports = exports = Page
