/*Canopy - The next generation of stoner streaming software
Copyright (C) 2024  Rainbownapkin and the TTN Community

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero 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 Affero General Public License for more details.

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

class canopyAdminUtils{
    constructor(){

    }

    //Statics
    static banUserPopup = class{
        constructor(target, cb){
            this.target = target;
            this.cb = cb;
            this.popup = new canopyUXUtils.popup("userBan", true, this.asyncConstruction.bind(this));
        }

        asyncConstruction(){
            this.title = document.querySelector(".popup-title");
            //Setup title text real quick-like :P
            this.title.innerHTML = `Ban ${this.target}`;

            this.permBan = document.querySelector("#ban-popup-perm");
            this.expiration = document.querySelector("#ban-popup-expiration");
            this.expirationPrefix = document.querySelector("#ban-popup-expiration-prefix");
            this.banButton = document.querySelector("#ban-popup-ban-button");
            this.cancelButton = document.querySelector("#ban-popup-cancel-button");

            this.setupInput();
        }

        setupInput(){
            this.permBan.addEventListener("change", this.permaBanLabel.bind(this));
            this.cancelButton.addEventListener("click", this.popup.closePopup.bind(this.popup));
            this.banButton.addEventListener("click",this.ban.bind(this));
        }

        permaBanLabel(event){
            if(event.target.checked){
                this.expirationPrefix.innerHTML = "Account Deletion In: "
                this.expiration.value = 30;
            }else{
                this.expirationPrefix.innerHTML = "Ban Expires In: "
                this.expiration.value = 14;
            }
        }

        async ban(event){
            //Close out the popup
            this.popup.closePopup();

            //Submit the user ban based on input
            const data = await adminUtil.banUser(this.target, this.permBan.checked, this.expiration.value);

            //For some reason comparing this against undefined or null wasnt working in and of itself...
            if(data != null){
                //Why add an extra get request when we already have the data? :P
                await this.cb(data);
            }
        }
    }

    //Methods
    async setUserRank(user, rank){
        var response = await fetch(`/api/admin/changeRank`,{
            method: "POST",
            headers: {
                "Content-Type": "application/json"
            },
            //Unfortunately JSON doesn't natively handle ES6 maps, and god forbid someone update the standard in a way that's backwards compatible...
            body: JSON.stringify({user, rank})
        });

        if(response.status == 200){
            return await response.json();
        }else{
            utils.ux.displayResponseError(await response.json());
        }
    }

    async setPermission(permMap){
        var response = await fetch(`/api/admin/permissions`,{
            method: "POST",
            headers: {
                "Content-Type": "application/json"
            },
            //Unfortunately JSON doesn't natively handle ES6 maps, and god forbid someone update the standard in a way that's backwards compatible...
            body: JSON.stringify({permissionsMap: Object.fromEntries(permMap)})
        });

        if(response.status == 200){
            return await response.json();
        }else{
            utils.ux.displayResponseError(await response.json());
        }
    }

    async setChannelOverride(permMap){
        var response = await fetch(`/api/admin/permissions`,{
            method: "POST",
            headers: {
                "Content-Type": "application/json"
            },
            //Unfortunately JSON doesn't natively handle ES6 maps, and god forbid someone update the standard in a way that's backwards compatible...
            body: JSON.stringify({channelPermissionsMap: Object.fromEntries(permMap)})
        });

        if(response.status == 200){
            return await response.json();
        }else{
            utils.ux.displayResponseError(await response.json());
        }
    }

    async getBans(){
        var response = await fetch(`/api/admin/ban`,{
            method: "GET"
        });

        if(response.status == 200){
            return await response.json();
        }else{
            utils.ux.displayResponseError(await response.json());
        }
    }

    async banUser(user, permanent, expirationDays){
        var response = await fetch(`/api/admin/ban`,{
            method: "POST",
            headers: {
                "Content-Type": "application/json"
            },
            //Unfortunately JSON doesn't natively handle ES6 maps, and god forbid someone update the standard in a way that's backwards compatible...
            body: JSON.stringify({user, permanent, expirationDays})
        });

        if(response.status == 200){
            return await response.json();
        }else{
            utils.ux.displayResponseError(await response.json());
        }
    }

    async unbanUser(user){
        var response = await fetch(`/api/admin/ban`,{
            method: "DELETE",
            headers: {
                "Content-Type": "application/json"
            },
            //Unfortunately JSON doesn't natively handle ES6 maps, and god forbid someone update the standard in a way that's backwards compatible...
            body: JSON.stringify({user})
        });

        if(response.status == 200){
            return await response.json();
        }else{
            utils.ux.displayResponseError(await response.json());
        }
    }

    async getTokeCommands(){
        var response = await fetch(`/api/admin/tokeCommands`,{
            method: "GET"
        });

        if(response.status == 200){
            return await response.json();
        }else{
            utils.ux.displayResponseError(await response.json());
        }
    }

    async addTokeCommand(command){
        var response = await fetch(`/api/admin/tokeCommands`,{
            method: "POST",
            headers: {
                "Content-Type": "application/json"
            },
            //Unfortunately JSON doesn't natively handle ES6 maps, and god forbid someone update the standard in a way that's backwards compatible...
            body: JSON.stringify({command})
        });

        if(response.status == 200){
            return await response.json();
        }else{
            utils.ux.displayResponseError(await response.json());
        }
    }

    async deleteTokeCommand(command){
        var response = await fetch(`/api/admin/tokeCommands`,{
            method: "DELETE",
            headers: {
                "Content-Type": "application/json"
            },
            //Unfortunately JSON doesn't natively handle ES6 maps, and god forbid someone update the standard in a way that's backwards compatible...
            body: JSON.stringify({command})
        });

        if(response.status == 200){
            return await response.json();
        }else{
            utils.ux.displayResponseError(await response.json());
        }
    }

    async getEmotes(){
        var response = await fetch(`/api/admin/emote`,{
            method: "GET"
        });

        if(response.status == 200){
            return await response.json();
        }else{
            utils.ux.displayResponseError(await response.json());
        }
    }
}

class adminUserList{
    constructor(){
        this.rankSelectors = document.querySelectorAll(".admin-user-list-rank-select");
        this.banIcons = document.querySelectorAll(".admin-user-list-ban-icon");

        this.setupInput();
    }

    setupInput(){
        this.rankSelectors.forEach((rankSelector)=>{
            rankSelector.addEventListener("change", this.setRank.bind(this))
        });

        this.banIcons.forEach((banIcon) => {
            banIcon.addEventListener("click", this.banPopup.bind(this));
        })
    }

    async setRank(event){
            const user = event.target.id.replace("admin-user-list-rank-select-","");
            const rank = event.target.value;

            this.updateSelect(await adminUtil.setUserRank(user, rank), event.target);
    }

    banPopup(event){
        const user = event.target.id.replace("admin-user-list-ban-icon-","");
        new canopyAdminUtils.banUserPopup(user, userBanList.renderBanList.bind(userBanList));
    }

    updateSelect(update, select){
        if(update != null){
            select.value = update.rank;
        }
    }

}

class adminPermissionList{
    constructor(){
        this.permissionSelectors = document.querySelectorAll(".admin-perm-list-rank-select");
        this.channelPermissionSelectors = document.querySelectorAll(".admin-chan-perm-list-rank-select");

        this.setupInput();
    }

    setupInput(){
        this.permissionSelectors.forEach((permissionSelector)=>{
            permissionSelector.addEventListener("change", this.setPerm.bind(this))
        });

        this.channelPermissionSelectors.forEach((permissionSelector)=>{
            permissionSelector.addEventListener("change", this.setChanPerm.bind(this))
        });
    }

    async setPerm(event){
        const permMap = new Map([[event.target.id.replace("admin-perm-list-rank-select-",""), event.target.value]]);

        this.updateSelect(await adminUtil.setPermission(permMap), event.target);
    }

    async setChanPerm(event){
        const permMap = new Map([[event.target.id.replace("admin-chan-perm-list-rank-select-",""), event.target.value]]);

        this.updateChanSelect(await adminUtil.setChannelOverride(permMap), event.target);
    }

    updateSelect(update, select){
        if(update != null){
            var perm = select.id.replace("admin-perm-list-rank-select-","");

            select.value = update[perm];
        }
    }

    updateChanSelect(update, select){
        if(update != null){
            var perm = select.id.replace("admin-chan-perm-list-rank-select-","");

            select.value = update.channelOverrides[perm];
        }
    }
}

class adminUserBanList{
    constructor(){
        this.table = document.querySelector("#admin-ban-list-table");
        
        this.getBanList();
    }

    async getBanList(){
        this.renderBanList(await adminUtil.getBans());
    }

    clearBanList(){
        const oldRows = this.table.querySelectorAll('tr.gen-row');
        oldRows.forEach((row) => {
            row.remove();
        });
    }

    async unban(event){
        //Get username from target id
        const user = event.target.id.replace("admin-user-list-unban-icon-","");
        //Send unban command to server and display the resulting banlist
        this.renderBanList(await adminUtil.unbanUser(user));
    }

    renderBanList(banList){
        this.clearBanList();
        banList.forEach((ban) => {
            //Calculate expiration date and expiration days
            var expirationDateString = `${new Date(ban.expirationDate).toDateString()}<br>(${ban.daysUntilExpiration} day(s) left)`;
            var banActionString = ban.permanent ? "Nuke<br>Accounts" : "Un-Ban";
            if(ban.user == null){
                //Fudge the user object if it's already been deleted
                ban.user = {
                    img: "/img/nuked.png",
                    id: "-",
                    user: ban.deletedNames[0] ? ban.deletedNames[0] : "UNKNOWN",
                    deleted: true
                };

                //Fake the display string
                var signUpDateString = "-"
                expirationDateString = "Accounts<br>Nuked"
                banActionString = "Accounts<br>Nuked"

            }else{
                var signUpDateString = new Date(ban.user.date).toDateString()
            }
            //Create entry row
            const entryRow = document.createElement('tr');
            entryRow.classList.add("admin-list-entry");

            //Create IMG node inside of IMG cell
            const imgNode = document.createElement('img');
            imgNode.classList.add("admin-list-entry","admin-list-entry-item");
            imgNode.src = ban.user.img;

            //Create unban icon
            const unbanIcon = document.createElement('i');
            unbanIcon.classList.add("bi-emoji-smile-fill","admin-user-list-icon","admin-user-list-unban-icon");
            unbanIcon.id = `admin-user-list-unban-icon-${ban.user.user}`;
            unbanIcon.title = `Unban ${ban.user.user}`;
            unbanIcon.addEventListener("click", this.unban.bind(this));

            //Create nuke account icon
            const nukeAccount = document.createElement('i');
            nukeAccount.classList.add("bi-radioactive","admin-user-list-icon","admin-user-list-unban-icon");
            nukeAccount.id = `admin-user-list-unban-icon-${ban.user.user}`;
            nukeAccount.title = `Nuke accounts`;
            nukeAccount.addEventListener("click",console.log);

            //append img cell to row
            entryRow.appendChild(utils.ux.newTableCell(imgNode, true));

            //Generate and append row to table
            this.table.appendChild(utils.ux.newTableRow([
                ban.user.id,
                ban.user.user,
                signUpDateString,
                new Date(ban.banDate).toDateString(),
                expirationDateString,
                banActionString,
                (ban.user.deleted ? unbanIcon : [unbanIcon, nukeAccount])
            ]));
        });
    }
}

class adminTokeCommandList{
    constructor(){
        this.tokeCommandList = document.querySelector('div.toke-command-list');
        this.newTokeCommandPrompt = document.querySelector('#new-toke-command-input');
        this.newTokeCommandButton = document.querySelector('#new-toke-command-button');

        //Setup input
        this.setupInput();

        //Pull the toke list on load
        this.getTokeList();
    }

    setupInput(){
        this.newTokeCommandButton.addEventListener('click', this.addToke.bind(this));
    }

    async addToke(event){
        //Send out the new toke command and get the new list
        const tokeList = await adminUtil.addTokeCommand(this.newTokeCommandPrompt.value);
        //clear the prompt
        this.newTokeCommandPrompt.value = "";
        //render the returned list
        this.renderTokeList(tokeList);
    }

    async getTokeList(){
        const tokeList = await adminUtil.getTokeCommands();
        this.renderTokeList(tokeList);
    }

    clearTokeList(){
        this.tokeCommandList.innerHTML = "";
    }

    async deleteToke(event){
        const name = event.target.id.replace("toke-command-delete-","");

        const tokeList = await adminUtil.deleteTokeCommand(name);

        this.renderTokeList(tokeList);
    }

    renderTokeList(tokeList){
        if(tokeList != null){
            //Clear our the toke list
            this.clearTokeList();

            //For each toke in the received list
            tokeList.forEach((toke)=>{
                //generate a toke command span, and append it to the toke list div
                this.tokeCommandList.appendChild(this.generateTokeSpan(toke));
            });
        }
    }

    generateTokeSpan(toke){
        //Create toke command span
        const tokeSpan = document.createElement('span');
        tokeSpan.classList.add('toke-command-list');

        //Create toke command label
        const tokeLabel = document.createElement('p');
        tokeLabel.innerHTML = `!${toke}`;
        tokeLabel.classList.add('toke-command-list');

        //Create toke command delete icon
        const tokeDelete = document.createElement('i');
        tokeDelete.classList.add('toke-command-list', 'bi-trash-fill', 'toke-command-delete');
        tokeDelete.id = `toke-command-delete-${toke}`;
        tokeDelete.addEventListener('click', this.deleteToke.bind(this));

        //append span contents to tokeSpan
        tokeSpan.appendChild(tokeLabel);
        tokeSpan.appendChild(tokeDelete);

        //return the toke span
        return tokeSpan
    }
}

const adminUtil = new canopyAdminUtils();
const userList = new adminUserList();
const permissionList = new adminPermissionList();
const userBanList = new adminUserBanList();
const tokeCommandList = new adminTokeCommandList();