const Controller = require('libflitter/controller/Controller')
const Page = require('../../../models/api/Page.model')
const Node = require('../../../models/api/Node.model')
const Database = require('../../../models/api/db/Database.model')
const ColumnDef = require('../../../models/api/db/ColumnDef.model')
const DBEntry = require('../../../models/api/db/DBEntry.model')

/*
 * Database Controller
 * -------------------------------------------------------------
 * Put some description here!
 */
class FormDatabase extends Controller {

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

        let page = await Page.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 NodeId = req.params.NodeId

        let node = await Node.findOne({UUID: NodeId})
        if ( !node ) return res.status(404).message('Node not found with that ID.').api({})

        const db = new Database({
            Name: req.body.name || req.body.Name || 'New Database',
            NodeId: node.UUID,
            PageId: page.UUID,
        })

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

        await db.version_save(`Added to page "${page.Name}"`, req.user.id)

        req.user.allow(`database:${db.UUID}`);
        await req.user.save()

        return res.api(db)
    }

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

        let page = await Page.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 NodeId = req.params.NodeId

        let node = await Node.findOne({UUID: NodeId})
        if ( !node ) return res.status(404).message('Node not found with that ID.').api({})

        const DatabaseId = req.params.DatabaseId
        const db = await Database.findOne({UUID: DatabaseId})
        if ( !db ) return res.status(404).message('Database not found with that ID.').api({})
        // if ( !db.accessible_by(req.user) ) return req.security.deny()

        return res.api(db)
    }

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

        let page = await Page.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 NodeId = req.params.NodeId

        let node = await Node.findOne({UUID: NodeId})
        if ( !node ) return res.status(404).message('Node not found with that ID.').api({})

        const DatabaseId = req.params.DatabaseId
        const db = await Database.findOne({UUID: DatabaseId})
        if ( !db ) return res.status(404).message('Database not found with that ID.').api({})

        const columns = []
        for ( const col_id of db.ColumnIds ) {
            const rec = await ColumnDef.findOne({UUID: col_id})
            if ( rec ) {
                rec.additionalData = rec.data()
                columns.push(rec)
            }
        }

        // Fallback for backwards compat
        if ( columns.length < 1 ) {
            return res.api((await ColumnDef.find({DatabaseId: db.UUID})).map(x => {
                x.additionalData = x.data()
                return x
            }))
        }

        return res.api(columns)
    }

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

        const PageId = req.params.PageId

        let page = await Page.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 NodeId = req.params.NodeId

        let node = await Node.findOne({UUID: NodeId})
        if ( !node ) return res.status(404).message('Node not found with that ID.').api({})

        const DatabaseId = req.params.DatabaseId
        const db = await Database.findOne({UUID: DatabaseId})
        if ( !db ) return res.status(404).message('Database not found with that ID.').api({})
        // if ( !db.accessible_by(req.user, 'update') ) return req.security.deny()

        if ( req.body.Name !== db.Name ) {
            db.Name = req.body.Name
            await db.version_save(`Changed database name to "${req.body.Name}"`, req.user.id)
        } else {
            await db.save()
        }

        return res.api(db)
    }

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

        let page = await Page.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 NodeId = req.params.NodeId

        let node = await Node.findOne({UUID: NodeId})
        if ( !node ) return res.status(404).message('Node not found with that ID.').api({})

        const DatabaseId = req.params.DatabaseId
        const db = await Database.findOne({UUID: DatabaseId})
        if ( !db ) return res.status(404).message('Database not found with that ID.').api({})
        // if ( !db.accessible_by(req.user, 'update') ) return req.security.deny()

        const existing_columns = await ColumnDef.find({ DatabaseId: db.UUID })
        const assoc_columns = []
        existing_columns.forEach(col => assoc_columns[col.UUID] = col)

        const update_columns = []
        for ( const col of req.body.columns ) {
            if ( col.UUID && assoc_columns[col.UUID] ) {
                assoc_columns[col.UUID].headerName = col.headerName
                assoc_columns[col.UUID].field = col.field
                assoc_columns[col.UUID].DatabaseId = db.UUID
                assoc_columns[col.UUID].Type = col.Type
                assoc_columns[col.UUID].additionalData = JSON.stringify(col.additionalData)
                await assoc_columns[col.UUID].version_save(`Updated in page "${page.Name}"`, req.user.id)
                update_columns.push(assoc_columns[col.UUID])
            } else {
                const new_col = new ColumnDef({
                    headerName: col.headerName,
                    field: col.field,
                    DatabaseId: db.UUID,
                    Type: col.Type,
                    additionalData: JSON.stringify(col.additionalData),
                })

                if ( col.UUID ) {
                    const existingUUID = await ColumnDef.findOne({ UUID: col.UUID })
                    if ( !existingUUID ) {
                        new_col.UUID = col.UUID
                    }
                }

                await new_col.version_save(`Added to database`, req.user.id)
                update_columns.push(new_col)
            }
        }

        for ( const col of existing_columns ) {
            if ( !update_columns.includes(col) ) {
                await col.delete()
            }
        }

        const new_cols = update_columns.map(x => x.UUID)
        const no_updates = (new_cols.length === db.ColumnIds.length) && (new_cols.every(val => db.ColumnIds.includes(val)));
        if ( !no_updates ) {
            db.ColumnIds = new_cols
            await db.version_save('Updated columns', req.user.id)
        } else {
            await db.save()
        }

        return res.api(update_columns)
    }

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

        let page = await Page.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 NodeId = req.params.NodeId

        let node = await Node.findOne({UUID: NodeId})
        if ( !node ) return res.status(404).message('Node not found with that ID.').api({})

        const DatabaseId = req.params.DatabaseId
        const db = await Database.findOne({UUID: DatabaseId})
        if ( !db ) return res.status(404).message('Database not found with that ID.').api({})
        // if ( !db.accessible_by(req.user) ) return req.security.deny()

        const entries = await DBEntry.find({DatabaseId: db.UUID})
        entries.forEach(entry => entry.RowData.UUID = entry.UUID)

        return res.api(entries)
    }

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

        let page = await Page.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 NodeId = req.params.NodeId

        let node = await Node.findOne({UUID: NodeId})
        if ( !node ) return res.status(404).message('Node not found with that ID.').api({})

        const DatabaseId = req.params.DatabaseId
        const db = await Database.findOne({UUID: DatabaseId})
        if ( !db ) return res.status(404).message('Database not found with that ID.').api({})
        // if ( !db.accessible_by(req.user) ) return req.security.deny()

        await DBEntry.deleteMany({DatabaseId: db.UUID})

        const new_recs = []
        for ( const rec of req.body ) {
            const data = {DatabaseId: db.UUID}
            if ( rec.UUID ) data.UUID = rec.UUID
            delete rec.UUID
            data.RowData = rec

            const dbe = new DBEntry(data)
            await dbe.save()
            dbe.RowData.UUID = dbe.UUID
            new_recs.push(dbe)
        }

        await db.version_save('Updated data', req.user.id)
        return res.api(await this._set_indices(db, new_recs))
    }

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

        let page = await Page.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 NodeId = req.params.NodeId

        let node = await Node.findOne({UUID: NodeId})
        if ( !node ) return res.status(404).message('Node not found with that ID.').api({})

        const DatabaseId = req.params.DatabaseId
        const db = await Database.findOne({UUID: DatabaseId})
        if ( !db ) return res.status(404).message('Database not found with that ID.').api({})
        // if ( !db.accessible_by(req.user) ) return req.security.deny()

        await DBEntry.deleteMany({DatabaseId: db.UUID})
        await db.version_save('Deleted', req.user.id)
        await db.delete()
        return res.api({})
    }

    async _set_indices(db, data) {
        const index_columns = await ColumnDef.find({DatabaseId: db.UUID, Type: 'index'})

        for ( const col of index_columns ) {
            let max_val = 0
            data.forEach(row => {
                const val = row.RowData[col.field]
                if ( val && val > max_val ) max_val = val
            });

            let next_val = max_val + 1
            for ( const row of data ) {
                if ( !row.RowData[col.field] ) {
                    row.RowData[col.field] = next_val
                    next_val += 1
                    await row.save()
                }
            }
        }

        return data
    }
}

module.exports = exports = FormDatabase
