const fastify = require('fastify')({ logger: true })
const fs = require('fs');
const path = require('path')
const CryptoJS = require("crypto-js");
// var LdapAuth = require('ldapauth-fork');

var usersBdd = "usersBdd.txt";
var prankPath = "prankdata.txt";
var activityPath = "activitydata.txt";
var treasurePath = "treasuredata.txt";
var goldenUsersPath = "goldenusers.txt";
var servicePath = "servicestate.txt";

initFs();

let UsersBDD = JSON.parse(fs.readFileSync(usersBdd));

let PrankData = JSON.parse(fs.readFileSync(prankPath));
let ActivityData = JSON.parse(fs.readFileSync(activityPath));
let TreasureData = JSON.parse(fs.readFileSync(treasurePath));
let GoldenUsers = JSON.parse(fs.readFileSync(goldenUsersPath));
let ServiceState = JSON.parse(fs.readFileSync(servicePath));
let AdminUsersUid = ["asyncnomi", "johan", "enthalpine", "fleur", "arina", "billy", "remi", "pierre", "matmaz", "mariusdrgc", "agathe", "jsansa"];
let UsersToken = {};
let TokenDurationSecond = 3600;
let MaxAmountCrepe = 10;
let Supplements = ["nature", "sucre", "nutella", "confiture"];

fastify.addContentTypeParser('application/json', {
    parseAs: 'string',
    bodyLimit: 10485760
}, function(req, body, done) {
    try {
        var json = JSON.parse(body)
        done(null, json)
    } catch (err) {
        err.statusCode = 400
        done(err, undefined)
    }
})

fastify.register(require('@fastify/static'), {
    root: path.join(__dirname, 'static'),
    decorateReply: false
})

fastify.post('/login', async (request, reply) => {
    let content = request.body;
    if (content.hasOwnProperty("user")
    && content.hasOwnProperty("password")) {
        if (UsersBDD.hasOwnProperty(content.user)) {
            var hash;
            try {
                hash = CryptoJS.SHA512(content.password).toString();
            } catch {
                return {
                    success: false,
                    why: "Wrong username or password"
                }
            }
            if (hash === UsersBDD[content.user].password) {
                let now = new Date();
                UsersToken[content.user] = {
                    token: makeid(64),
                    expire: now.setSeconds(now.getSeconds() + TokenDurationSecond)
                }
                return {
                    success: true,
                    user: {
                        uid: content.user,
                        isAdmin: AdminUsersUid.includes(content.user)
                    },
                    token: UsersToken[content.user].token
                }
            } else {
                return {
                    success: false,
                    why: "Wrong username or password"
                }
            }
        } else {
            return {
                success: false,
                why: "Wrong username or password"
            }
        }
    } else {
        return {
            success: false,
            why: "The username or password is missing"
        }
    }
})

fastify.post('/register', async (request, reply) => {
    let content = request.body;
    if (content.hasOwnProperty("user")
    && content.hasOwnProperty("password")) {
        if (UsersBDD.hasOwnProperty(content.user)) {
            return {
                success: false,
                why: "This user already exists"
            }
        } else {
            var hash;
            try {
                hash = CryptoJS.SHA512(content.password).toString();
            } catch {
                return {
                    success: false,
                    why: "What are you doing bruh ??"
                }
            }
            UsersBDD[content.user] = {
                password: hash
            }
            saveData(usersBdd, UsersBDD);
            let now = new Date();
            UsersToken[content.user] = {
                token: makeid(64),
                expire: now.setSeconds(now.getSeconds() + TokenDurationSecond)
            }
            return {
                success: true,
                user: {
                    uid: content.user,
                    isAdmin: AdminUsersUid.includes(content.user)
                },
                token: UsersToken[content.user].token
            }
        }
    } else {
        return {
            success: false,
            why: "The username or password is missing"
        }
    }
})

fastify.post('/switchState', async (request, reply) => {
    let content = request.body;
    let auth = checkAuthetification(content);
    if (auth.success) {
        if (ServiceState.state == "closed") {
            ServiceState.state = "open";
        } else {
            ServiceState.state = "closed";
        }
        saveData(servicePath, ServiceState);
        return {
            success: true,
            state: ServiceState.state
        }
    } else {
        return auth
    }
})

fastify.post('/addPrank', async (request, reply) => {
    let content = request.body;
    let auth = checkAuthetification(content);
    if (auth.success) {
        if ("type" in content) {
            let prankUid = makeid(16);
            if ("prankUid" in content) {
                let prankExists = check(content, "prankUid", PrankData)
                if (prankExists.success) {
                    if (PrankData[prankUid].state != "Pending") {
                        return {
                            success: false,
                            why: "You cannot edit already accepted prank request"
                        }
                    } else {
                        prankUid = content.prankUid;
                    }
                } else {
                    return prankExists;
                }
            }
            let note = ("note" in content) ? content.note : "N/A";
            switch (content.type) {
                case "crêpe":
                    if ("where" in content
                    && "amount" in content
                    && "supplement" in content) {
                        let amount = parseInt(content.amount)
                        if (!isNaN(amount)) {
                            if (Supplements.includes(content.supplement)) {
                                if (amount < MaxAmountCrepe) {
                                    let prankUid = makeid(16);
                                    PrankData[prankUid] = {
                                        date: new Date(),
                                        creator: content.uid,
                                        type: content.type,
                                        where: content.where,
                                        amount: amount,
                                        supplement: content.supplement,
                                        note: content.note,
                                        state: "Pending",
                                        manageBy: null
                                    }
                                    saveData(prankPath, PrankData);
                                    return {
                                        success: true,
                                        uid: prankUid,
                                        prank: PrankData[prankUid]
                                    }
                                } else {
                                    return {
                                        success: false,
                                        why: "Too much"
                                    }
                                }
                            } else {
                                return {
                                    success: false,
                                    why: "This supplement isn't available"
                                }
                            }
                        } else {
                            return {
                                success: false,
                                why: "Unable to parse the amount as integer"
                            }
                        }
                    } else {
                        return {
                            success: false,
                            why: "Missing amount, where or supplement"
                        }
                    }
                    break;
                case "kidnap":
                    if ("targetUid" in content
                    && "when" in content) {
                        let prankUid = makeid(16);
                        PrankData[prankUid] = {
                            creator: content.uid,
                            type: content.type,
                            targetUid: content.targetUid,
                            when: content.when,
                            note: content.note,
                            state: "Pending",
                            manageBy: null
                        }
                        saveData(prankPath, PrankData);
                        return {
                            success: true,
                            uid: prankUid,
                            prank: PrankData[prankUid]
                        }
                    } else {
                        return {
                            success: false,
                            why: "Missing amount or where"
                        }
                    }
                    break;
                default:
                    return {
                        success: false,
                        why: "Unknow type"
                    }
            }
        } else {
            return {
                success: false,
                why: "Missing type"
            }
        }
    } else {
        return auth
    }
})

fastify.post('/delPrank', async (request, reply) => {
    let content = request.body;
    let auth = checkAuthetification(content);
    if (auth.success) {
        let prankExists = check(content, "prankUid", PrankData)
        if (prankExists.success) {
            if (PrankData[content.prankUid].creator === content.uid
            && PrankData[content.prankUid].state === "Pending") {
                delete PrankData[content.prankUid];
                saveData(prankPath, PrankData);
                return {
                    success: true,
                }
            } else {
                return {
                    success: false,
                    why: "You can't delete prank that aren't yours or those already Accepted or Refused"
                }
            }
        } else {
            return prankExists
        }
    } else {
        return auth
    }
})

fastify.post('/acceptPrank', async (request, reply) => {
    let content = request.body;
    let prankExists = checkManage(content, "prankUid", PrankData)
    if (prankExists.success) {
        PrankData[content.prankUid].state = "Accepted";
        PrankData[content.prankUid].manageBy = content.uid;
        saveData(prankPath, PrankData);
        return {
            success: true,
        }
    } else {
        return prankExists
    }
})

fastify.post('/donePrank', async (request, reply) => {
    let content = request.body;
    let prankExists = checkManage(content, "prankUid", PrankData)
    if (prankExists.success) {
        if (PrankData[content.prankUid].manageBy == content.uid) {
            PrankData[content.prankUid].state = "Done";
            saveData(prankPath, PrankData);
            return {
                success: true,
            }
        } else {
            return {
                success: false,
                why: "Not allowed"
            }
        }
    } else {
        return prankExists
    }
})

fastify.post('/refusePrank', async (request, reply) => {
    let content = request.body;
    let prankExists = checkManage(content, "prankUid", PrankData)
    if (prankExists.success) {
        PrankData[content.prankUid].state = "Refused";
        PrankData[content.prankUid].manageBy = content.uid;
        saveData(prankPath, PrankData);
        return {
            success: true,
        }
    } else {
        return prankExists
    }
})

fastify.post('/get', async (request, reply) => {
    let content = request.body;
    let auth = checkAuthetification(content);
    if (auth.success) {
        if ("type" in content) {
            switch (content.type) {
                case "prank":
                    if (AdminUsersUid.includes(content.uid)) {
                        return {
                            success: true,
                            prankData: PrankData
                        }
                    } else {
                        let prankData = {};
                        for (prank in PrankData) {
                            if (PrankData[prank].creator == content.uid) {
                                prankData[prank] = PrankData[prank];
                            }
                        }
                        return {
                            success: true,
                            prankData: prankData
                        }
                    }
                    break;
                case "activity":
                    return {
                        success: true,
                        activityData: ActivityData
                    }
                    break;
                case "treasure":
                    let treasureData = JSON.parse(JSON.stringify(TreasureData));
                    for (treasure in treasureData) {
                        treasureData[treasure].activity = ActivityData[treasureData[treasure].activityUid];
                    }
                    if (AdminUsersUid.includes(content.uid)) {
                        return {
                            success: true,
                            treasureData: treasureData
                        }
                    } else {
                        let treasureDataUser = {};
                        for (treasure in treasureData) {
                            if (treasureData[treasure].creator == content.uid) {
                                treasureDataUser[treasure] = treasureData[treasure];
                            }
                        }
                        return {
                            success: true,
                            treasureData: treasureDataUser
                        }
                    }
                    break;
                default:
                    return {
                        success: false,
                        why: "Unknown type"
                    }
            }
        } else {
            return {
                success: false,
                why: "Missing type"
            }
        }
    } else if ("type" in content) {
        switch (content.type) {
            case "activity":
                return {
                    success: true,
                    activityData: ActivityData
                }
                break;
            default:
                return auth;
                break;
        }
    } else {
        return auth
    }
})

fastify.post('/addActivity', async (request, reply) => {
    let content = request.body;
    let auth = checkAuthetification(content);
    if (auth.success) {
        if (AdminUsersUid.includes(content.uid)) {
            if ("type" in content
            && "title" in content
            && "desc" in content
            && "start" in content
            && "where" in content) {
                let activityUid = makeid(16);
                if ("activityUid" in content) {
                    let activityExists = check(content, "activityUid", ActivityData)
                    if (activityExists.success) {
                        activityUid = content.activityUid;
                    } else {
                        return activityExists;
                    }
                }
                switch (content.type) {
                    case "event":
                        if ("end" in content) {
                            ActivityData[activityUid] = {
                                type: content.type,
                                title: content.title,
                                desc: content.desc,
                                start: content.start,
                                end: content.end,
                                where: content.where,
                            }
                            saveData(activityPath, ActivityData);
                            return {
                                success: true,
                                uid: activityUid,
                                activity: ActivityData[activityUid]
                            }
                        } else {
                            return {
                                success: false,
                                why: "Missing end"
                            }
                        }
                        break;
                    case "treasure":
                        ActivityData[activityUid] = {
                            type: content.type,
                            title: content.title,
                            desc: content.desc,
                            start: content.start,
                            where: content.where,
                            treasureState: "Pending"
                        }
                        saveData(activityPath, ActivityData);
                        return {
                            success: true,
                            uid: activityUid,
                            activity: ActivityData[activityUid]
                        }
                        break;
                    default:
                        return {
                            success: false,
                            why: "Unkonw type"
                        }
                }
            } else {
                return {
                    success: false,
                    why: "Missing type, title, desc, start, end or where"
                }
            }
        } else {
            return {
                success: false,
                why: "Not Allowed"
            }
        }
    }
})

fastify.post('/delActivity', async (request, reply) => {
    let content = request.body;
    let activityExists = checkManage(content, "activityUid", ActivityData)
    if (activityExists.success) {
        delete ActivityData[content.activityUid]
        saveData(activityPath, ActivityData);
        return {
            success: true,
        }
    } else {
        return activityExists
    }
})

fastify.post('/sendTreasure', async (request, reply) => {
    let content = request.body;
    let auth = checkAuthetification(content);
    if (auth.success) {
        let activityExists = check(content, "activityUid", ActivityData)
        if (activityExists.success) {
            if ("image" in content
            && "desc" in content
            && "activityUid" in content) {
                let treasureUid = makeid(16);
                if ("treasureUid" in content) {
                    let treasureExists = check(content, "treasureUid", TreasureData)
                    if (treasureExists.success) {
                        if (TreasureData[content.treasureUid].state != "Pending"
                        || TreasureData[content.treasureUid].creator != content.uid) {
                            return {
                                success: false,
                                why: "You cannot edit already accepted or refused treasure request, or request form other people"
                            }
                        } else {
                            treasureUid = content.treasureUid;
                        }
                    } else {
                        return treasureExists;
                    }
                }
                let activityExists = check(content, "activityUid", ActivityData)
                if (activityExists.success) {
                    if (ActivityData[content.activityUid].type == "treasure") {
                        let imageUid = makeid(128);
                        let fileImage = `
                            <!DOCTYPE html>
                            <html>
                                <body>
                                    <img style='object-fit: contain; width:100%; height:100%;' src='${content.image}'/>
                                </body>
                            </html>
                        `
                        fs.writeFileSync("static/images/" + imageUid + ".html", fileImage);
                        TreasureData[treasureUid] = {
                            date: new Date(),
                            creator: content.uid,
                            image: imageUid,
                            desc: content.desc,
                            activityUid: content.activityUid,
                            state: "Pending"
                        }
                        saveData(treasurePath, TreasureData);
                        return {
                            success: true,
                            uid: treasureUid,
                            treasure: TreasureData[treasureUid]
                        }
                    } else {
                        return {
                            success: false,
                            why: "The given activityUid refers to an event and not a treasure quest"
                        }
                    }
                } else {
                    return activityExists
                }
            } else {
                return {
                    success: false,
                    why: "Missing image, desc or activityUid"
                }
            }
        } else {
            return activityExists
        }
    } else {
        return auth
    }
})

fastify.post('/delTreasure', async (request, reply) => {
    let content = request.body;
    let auth = checkAuthetification(content);
    if (auth.success) {
        let treasureExists = check(content, "treasureUid", TreasureData)
        if (treasureExists.success) {
            if (TreasureData[content.treasureUid].creator === content.uid
            && TreasureData[content.treasureUid].state == "Pending") {
                delete TreasureData[content.treasureUid];
                saveData(treasurePath, TreasureData);
                return {
                    success: true,
                }
            } else {
                return {
                    success: false,
                    why: "You can't delete treasure that aren't yours or those already Accepted or Refused"
                }
            }
        } else {
            return treasureExists
        }
    } else {
        return auth
    }
})

fastify.post('/acceptTreasure', async (request, reply) => {
    let content = request.body;
    let treasureExists = checkManage(content, "treasureUid", TreasureData);
    if (treasureExists.success) {
        TreasureData[content.treasureUid].state = "Accepted";
        saveData(treasurePath, TreasureData);
        ActivityData[TreasureData[content.treasureUid].activityUid].treasureState = "Accepted";
        saveData(activityPath, ActivityData);
        GoldenUsers[TreasureData[content.treasureUid].activityUid] = {
            userUid: TreasureData[content.treasureUid].creator,
            activityUid: TreasureData[content.treasureUid].activityUid
        }
        saveData(goldenUsersPath, GoldenUsers);
        return {
            success: true,
        }
    } else {
        return treasureExists
    }
})

fastify.post('/refuseTreasure', async (request, reply) => {
    let content = request.body;
    let treasureExists = checkManage(content, "treasureUid", TreasureData);
    if (treasureExists.success) {
        TreasureData[content.treasureUid].state = "Refused";
        saveData(treasurePath, TreasureData);
        return {
            success: true,
        }
    } else {
        return treasureExists
    }
})

fastify.post('/isGolden', async (request, reply) => {
    let content = request.body;
    let auth = checkAuthetification(content);
    if (auth.success) {
        for (activityUid in GoldenUsers) {
            if (GoldenUsers[activityUid].userUid === content.uid) {
                return {
                    success: true,
                    userUid: content.uid,
                    activity: ActivityData[GoldenUsers[activityUid].activityUid]
                }
            }
        }
        return {
            success: false
        }
    } else {
        return auth
    }
})

function saveData(path, data) {
  fs.writeFileSync(path, JSON.stringify(data));
}

function checkAuthetification(content) {
    if (content.hasOwnProperty("uid")
    && content.hasOwnProperty("token")) {
        if (UsersToken.hasOwnProperty(content.uid)
        && UsersToken[content.uid].token === content.token) {
            if (UsersToken[content.uid].expire > new Date()) {
                return {
                    success: true
                }
            } else {
                delete UsersToken[content.uid];
                return {
                    success: false,
                    why: "Token expired"
                }
            }
        } else {
            return {
                success: false,
                why: "Not authentificated"
            }
        }
    } else {
        return {
            success: false,
            why: "Missing uid or token"
        }
    }
}

function check(content, input, data) {
    if (input in content) {
        if (content[input] in data) {
            return {
                success: true,
            }
        } else {
            return {
                success: false,
                why: "Unknow "+input
            }
        }
    } else {
        return {
            success: false,
            why: "Missing "+input
        }
    }
}

function checkManage(content, input, data) {
    let auth = checkAuthetification(content);
    if (auth.success) {
        if (AdminUsersUid.includes(content.uid)) {
            let exists = check(content, input, data)
            if (exists.success) {
                return {
                    success: true
                }
            } else {
                return exists
            }
        } else {
            return {
                success: false,
                why: "Not Allowed"
            }
        }
    } else {
        return auth
    }
}

function initFs() {
    if (!fs.existsSync(usersBdd)) {
        fs.writeFileSync(usersBdd, "{}");
    }
    if (!fs.existsSync(prankPath)) {
        fs.writeFileSync(prankPath, "{}");
    }
    if (!fs.existsSync(activityPath)) {
        fs.writeFileSync(activityPath, "{}");
    }
    if (!fs.existsSync(treasurePath)) {
        fs.writeFileSync(treasurePath, "{}");
    }
    if (!fs.existsSync(goldenUsersPath)) {
        fs.writeFileSync(goldenUsersPath, "{}");
    }
    if (!fs.existsSync(servicePath)) {
        fs.writeFileSync(servicePath, "{state: 'closed'}");
    }
    if (!fs.existsSync("static/images")){
        fs.mkdirSync("static/images");
    }
}

function makeid(length) {
    var result           = '';
    var characters       = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
    var charactersLength = characters.length;
    for (var i = 0; i < length; i++) {
        result += characters.charAt(Math.floor(Math.random() * charactersLength));
    }
    return result;
}

const start = async () => {
    try {
        await fastify.listen({ port: 3000 , host: '127.0.0.1',})
    } catch (err) {
        fastify.log.error(err)
        LDAP.close(function(err) {
            console.log(err);
        })
        process.exit(1)
    }
}
start()