import { RPCExporter } from "rpclibrary";
import { Git } from "upgiter"
import { FolderStatus } from "upgiter/js/src/Types";
import { Plugin } from "../Types/Plugin";
import { FrontworkAdmin } from "../Admin/Admin";

class PluginLoader {
    private runningPlugins: Plugin[] = []
    private pluginUpdaters:{[name in string]:Git.Updater} = {}

    constructor(private admin:FrontworkAdmin){}

    async selfUpdate(force:boolean = false){
        const updater = new Git.Updater({
            schema: 'https',
            localPath: './dist',
            remoteHost: 'www.versioncontrol.me',
            remotePath: 'frontwork-distribution',
            repoName: 'admin'
        })

        let status = await updater.getStatus()
        if(force || !status.remote || !status.remote.includes("fb-dist/admin") || !status.exists || status.empty || !status.currentTag){
            //logger.warn("Cloning fb-dist/admin into ./dist ..."+(force?" USING FORCE!":""))
            status = await updater.cloneRepo(force)
            const installer = eval("require")("./Installer").installAdmin
            installer(this.getPlugins())
        }
        return status
    }

    async installPlugin(name: string, force:boolean = false):Promise<FolderStatus>{
        //logger.warn("Cloning fb-dist/"+name+".git into ./plugins/"+name+" ..."+(force?" USING FORCE!":""))
        this.pluginUpdaters[name] = new Git.Updater({
            schema: 'https',
            localPath: './plugins/'+name,
            remoteHost: 'www.versioncontrol.me',
            remotePath: 'frontblock-distribution',
            repoName: name
        })
        const status = await this.pluginUpdaters[name].cloneRepo(force)
        return status
    }

    async startPlugin(name:string):Promise<boolean>{
        if(!this.pluginUpdaters[name]) return false
        const status = await this.pluginUpdaters[name].getStatus()
        if(!status.exists || status.empty || !status.tags || status.tags.length === 0){
            if(status.currentTag && !status.latestTag){
                //git glitches sometimes if you check immediately after clone
                //logger.warn("re-fetching tag for "+name+"...")
                return await this.startPlugin(name)
            }
            //logger.error("Bad repo status", name, status)
            return false
        }

/*        if(this.loadedPlugins[name]){
            logger.error("Plugin", name, "is already started")
            return false
        }
*/

        let evalstr = "../plugins/"+name+"/Plugin"
        const pluginClass = await eval('require')(evalstr)
        const pluginObj = new pluginClass.default(this.admin)
        try{
            if(pluginObj.start)
                await pluginObj.start()
            //this.pushNotification({message: "Started plugin "+pluginObj.name, severity: "Important", topic:"admin"})
            this.addPlugin(pluginObj)
            return true
        }catch(e){
            //logger.error(e)
            //this.pushNotification({message: "Start of plugin "+pluginObj.name+" failed because of "+String(e), severity: "Error", topic:"admin"})
            return false
        }
    }

    async updatePlugin(name:string):Promise<boolean>{
        if(!this.pluginUpdaters[name]) return false
        const status = await this.pluginUpdaters[name].getStatus()
        if(!status.exists || status.empty || !status.tags || status.tags.length === 0){
            //logger.error("Bad repo status", name, status)
            return false
        }
        if(status.currentTag == status.latestTag){
            //logger.warn(name, "already at latest tag")
            return false
        }

        /*
        if(this.loadedPlugins[name]){
            logger.error("Plugin", name, "is running. Stop it first")
            return false
        }
        */

        this.pluginUpdaters[name].checkoutTag(status.latestTag!)
        return true
    }

    async setPluginVersion(pluginName:string, tag:string):Promise<FolderStatus>{
        const status = await this.pluginUpdaters[pluginName].getStatus()
        if(!status.exists || !status.tags || status.tags.length === 0 || !status.tags.includes(tag)){
            //logger.error("Bad repo status", pluginName, status)
            return status
        }

        return await this.pluginUpdaters[pluginName].checkoutTag(tag)
    }

    async addPlugin(plugin: Plugin){
        this.runningPlugins.push(plugin)
        if(plugin.getTableDefinitions().length != 0)
            await this.admin.makeKnex()
    }

    removePlugin(plugin: Plugin){
        this.runningPlugins = this.runningPlugins.filter(p => p.name !== plugin.name)
    }

    getPlugins():Plugin[]{
        return [...this.runningPlugins]
    }
}


export type PluginLoaderIfc = {
    PluginLoader: {
        installPlugin: (name:string, force:boolean) => Promise<FolderStatus>
        startPlugin: (name:string) => Promise<boolean>
        updatePlugin: (name:string) => Promise<boolean>
        setPluginVersion: (name:string, tag:string) => Promise<FolderStatus>
        getLoadedPluginNames: () => Promise<String[]>
        selfUpdate: (force:boolean) => Promise<FolderStatus>
    }
}

export class RPCPluginLoader 
extends PluginLoader
implements RPCExporter<PluginLoaderIfc, "PluginLoader">{

    name = "PluginLoader" as "PluginLoader";

    exportRPCs(){
        return [{
            name: "installPlugin" as "installPlugin",
            call: this.installPlugin,
        },{
            name: "startPlugin" as "startPlugin",
            call: this.startPlugin,
        },{
            name: "updatePlugin" as "updatePlugin",
            call: this.updatePlugin,
        },{
            name: "setPluginVersion" as "setPluginVersion",
            call: this.setPluginVersion,
        },{
            name: "getLoadedPluginNames" as "getLoadedPluginNames",
            call: async () => this.getPlugins().map(p => p.name),
        },{
            name: "selfUpdate" as "selfUpdate",
            call: this.selfUpdate,
        }]
    }
}