const cfgHandle = require("./configHandler");
const logger = require("./logger");
const parser = require("./packet-parser");
const frontend = require("./frontend");
const websocket = require("./ws-handler");
const crypt = require("./crypt");
const EventEmitter = require('events');
const net = require('net');
const fs = require('fs');
const path = require('path');

const { API } = require('./api');

class Server extends EventEmitter {
    constructor () {
        super();
        this.on("config.update", ({
            oldConfig, newConfig
        }) => {

        });
    }

    run (address, port) {
        this.address = address;
        this.port = port;

        const wsPort = global.config.server?.staticInfo?.wsPort ?? 57891;
        const httpPort = global.config.server?.staticInfo?.httpPort ?? 49901;

        logger.log({
            wsPort, httpPort
        });

        this.api = new API();

        if (global.config.server.useFrontend)
            this.frontend = frontend("127.0.0.1", httpPort);
        if (global.config.server.useWebSocketAPI)
            this.websocket = websocket("127.0.0.1", wsPort, this.api);

        this.server = net.createServer((srvSocket) => {
            srvSocket.on("error", (err) => {
                logger.log("Caught server socket error: ");
                logger.log(err.stack);
            });
        });

        function socketErrHanler (err) {
            if (err) {
                logger.log("Caught server socket error: ");
                logger.log(err.stack);
            }
        }

        function socketDataHandler (data, socket) {
            socket.write(data, function(err) {
                if (err) {
                    logger.log(`error: ${socket.remoteAddress} (remote) throwed: ${err.name}\n${err.stack}`);
				}
            });
        }

        this.server.on("connection", (socket) => {
            logger.log(`${socket.remoteAddress}${socket.remotePort} is connected`);
            socket.mainconnect = null;

            if (global.config.server.useWebSocketAPI) {
                socket.wsReverse = net.createConnection(wsPort, "127.0.0.1");
                socket.wsReverse.on("error", socketErrHanler);
                socket.wsReverse.on("data", (data) => socketDataHandler(data, socket));
            }
            if (global.config.server.useFrontend) {
                socket.httpReverse = net.createConnection(httpPort, "127.0.0.1");
                socket.httpReverse.on("error", socketErrHanler);
                socket.httpReverse.on("data", (data) => socketDataHandler(data, socket));
            }

            socket.on("error", (err) => {
                logger.log(
                    `socket.on(${socket.remoteAddress}) error:`
                );
                logger.log(err.stack);
                try { socket.end(); } catch (_) {}
                // socket.end();
            });

            socket.on('data', async (data) => {
                const parserResult = await parser(data);
				// if (global.config.server.secureMode && data.toString().includes("http/1.1")) {
					// logger.log("Encrypted WS!");
					// parserResult.result.isWebSocket = true;
				// }
				
                // const parserDecryptedResult = await parser(global.openSSH.decrypt(data));
				const peerAddress = `${socket.remoteAddress}:${socket.remotePort}`;
                //logger.log("Parser result is", parserResult);

                if (!socket.mainconnect) {
                    // if (parserResult.result.isWebSocket || parserDecryptedResult.result.isWebSocket) {
                    if (parserResult.result.isWebSocket) {
                        socket.mainconnect = "ws";
						if (global.config.server.useFrontend)
							socket.httpReverse.end();
                        if (global.config.server.useWebSocketAPI) {
                            return socket.wsReverse.write(data, socketErrHanler);
                        }
                        return;
                    }
                    else if (parserResult.result.isHTTP) {
                        socket.mainconnect = "http";
						socket.wsReverse.end();
                        if (global.config.server.useFrontend) {
                            return socket.httpReverse.write(data, socketErrHanler);
                        }
                        return;
                    }
                    else {
						if (global.config.server.useFrontend)
							socket.httpReverse.end();
						if (global.config.server.useWebSocketAPI)
							socket.wsReverse.end();
                        socket.mainconnect = "tcp";
                        return await this.api.exec(peerAddress, data, () => {});
                    }
                }
                else {
                    switch (socket.mainconnect) {
                        case "ws":
                            if (global.config.server.useWebSocketAPI) {
                                return socket.wsReverse.write(data, socketErrHanler);
                            }
                        case "http":
                            if (global.config.server.useFrontend) {
                                return socket.httpReverse.write(data, socketErrHanler);
                            }
                        case "tcp":
                            return await this.api.exec(peerAddress, data);
                        default:
                            return;
                    }
                }
            });
        });

        this.server.on('error', (err) => {
            logger.log("Error of server");
            logger.log(err.stack);
        });

        this.server.listen(this.port, this.address, (err) => {
            if (err) throw err;
            logger.log(`TCP server for ai-adventure labs connected on ${this.address}:${this.port}`);
        });
    }
}
const defaultConfigData = {
    server: {
        address: "0.0.0.0",
        port: 8841,
        useWebSocketAPI: true,
        useFrontend: true,
        staticInfo: {
            wsPort: 57891,
            httpPort: 49901
        }
    },
    database: {
        dialect: "sqlite",
        path: "./data"
    },
    ai: {
        apiType: "kobold"
    }
};

const server = new Server();
cfgHandle(server, defaultConfigData, logger);

logger.log("Current config:", global.config);

server.run(global.config.server.address, global.config.server.port);

/*
if (global.config.server.secureMode) {
	async function initEncryption () {
		const datadir = global.config.server["ssl-path"] ?? path.join(__dirname, "/encryption");
		
		function generate () {
			logger.log("Generate new keys...");
			global.openSSH = crypt.generateOpenSSH({
				commonName: global.config.server.info.name
			}, {
				days: 365,
				pkcs7: true,
				clientCertificate: true,
				clientCertificateCN: "AI Adventure Labs",
				algorithm: 'sha256',
				keySize: 2048,
			});
			fs.writeFileSync(path.join(datadir, "public.pem"), global.openSSH.public_key.bytes);
			fs.writeFileSync(path.join(datadir, "private.pem"), global.openSSH.private_key.bytes);
			fs.writeFileSync(path.join(datadir, "cert.crt"), global.openSSH.cert);
		}
		
		const readFilePromise = new Promise((resolve, reject) => {
			fs.readFile(path.join(datadir, "public.pem"), (err, public_key) => {
				if (err) {
					return resolve(generate());
				}
				try {
					public_key = new crypt.CryptKey(public_key);
					const private_key = new crypt.CryptKey(fs.readFileSync(path.join(datadir, "private.pem")));
					const cert = fs.readFileSync(path.join(datadir, "cert.crt"));
					
					global.openSSH = new crypt.OpenSSH({
						public_key, private_key, cert
					});
					resolve();
				} catch (_) {
					resolve(generate());
				}
			});
		});
		
		await readFilePromise;
		
		logger.log(`loaded from ${datadir}`);
	};
	initEncryption().then(() => {
		server.run(global.config.server.address, global.config.server.port);
	}).catch(e => {
		throw e;
	});
}
else
	server.run(global.config.server.address, global.config.server.port);
*/