import { getHostData } from "@lib/diagnostics";
import { isProxy } from "util/types";
import { startReadyWatcher } from "./boot/startReadyWatcher";
import { Deployer } from "./deployer";
import { TxConfigState, type FxMonitorHealth } from "@shared/enums";
import type { GlobalStatusType } from "@shared/socketioTypes";
import quitProcess from "@lib/quitProcess";
import consoleFactory, { processStdioEnsureEol, setTTYTitle } from "@lib/console";
import { isNumber, isString } from "@modules/CacheStore";
const console = consoleFactory('Manager');

//Types
type gameNames = 'fivem' | 'redm';
type HostStatusType = {
    //txAdmin state
    cfgPath: string | null;
    dataPath: string | null;
    isConfigured: boolean;
    playerCount: number;
    status: FxMonitorHealth;

    //Detected at runtime
    cfxId: string | null;
    gameName: gameNames | null;
    joinLink: string | null;
    joinDeepLink: string | null;
    playerSlots: number | null;
    projectName: string | null;
    projectDesc: string | null;
}


/**
 * This class is for "high order" logic and methods that shouldn't live inside any specific component.
 */
export default class TxManager {
    public deployer: Deployer | null = null; //FIXME: implementar o deployer
    private readonly moduleShutdownHandlers: (() => void)[] = [];
    public isShuttingDown = false;

    //TODO: move txRuntime here?!

    constructor() {
        //Listen for shutdown signals
        process.on('SIGHUP', this.gracefulShutdown.bind(this));     //terminal closed
        process.on('SIGINT', this.gracefulShutdown.bind(this));     //ctrl+c (mostly users)
        process.on('SIGTERM', this.gracefulShutdown.bind(this));    //kill (docker, etc)

        //Sync start, boot fxserver when conditions are met
        startReadyWatcher(() => {
            txCore.fxRunner.signalStartReady();
        });

        //FIXME: mover o cron do FxMonitor (getHostStats() + websocket push) para cá
        //FIXME: if ever changing this, need to make sure the other data
        //in the status event will be pushed, since right some of now it
        //relies on this event every 5 seconds
        //NOTE: probably txManager should be the one to decide if stuff like the host
        //stats changed enough to merit a refresh push
        setInterval(async () => {
            txCore.webServer.webSocket.pushRefresh('status');
        }, 5000);

        //Updates the terminal title every 15 seconds
        setInterval(() => {
            setTTYTitle(`(${txCore.fxPlayerlist.onlineCount}) ${txConfig.general.serverName} - txAdmin`);
        }, 15000);

        //Pre-calculate static data
        setTimeout(() => {
            getHostData().catch((e) => { });
        }, 10_000);
    }


    /**
     * Gracefully shuts down the application by running all exit handlers.  
     * If the process takes more than 5 seconds to exit, it will force exit.
     */
    public async gracefulShutdown(signal: NodeJS.Signals) {
        //Prevent race conditions
        if (this.isShuttingDown) {
            processStdioEnsureEol();
            console.warn(`Got ${signal} while already shutting down.`);
            return;
        }
        console.warn(`Got ${signal}, shutting down...`);
        this.isShuttingDown = true;

        //Stop all module timers
        for (const moduleName of Object.keys(txCore)) {
            const module = txCore[moduleName as keyof typeof txCore] as GenericTxModuleInstance;
            if (Array.isArray(module.timers)) {
                for (const interval of module.timers) {
                    clearInterval(interval);
                }
            }
        }

        //Sets a hard limit to the shutdown process
        setTimeout(() => {
            console.error(`Graceful shutdown timed out after 5s, forcing exit...`);
            quitProcess(1);
        }, 5000);

        //Run all exit handlers
        await Promise.allSettled(this.moduleShutdownHandlers.map((handler) => handler()));
        console.verbose.debug(`All exit handlers finished, shutting down...`);
        quitProcess(0);
    }


    /**
     * Adds a handler to be run when txAdmin gets a SIG* event
     */
    public addShutdownHandler(handler: () => void) {
        this.moduleShutdownHandlers.push(handler);
    }


    /**
     * Starts the deployer (TODO: rewrite deployer)
     */
    startDeployer(
        recipeText: string | false,
        deploymentID: string,
        targetPath: string,
        isTrustedSource: boolean,
        customMetaData: Record<string, string> = {},
    ) {
        if (this.deployer) {
            throw new Error('Deployer is already running');
        }
        this.deployer = new Deployer(recipeText, deploymentID, targetPath, isTrustedSource, customMetaData);
    }


    // isDeployerRunning(): this is { deployer: Deployer } {
    //     return this.deployer !== null;
    // }


    /**
     * Unknown, Deployer, Setup, Ready
     */
    get configState() {
        if (isProxy(txCore)) {
            return TxConfigState.Unkown;
        } else if (this.deployer) {
            return TxConfigState.Deployer;
        } else if (!txCore.fxRunner.isConfigured) {
            return TxConfigState.Setup;
        } else {
            return TxConfigState.Ready;
        }
    }


    /**
     * Returns the status object that is sent to the host status endpoint
     */
    get hostStatus(): HostStatusType {
        const serverPaths = txCore.fxRunner.serverPaths;
        const cfxId = txCore.cacheStore.getTyped('fxsRuntime:cfxId', isString) ?? null;
        const isGameName = (val: any): val is gameNames => val === 'fivem' || val === 'redm';
        return {
            //txAdmin state
            isConfigured: this.configState === TxConfigState.Ready,
            dataPath: serverPaths?.dataPath ?? null,
            cfgPath: serverPaths?.cfgPath ?? null,
            playerCount: txCore.fxPlayerlist.onlineCount,
            status: txCore.fxMonitor.status.health,

            //Detected at runtime
            cfxId,
            gameName: txCore.cacheStore.getTyped('fxsRuntime:gameName', isGameName) ?? null,
            joinDeepLink: cfxId ? `fivem://connect/cfx.re/join/${cfxId}` : null,
            joinLink: cfxId ? `https://cfx.re/join/${cfxId}` : null,
            playerSlots: txCore.cacheStore.getTyped('fxsRuntime:maxClients', isNumber) ?? null,
            projectName: txCore.cacheStore.getTyped('fxsRuntime:projectName', isString) ?? null,
            projectDesc: txCore.cacheStore.getTyped('fxsRuntime:projectDesc', isString) ?? null,
        }
    }


    /**
     * Returns the global status object that is sent to the clients
     */
    get globalStatus(): GlobalStatusType {
        const fxMonitorStatus = txCore.fxMonitor.status;
        return {
            configState: txManager.configState,
            discord: txCore.discordBot.status,
            runner: {
                isIdle: txCore.fxRunner.isIdle,
                isChildAlive: txCore.fxRunner.child?.isAlive ?? false,
            },
            server: {
                name: txConfig.general.serverName,
                uptime: fxMonitorStatus.uptime,
                health: fxMonitorStatus.health,
                healthReason: fxMonitorStatus.healthReason,
                whitelist: txConfig.whitelist.mode,
            },
            scheduler: txCore.fxScheduler.getStatus(), //no push events, updated every Scheduler.checkSchedule()
        }
    }
}

export type TxManagerType = InstanceType<typeof TxManager>;
