const { Controller } = require('libflitter')
const ncp = require('ncp').ncp
const fs = require('fs').promises
const path = require('path')
const md = require('markdown').markdown
const rimraf = require('rimraf')
const uuid = require('uuid/v4')

class ExportController extends Controller {
    static get services() {
        return [...super.services, 'models', 'utility', 'upload']
    }

    async get_export_list(req, res, next) {
        const Export = this.models.get('api:Export')
        const exports = await Export.find({
            user_id: req.user.id,
        })

        return res.api(exports)
    }

    async download_export(req, res, next) {
        const Export = this.models.get('api:Export')
        const exported = await Export.findOne({ UUID: req.params.ExportId, user_id: req.user.id })
        if ( !exported ) {
            return res.status(404)
                .message('No export with that UUID found.')
                .api()
        }

        const File = this.models.get('upload::File')
        const file = await File.findOne({_id: File.ObjectId(exported.file_id)})
        if ( !file ) return res.status(404).message('This export file has been deleted.').api({})

        return file.send(res)
    }

    async export_subtree(req, res, next) {
        const Export = this.models.get('api:Export')
        const format = req.form.format
        const page = req.form.page

        if ( format === 'html' ) {
            const generated_export = await this.export_subtree_as_html(page, req.user)

            // Store the generated archive
            const uploader = this.upload.provider()
            const file = await uploader.store({
                temp_path: generated_export,
                original_name: `export-${uuid()}.tar.gz`,
                mime_type: 'application/gzip',
                tag: 'generated_export',
            })

            const exp = new Export({
                user_id: req.user.id,
                format,
                subtree: true,
                file_id: file.id,
                PageId: page.UUID,
            })

            await exp.save()
            return res.api(exp)
        }

        return res.status(400)
            .message('Invalid export format!')
            .api()
    }

    async export_subtree_as_html(page, user) {
        const flat_tree = []
        await page.version_save('Exported page as HTML', user.id)

        const add_to_tree = async (page, level = 0) => {
            if ( await page.is_accessible_by(user, 'view') ) {
                flat_tree.push({
                    level,
                    page,
                    file_name: `${page.Name.replace(/\s/g, '-').replace(/[\/\\]/g, '-')}-${page.UUID}.html`,
                })

                const children = await page.childPages
                for ( const child of children ) {
                    await add_to_tree(child, level + 1)
                }
            }
        }

        await add_to_tree(page)

        const manifest = {
            sidebar: [],
        }

        for ( const item of flat_tree ) {
            manifest.sidebar.push({
                title: item.page.Name,
                level: item.level,
                link: item.file_name,
            })
        }

        // Copy the template over
        const work_dir = await this.scratch_dir()
        await this.copy_template(this.utility.path('app', 'assets', 'export', 'html'), work_dir)

        const html_template = await fs.readFile(path.resolve(work_dir, 'index.html'), 'utf-8')

        for ( const item of flat_tree ) {
            let item_template = html_template

            item_template = item_template.replace(/{{\s?MANIFEST\s?}}/g, JSON.stringify(manifest))
            item_template = item_template.replace(/{{\s?GROUP_TITLE\s?}}/g, page.Name)
            item_template = item_template.replace(/{{\s?PAGE_TITLE\s?}}/g, item.page.Name)
            item_template = item_template.replace(/{{\s?PAGE_CONTENT\s?}}/g, await this.page_as_html(item.page, work_dir))

            await fs.writeFile(path.resolve(work_dir, item.file_name), item_template)
        }

        // Write the main page redirect
        const redir_html = `
            <!DOCTYPE html>
            <html>
                <head>
                    <meta http-equiv="refresh" content="0; url=${flat_tree[0].file_name}">
                </head>
            </html>
        `
        await fs.writeFile(path.resolve(work_dir, 'index.html'), redir_html)

        // Create the archive
        const tar = require('tar')
        const archive_path = path.resolve(work_dir, '..', `export-${uuid()}.tar.gz`)
        await tar.c({
            gzip: true,
            file: archive_path,
            cwd: path.resolve(work_dir, '..'),
        }, [path.basename(work_dir)])

        await new Promise(res => {
            rimraf(work_dir, res)
        })
        return archive_path
    }

    async copy_template(from, to) {
        return new Promise((res, rej) => {
            ncp(from, to, err => {
                if ( err ) rej(err)
                else res()
            })
        })
    }

    async scratch_dir() {
        const tmp = require('tmp')
        const gen_id = uuid()

        return new Promise((res, rej) => {
            tmp.dir((err, tmp_path) => {
                if ( err ) rej(err)
                else {
                    fs.mkdir(path.resolve(tmp_path, `export`)).then(() => {
                        res(path.resolve(tmp_path, `export`))
                    })
                }
            })
        })
    }

    /**
     * Given a page and a template working directory, render that page as HTML.
     * @param {Page} page
     * @param {string} work_dir
     * @return {Promise<string>}
     */
    async page_as_html(page, work_dir) {
        const Codium = this.models.get('api:Codium')
        const FileGroup = this.models.get('api:FileGroup')
        const File = this.models.get('upload::File')
        const Database = this.models.get('api:db:Database')
        const DBEntry = this.models.get('api:db:DBEntry')

        let html = ''

        const nodes = await page.nodes
        for ( const node of nodes ) {
            // ATM, there are 5 node types: norm, markdown, database_ref, files_ref, and code_ref

            if ( node.Value.Mode === 'norm' ) {
                html += node.Value.Value
            } else if ( node.Value.Mode === 'markdown' ) {
                html += md.toHTML(node.Value.Value)
            } else if ( node.Type === 'code_ref' ) {
                const code = await Codium.findOne({ UUID: node.Value.Value })
                if ( code ) {
                    const snip_file = `code-snippet-${code.UUID}.txt`
                    await fs.writeFile(path.resolve(work_dir, snip_file), code.code)

                    html += `
                        <div class="code-ref">
                            <wc-monaco-editor id="${code.UUID}" language="${code.Language}" src="${snip_file}"></wc-monaco-editor>
                        </div>
                    `
                }
            } else if ( node.Type === 'file_ref' ) {
                const file_group = await FileGroup.findOne({ UUID: node.Value.Value })
                if ( file_group ) {
                    const file_htmls = []

                    for ( const file_id of file_group.FileIds ) {
                        const file = await File.findById(file_id)
                        if ( file ) {
                            const store_path = file.provider().filepath(file.store_id)
                            const ext = file.original_name.split('.').reverse()[0]
                            await this.copy_template(store_path, path.resolve(work_dir, `file-${file.upload_name}.${ext}`))

                            file_htmls.push(`
                                <div class="file">
                                    <div class="file-name">${file.original_name}</div>
                                    <a href="file-${file.upload_name}.${ext}" class="dl-link" target="_blank">▼</a>
                                </div>
                            `)
                        }
                    }

                    html += `
                        <div class="file-ref">
                            ${file_htmls.join('\n')}
                        </div>`
                }
            } else if ( node.Type === 'database_ref' ) {
                const grid_id = `dbase-${node.Value.Value}`
                const db = await Database.findOne({ UUID: node.Value.Value })

                if ( db ) {
                    html += `
                        <div id="${grid_id}-container" class="database-container">
                            <h4>${db.Name}</h4>
                            <div id="${grid_id}" class="ag-theme-balham database-ref"></div>
                        </div>
                        <script src="${grid_id}.js"></script>
                    `

                    // generate the column defs
                    const cols = await db.get_columns()
                    const col_defs = cols.map(col => {
                        return {
                            field: col.field,
                            headerName: col.headerName,
                        }
                    })

                    // generate the data rows
                    const entries = await DBEntry.find({ DatabaseId: db.UUID })
                    const rows = entries.map(entry => entry.RowData)

                    let dbTemplateContents = await fs.readFile(path.resolve(work_dir, 'database.js'), 'utf-8')
                    dbTemplateContents = dbTemplateContents.replace(/{{\s?COLUMN_DEFS\s?}}/g, JSON.stringify(col_defs))
                    dbTemplateContents = dbTemplateContents.replace(/{{\s?ROW_DATA\s?}}/g, JSON.stringify(rows))
                    dbTemplateContents = dbTemplateContents.replace(/{{\s?GRID_ID\s?}}/g, grid_id)
                    await fs.writeFile(path.resolve(work_dir, `${grid_id}.js`), dbTemplateContents)
                }
            }
        }

        return html
    }
}

module.exports = exports = ExportController
