'use strict'

import { getLogger } from 'frontblock-generic/Types';
import { promises as fs, mkdirSync } from "fs"
import { RPCServer } from 'rpclibrary'
import * as Path from 'path'
import * as Knex from  'knex';
import * as http from 'http';
import * as express from 'express';
import { GuildManager } from '../Components/Guild/GuildManager';
import { ItemManager } from '../Components/Item/ItemManager';
import { RaidManager } from '../Components/Raid/RaidManager';
import { CharacterManager } from '../Components/User/CharacterManager';
import { LoginManager } from '../Components/User/LoginManager';
import { RootComponent } from '../Injector/ServiceDecorator';
import { TableDefinitionExporter } from '../Types/Interfaces';
import { AdminConf, TableDefiniton } from '../Types/Types';
import { RPCConfigLoader } from '../Components/RPCConfigLoader';
import { FrontworkComponent } from '../Types/FrontworkComponent';
import { IAdmin } from './Interface';


const logger = getLogger("admin", 'debug') 

@RootComponent({
    rootInterface: IAdmin,
    imports: [
        GuildManager,
        ItemManager,
        RaidManager,
        CharacterManager,
        LoginManager
    ]
})
export class FrontworkAdmin 
implements TableDefinitionExporter, IAdmin {
    knex:Knex
    config: RPCConfigLoader<AdminConf>
    rpcServer: RPCServer

    private express
    private httpServer

    constructor(private frontworkComponents: FrontworkComponent[] = []){
        this.config = new RPCConfigLoader<AdminConf>({
            name: "FrontworkAdminConf", 
            getDefaultConfig: () => {
                return {
                    httpPort: 8080,
                    eventBusConf: {},
                    dbConf: {
                        client: 'sqlite3',
                        connection: {
                            filename: Path.join(__dirname, "data/frontworkAdmin.sqlite")
                        },
                        useNullAsDefault: true
                    }
                }   
            }
        }, './config', this.configChangeHandler) 
    }

    async start(){
        await this.makeKnex()
        this.startWebsocket()
        await Promise.all( this.frontworkComponents.map(c => c.initialize?c.initialize():undefined ))
        this.startWebserver()
    }

    stop(){
        process.exit(0);
    }

    protected configChangeHandler = (conf:AdminConf, key?:string) => {
        if(key === 'dbConf'){
            this.makeKnex()
        }
    }

    getConfigKey(key:string){
        return this.config.getConfigKey(key)
    }

    setConfigKey(key:string, value:any){
        return this.config.setConfigKey(key, value)
    }

    getTableDefinitions(): TableDefiniton[]{
        return [
            ...this.frontworkComponents
        ].flatMap(exporter => exporter.getTableDefinitions())
    }

    private startWebsocket(){
        this.rpcServer = new RPCServer(20000, [
            ...this.frontworkComponents,
        ])
    }

    private startWebserver(){
        if(this.httpServer != null || this.express != null){
            logger.warn("Webserver is already running")
            return
        }
        
        let port:number = this.config.getConfig().httpPort
        this.express = express()
        this.express.use('/', express.static('dist/static'))

        /**
         * get the compiled FrontendPlugins.js
         */
        this.express.get('/plugins/:id'+".js", async (request, response) => {
            const pth = Path.resolve("plugins/"+request.params.id, "FrontendPlugin.js");
            const file = await fs.readFile(pth)
            const frontend = file.toString()

            response.status(200)
            response.set('Content-Type', 'application/javascript')
            response.send(frontend)
        })

        /**
         * serve the index.html from the static folder
         */
        this.express.get("/", (request, response) => {
            response.status(200)
            response.sendFile('index.html');
        })

        /**
         * redirect all the other traffic to the single page app
         */
        this.express.get("*", (request, response) => {
            response.status(301)
            response.redirect('/')
        })

        this.httpServer = new http.Server(this.express)
        this.httpServer.listen(port, () => {
            logger.info('Admin panel listening for HTTP on *'+port)
        })
    }

    private stopWebserver(){
        if(this.httpServer == null || this.express == null){
            logger.warn("Webserver is not running")
            return
        }
        this.httpServer.close()
        this.httpServer = null
        this.express = null
        logger.info("Webserver stopped")
    }

    async makeKnex():Promise<Knex>{
        const conf:Knex.Config = this.config.getConfigKey("dbConf")

        logger.debug("Making new knex:", conf)        
        if(conf.client === 'sqlite3'){
            mkdirSync(Path.dirname((<any>conf.connection).filename), {recursive: true})
        }

        this.knex = Knex(conf)
    
        await Promise.all(
            this.getTableDefinitions()
            //make unique by name
            .filter((other, index, self) => index === self.findIndex(
                (self) => self.name === other.name)
            )
            //create table if not exists
            .map(async (def)=> {
                const hasTable = await this.knex.schema.hasTable(def.name)
                if(!hasTable)
                    return await this.knex.schema.createTable(def.name, def.tableBuilder)
            })
        )
        
        return this.knex
    }
}

process.on( 'SIGINT', function() {
    logger.info("Shutting down from SIGINT (Ctrl-C)" );
    // some other closing procedures go here
    process.exit(0);
})
