/*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/>.*/

//NPM Imports
const validator = require('validator');//No express here, so regular validator it is!

//Local Imports
const tokebot = require('./tokebot');
const permissionModel = require('../../schemas/permissionSchema');
const channelModel = require('../../schemas/channel/channelSchema');

module.exports = class commandPreprocessor{
    constructor(server, chatHandler){
        this.server = server;
        this.chatHandler = chatHandler;
        this.commandProcessor = new commandProcessor(server, chatHandler);
        this.tokebot = new tokebot(server, chatHandler);
    }

    async preprocess(socket, data){
        //Set socket, data, and sendFlag
        this.socket = socket;
        this.sendFlag = true;
        this.rawData = data;
        this.chatType = 'chat';

        //If we don't pass sanatization/validation turn this car around
        if(!this.sanatizeCommand()){
            return;
        }

        //split the command
        this.splitCommand();
        //Process the command
        await this.processServerCommand();

        //If we're going to relay this command as a message, continue on to chat processing
        if(this.sendFlag){
            //Prep the message
            await this.prepMessage();
            //Send the chat
            this.sendChat();
        }
    }

    sanatizeCommand(){
        //Trim and Sanatize for XSS
        this.command = validator.trim(validator.escape(this.rawData.msg));

        //nuke the message if its empty or huge
        return (validator.isLength(this.command, {min: 1, max: 255}));
    }

    splitCommand(){
        //Split string by words
        this.commandArray = this.command.split(/\b/g);//Split by word-borders
        this.argumentArray = this.command.match(/\b\w+\b/g);//Match by words surrounded by borders
    }

    async processServerCommand(){
        //If the raw message starts with '!' (skip commands that start with whitespace so people can send example commands in chat)
        if(this.rawData.msg[0] == '!'){
            //if it isn't just an exclimation point, and we have a real command
            if(this.argumentArray != null){
                if(this.commandProcessor[this.argumentArray[0].toLowerCase()] != null){
                    //Process the command and use the return value to set the sendflag (true if command valid)
                    this.sendFlag = await this.commandProcessor[this.argumentArray[0].toLowerCase()](this);
                }else{
                    //Process as toke command if we didnt get a match from the standard server-side command processor
                    this.sendFlag = await this.tokebot.tokeProcessor(this);
                }
            }
        }
    }

    async markLinks(){
        const maxSize = 4000000;
        //Empty out the links array
        this.links = [];

        //For each link sent from the client
        //this.rawData.links.forEach((link) => {
        for (const link of this.rawData.links){
            //Set standard link type
            var type = 'link';

            //Don't try this at home, we're what you call "Experts"
            //TODO: Handle this shit simultaneously and send the chat before its done, then send updated types for each link as they're pulled individually
            try{
                //Pull content type
                var response = await fetch(link,{
                    method: "HEAD",
                });

                //Get file type from header
                const fileType = response.headers.get('content-type');
                const fileSize = response.headers.get('content-length');

                //If they're reporting file types
                if(fileType != null){
                    //If we have an image
                    if(fileType.match('image/')){
                        //If the file size is unreported OR it's smaller than 4MB (not all servers report this and images that big are pretty rare)
                        if(fileSize == null || fileSize <= maxSize){
                            //Mark link as an image
                            type = 'image';
                        }
                    //If it's a video
                    }else if(fileType.match('video/mp4' || 'video/webm')){
                        //If the server is reporting file-size and it's reporting under 4MB (Reject unreported sizes to be on the safe side is video is huge)
                        if(fileSize != null && fileSize <= maxSize){
                            //mark link as a video
                            type = 'video';
                        }
                    }
                }
            //Probably bad form but if something happens in here I'm blaming whoever hosted the link
            //maybe don't host a fucked up server and I wouldn't handle with an empty catch
            }catch{};

            //Add a marked link object to our links array
            this.links.push({
                link,
                type
            });

        }
    }

    async prepMessage(){
        //Create message from commandArray
        this.message = this.commandArray.join('').trimStart();
        //Validate links and mark them by embed type
        await this.markLinks();
    }

    sendChat(){
        //FUCKIN' SEND IT!
        this.chatHandler.relayUserChat(this.socket, this.message, this.chatType, this.links);
    }
}

class commandProcessor{
    constructor(server, chatHandler){
        this.server = server;
        this.chatHandler = chatHandler;
    }

    //Command keywords get run through .toLowerCase(), so we should use lowercase method names for command methods
    whisper(preprocessor){
        //splice out our command
        preprocessor.commandArray.splice(0,2);

        //Mark out the current message as a whisper
        preprocessor.chatType = 'whisper';

        //Make sure to throw the send flag
        return true
    }

    async announce(preprocessor){
        //Get the current channel from the database
        const chanDB = await channelModel.findOne({name: preprocessor.socket.chan});

        //Check if the user has permission, and publicly shame them if they don't (lmao)
        if(chanDB != null && await chanDB.permCheck(preprocessor.socket.user, 'announce')){
            //splice out our command
            preprocessor.commandArray.splice(0,2);

            //Prep the message using pre-processor functions chat-handling
            await preprocessor.prepMessage();

            //send it
            this.chatHandler.relayChannelAnnouncement(preprocessor.socket.chan, preprocessor.message, preprocessor.links);

            //throw send flag
            return false;
        }

        //throw send flag
        return true;
    }

    async serverannounce(preprocessor){
        //Check if the user has permission, and publicly shame them if they don't (lmao)
        if(await permissionModel.permCheck(preprocessor.socket.user, 'announce')){
            //splice out our command
            preprocessor.commandArray.splice(0,2);

            //Prep the message using pre-processor functions for chat-handling
            await preprocessor.prepMessage();

            //send it
            this.chatHandler.relayServerAnnouncement(preprocessor.message, preprocessor.links);

            //throw send flag
            return false;
        }

        //throw send flag
        return true;
    }

    async clear(preprocessor){
        //Get the current channel from the database
        const chanDB = await channelModel.findOne({name: preprocessor.socket.chan});

        //Check if the user has permission, and publicly shame them if they don't (lmao)
        if(await chanDB.permCheck(preprocessor.socket.user, 'clearChat')){
            //Send off the command
            this.chatHandler.clearChat(preprocessor.socket.chan, preprocessor.argumentArray[1]);
            //throw send flag
            return false;
        }

        //throw send flag
        return true;
    }
}