/* ----------------------------------------------------------------------------

 Question Server
 GitLab: <https://gitlab.com/MrFry/mrfrys-node-server>

 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program. If not, see <https://www.gnu.org/licenses/>.

 ------------------------------------------------------------------------- */

import { v4 as uuidv4 } from 'uuid'

import logger from '../../../utils/logger'
import utils from '../../../utils/utils'
import { Request, SubmoduleData, User } from '../../../types/basicTypes'

interface Comment {
    date: string
    user: number
    content: string
    admin: boolean
    subComments?: Comment[]
    reacts?: {
        [key: string]: number[]
    }
}

interface ForumEntry {
    date: string
    user: number
    title: string
    content: string
    admin: boolean
    comments: Comment[]
    mediaPath?: string
    reacts: {
        [key: string]: number[]
    }
}

interface ForumContents {
    user: number
    title: string
    admin: boolean
    commentCount: number
}

const adminUsersFile = 'data/admins.json'
const forumContentsFileName = '.contents.json'

function countComments(comments: Comment[]) {
    return comments.reduce((acc, comment) => {
        if (comment.subComments) {
            acc += countComments(comment.subComments) + 1
        } else {
            acc += 1
        }
        return acc
    }, 0)
}

function addComment(obj: Comment[], path: number[], comment: Comment) {
    if (path.length === 0) {
        obj.push(comment)
    } else {
        const i = path.pop()
        if (!obj[i].subComments) {
            obj[i].subComments = []
        }
        addComment(obj[i].subComments, path, comment)
    }
}

function deleteComment(
    obj: Comment[],
    path: number[],
    userid: number
): boolean {
    if (path.length === 1) {
        if (obj[path[0]].user === userid) {
            obj.splice(path[0], 1)
            return true
        } else {
            return false
        }
    } else {
        const i = path.pop()
        deleteComment(obj[i].subComments, path, userid)
        return true
    }
}

function addReaction(
    obj: Comment[],
    path: number[],
    {
        reaction,
        isDelete,
        uid,
    }: { reaction: string; isDelete: boolean; uid: number }
) {
    if (path.length === 1) {
        const index = path[0]
        if (!obj[index].reacts) {
            obj[index].reacts = {}
        }
        if (isDelete) {
            if (obj[index].reacts[reaction]) {
                obj[index].reacts[reaction] = obj[index].reacts[
                    reaction
                ].filter((currUid: number) => {
                    return uid !== currUid
                })
                if (obj[index].reacts[reaction].length === 0) {
                    delete obj[index].reacts[reaction]
                }
            }
        } else {
            if (!obj[index].reacts[reaction]) {
                obj[index].reacts[reaction] = [uid]
            } else {
                if (!obj[index].reacts[reaction].includes(uid)) {
                    obj[index].reacts[reaction].push(uid)
                }
            }
        }
    } else {
        const i = path.pop()
        addReaction(obj[i].subComments, path, {
            reaction: reaction,
            isDelete: isDelete,
            uid: uid,
        })
    }
}

function getForumData(
    forumName: string,
    forumDir: string
): { forumPath: string; contentFilePath: string; contents: ForumContents[] } {
    const safeForumName = forumName.replace(/\./g, '').replace(/\/+/g, '')
    const forumPath = forumDir + '/' + safeForumName
    const contentFilePath = forumPath + '/' + forumContentsFileName

    if (!utils.FileExists(forumPath)) {
        utils.CreatePath(forumPath, true)
    }

    if (!utils.FileExists(contentFilePath)) {
        utils.WriteFile('{}', contentFilePath)
    }
    const contents: ForumContents[] = utils.ReadJSON(contentFilePath)
    return {
        forumPath: forumPath,
        contentFilePath: contentFilePath,
        contents: contents,
    }
}

function getPostData(
    postKey: string,
    contents: ForumContents[],
    forumPath: string
): {
    postData: ForumEntry
    postPath: string
} {
    const safePostKey = postKey.replace(/\./g, '').replace(/\/+/g, '')
    const postPath = forumPath + '/' + safePostKey
    if (!contents[postKey] || !utils.FileExists(postPath)) {
        return null
    }
    return { postData: utils.ReadJSON(postPath), postPath: postPath }
}

function setup(data: SubmoduleData): void {
    const { app, /* userDB, url, */ publicdirs /*, moduleSpecificData */ } =
        data

    const publicDir = publicdirs[0]

    const forumDir = publicDir + 'forum'
    const forumFiles = publicDir + 'forumFiles'

    if (!utils.FileExists(forumDir)) {
        utils.CreatePath(forumDir, true)
    }

    app.get('/forumEntries', (req: Request, res) => {
        logger.LogReq(req)
        const forumName: string = req.query.forumName
        if (!forumName) {
            res.json({
                success: false,
                msg: `Forum name is not specified!`,
            })
            return
        }

        const { forumPath, contents } = getForumData(forumName, forumDir)

        const from = req.query.from || Object.keys(contents).reverse()[0]
        const count = parseInt(
            req.query.count ? req.query.count.toString() : '5'
        )
        const getContent = req.query.getContent

        let entries = {}
        let i = 0
        let passed = false
        let lastKey = undefined
        Object.keys(contents)
            .reverse()
            .some((key) => {
                const entry = getContent
                    ? utils.ReadJSON(forumPath + '/' + key)
                    : contents[key]
                if (key === from) {
                    passed = true
                }
                if (i < count && passed) {
                    entries = {
                        ...entries,
                        [key]: entry,
                    }
                }
                if (i === count) {
                    lastKey = key
                    return true
                }
                if (passed) {
                    i++
                }
                return false
            })

        res.json({
            entries: entries,
            nextKey: lastKey,
        })
    })

    app.get('/forumEntry', (req: Request, res) => {
        logger.LogReq(req)
        const forumName: string = req.query.forumName
        const postKey: string = req.query.postKey
        if (!forumName || !postKey) {
            res.json({
                success: false,
                msg: `ForumName or postKey is not specified!`,
            })
            return
        }

        const { forumPath } = getForumData(forumName, forumDir)

        res.json({
            entry: utils.ReadJSON(
                forumPath + '/' + postKey.replace(/\./g, '').replace(/\/+/g, '')
            ),
            success: true,
        })
    })

    app.get('/forumRanklist', (req: Request, res) => {
        const forumName: string = req.query.forumName
        if (!forumName) {
            res.json({ success: false, msg: 'forumName required' })
            return
        }

        const { forumPath, contents } = getForumData(forumName, forumDir)

        const forumEntries = Object.keys(contents).map((key) => {
            const entry = utils.ReadJSON(forumPath + '/' + key)
            return entry
        })

        const leaderBoard = forumEntries.reduce((acc, forumEntry) => {
            const { user, reacts } = forumEntry
            const ups = reacts?.['thumbs up']?.length || 0
            const downs = reacts?.['thumbs down']?.length || 0

            if (!acc[user]) {
                acc[user] = {
                    up: ups,
                    down: downs,
                }
            } else {
                acc[user] = {
                    up: acc[user].up + ups,
                    down: acc[user].down + downs,
                }
            }
            return acc
        }, {})

        res.json({
            success: true,
            leaderBoard: Object.keys(leaderBoard)
                .map((key) => {
                    const val = leaderBoard[key]
                    return {
                        ...val,
                        user: key,
                        sum: val.up - val.down,
                    }
                })
                .sort((a, b) => {
                    return b.sum - a.sum
                }),
        })
    })

    app.post('/postMeme', (req: Request, res) => {
        utils
            .uploadFile(req, forumFiles)
            .then(() => {
                res.json({ success: true })
            })
            .catch(() => {
                res.json({ success: false, msg: 'error during uploading' })
                return
            })
    })

    app.post(
        '/addPost',
        (
            req: Request<{
                forumName: string
                content: string
                title: string
                image?: string
            }>,
            res
        ) => {
            logger.LogReq(req)

            const { title, content, forumName, image } = req.body
            if (!forumName) {
                res.json({
                    success: false,
                    msg: `Forum name is not specified!`,
                })
                return
            }
            const { forumPath, contents, contentFilePath } = getForumData(
                forumName,
                forumDir
            )
            const user: User = req.session.user
            const admins: string[] = utils.FileExists(adminUsersFile)
                ? utils.ReadJSON(adminUsersFile)
                : []

            const newPostKey = uuidv4()
            const postData = {
                date: utils.GetDateString(),
                user: user.id,
                title: title,
                admin: admins.includes(user.id.toString()),
                commentCount: 0,
                mediaPath: image,
            }

            contents[newPostKey] = postData
            utils.WriteFile(JSON.stringify(contents, null, 2), contentFilePath)
            utils.WriteFile(
                JSON.stringify({ ...postData, content: content }, null, 2),
                forumPath + '/' + newPostKey
            )

            res.json({
                success: true,
                newPostKey: newPostKey,
                newEntry: { ...postData, content: content },
            })
        }
    )

    app.post(
        '/rmPost',
        (
            req: Request<{
                postKey: string
                forumName: string
            }>,
            res
        ) => {
            logger.LogReq(req)
            const { postKey } = req.body
            const forumName = req.body.forumName
            if (!forumName) {
                res.json({
                    success: false,
                    msg: `Forum name is not specified!`,
                })
                return
            }
            const { forumPath, contentFilePath, contents } = getForumData(
                forumName,
                forumDir
            )
            const user: User = req.session.user

            if (contents[postKey] && contents[postKey].user === user.id) {
                utils.deleteFile(forumPath + '/' + postKey)
                delete contents[postKey]
                utils.WriteFile(
                    JSON.stringify(contents, null, 2),
                    contentFilePath
                )

                res.json({ success: true, deletedId: postKey })
            } else {
                res.json({
                    success: false,
                    msg: 'Entry does not exists, or you are not authorized to delete this post',
                })
                return
            }
        }
    )

    app.post(
        '/comment',
        (
            req: Request<{
                type: string
                path: number[]
                postKey: string
                forumName: string
                content: string
            }>,
            res
        ) => {
            logger.LogReq(req)

            const forumName = req.body.forumName
            if (!forumName) {
                res.json({
                    success: false,
                    msg: `Forum name is not specified!`,
                })
                return
            }
            const { forumPath, contentFilePath, contents } = getForumData(
                forumName,
                forumDir
            )
            const user: User = req.session.user
            const admins: string[] = utils.FileExists(adminUsersFile)
                ? utils.ReadJSON(adminUsersFile)
                : []
            const { type, path, postKey } = req.body
            if (!postKey || !type || !path) {
                res.json({
                    success: false,
                    msg: 'type or path or postKey is undefined',
                })
                return
            }

            const { postPath, postData } = getPostData(
                postKey,
                contents,
                forumPath
            )
            if (!postData) {
                res.json({
                    success: false,
                    msg: `Post entry '${postKey}' from forum '${forumName}' does not exits!`,
                })
            }

            if (type === 'add') {
                const { content } = req.body
                const comment = {
                    date: utils.GetDateString(),
                    user: user.id,
                    content: content,
                    admin: admins.includes(user.id.toString()),
                }
                if (!postData.comments) {
                    postData.comments = []
                }
                addComment(postData.comments, path, comment)
            } else if (type === 'delete') {
                if (postData.comments) {
                    const success = deleteComment(
                        postData.comments,
                        path,
                        user.id
                    )
                    if (!success) {
                        res.json({
                            success: false,
                            msg: 'you cant delete other users comments',
                            postData: postData,
                        })
                        return
                    }
                }
            } else {
                res.json({ success: false, msg: 'no such type' })
                return
            }

            contents[postKey].commentCount = countComments(postData.comments)

            utils.WriteFile(JSON.stringify(postData, null, 2), postPath)
            utils.WriteFile(JSON.stringify(contents, null, 2), contentFilePath)
            res.json({ success: true, postKey: postKey, postData: postData })
        }
    )

    app.post(
        '/react',
        (
            req: Request<{
                forumName: string
                postKey: string
                path: number[]
                reaction: string
                isDelete: boolean
            }>,
            res
        ) => {
            logger.LogReq(req)

            const forumName = req.body.forumName
            if (!forumName) {
                res.json({
                    success: false,
                    msg: `Forum name is not specified!`,
                })
                return
            }
            const { forumPath, contents } = getForumData(forumName, forumDir)
            const user: User = req.session.user

            const { postKey, path, reaction, isDelete } = req.body
            if (!postKey || !reaction) {
                res.json({ success: false, msg: 'no postkey or reaction' })
                return
            }

            const { postPath, postData } = getPostData(
                postKey,
                contents,
                forumPath
            )
            if (!postData) {
                res.json({
                    success: false,
                    msg: `Post entry '${postKey}' from forum '${forumName}' does not exits!`,
                })
            }

            if (!path || path.length === 0) {
                if (postData) {
                    if (isDelete) {
                        if (postData.reacts) {
                            postData.reacts[reaction] = postData.reacts[
                                reaction
                            ].filter((uid) => {
                                return uid !== user.id
                            })
                            if (postData.reacts[reaction].length === 0) {
                                delete postData.reacts[reaction]
                            }
                        }
                    } else {
                        if (!postData.reacts) {
                            postData.reacts = { [reaction]: [user.id] }
                        } else {
                            if (Array.isArray(postData.reacts[reaction])) {
                                if (
                                    !postData.reacts[reaction].includes(user.id)
                                ) {
                                    postData.reacts[reaction].push(user.id)
                                }
                            } else {
                                postData.reacts[reaction] = [user.id]
                            }
                        }
                    }
                }
            } else {
                addReaction(postData.comments, path, {
                    reaction: reaction,
                    isDelete: isDelete,
                    uid: user.id,
                })
            }

            utils.WriteFile(JSON.stringify(postData, null, 2), postPath)
            res.json({ success: true, postData: postData })
        }
    )
}

export default {
    setup: setup,
}
