import {fork} from "child_process"
import {ChatInputCommandInteraction, Client, InteractionWebhook} from "discord.js"

export enum RestartReason {
    RestartCommand = "restart",
}

export enum StartState {
    NeverStarted = "never_started",
    Errored = "errored",
    Crashed = "crashed",
    Started = "started",
    Ready = "ready",
    Failed = "failed",
}

export interface StartData {
    state: StartState,
    status?: string
}

export interface RestartData {
    appId: string
    token: string
    reason: RestartReason
}

let restartState: RestartData | null = null

export async function checkIsRestart(): Promise<RestartData | null> {
    if (!process.connected) {
        return null
    } else {
        try {
            const result = await new Promise<RestartData | null>((resolve) => {
                process.on("message", (value: { restart?: RestartData }) => {
                    if (value && value.restart) {
                        resolve(value.restart)
                    } else {
                        resolve(null)
                    }
                })
                setTimeout(() => resolve(null), 50)
            })
            if (result !== null) {
                restartState = result
            }
            return result
        } catch (ex) {
            return null
        }
    }
}

export async function reportStarted(): Promise<void> {
    if (!process.send) {
        return
    }
    const startData: StartData = {
        state: StartState.Started,
    }
    process.send(startData)
}

export async function reportReady(c: Client<true>): Promise<boolean> {
    if (restartState) {
        try {
            const hook = new InteractionWebhook(c, restartState.appId, restartState.token)
            await hook.send({
                content: "yawwwwn... Good morning...",
            })
        } catch (ex) {
            console.log("followup failed to send", ex)
        }
    }
    if (!process.send) {
        return false
    }
    const startData: StartData = {
        state: StartState.Ready,
    }
    process.send(startData)
    return true
}

export async function reportFailed(c: Client, error: unknown): Promise<void> {
    console.log("failed to start", error)
    if (restartState) {
        try {
            const hook = new InteractionWebhook(c, restartState.appId, restartState.token)
            await hook.send({
                content: "Ugh... I can't get up... Head hurts... sorry...",
            })
        } catch (ex) {
            console.log("followup failed to send", ex)
        }
    }
    if (!process.send) {
        return
    }
    const startData: StartData = {
        state: StartState.Failed,
        status: `${error}`,
    }
    process.send(startData)
}

export async function wrappedRestart(b: ChatInputCommandInteraction) {
    try {
        await doRestart(b.client, b.webhook.id, b.webhook.token, RestartReason.RestartCommand)
    } catch (ex) {
        console.log("failed doing restart", ex)
        let data: StartData | null = null
        if (typeof ex === "object" && ex !== null && ex.hasOwnProperty("state")) {
            data = ex as StartData
        }
        if ((data && data.state)) {
            try {
                b.client.user.presence.set({
                    status: "invisible",
                    activities: [],
                })
                await b.followUp("Mhhhh... I don't feel good...")
            } catch (ex) {
                console.log("failed resetting presence and sending followup", ex)
            }
        }
    } finally {
        b.client.destroy()
    }
}

export async function doRestart(client: Client, appId: string, token: string,
                                restartReason: RestartReason): Promise<StartData> {
    return new Promise((resolve, reject) => {
        const child = fork(process.argv[1], process.argv.slice(2), {
            execPath: process.execPath,
            cwd: process.cwd(),
            detached: true,
            stdio: "inherit",
        })
        let result: StartData = {
            state: StartState.NeverStarted,
        }
        child.once("error", (err) => {
            result.state = StartState.Errored
            result.status = `${err}`
            if (child.connected) {
                child.unref()
                child.disconnect()
            }
            reject({...result})
        })
        child.once("exit", (code, signal) => {
            result.state = StartState.Crashed
            result.status = `Exited unexpectedly with code ${code} (signal ${signal})`
            if (child.connected) {
                child.unref()
                child.disconnect()
            }
            reject({...result})
        })
        child.on("message", (message: StartData) => {
            switch (typeof message === "object" && message.state) {
                case StartState.Started:
                    result.state = StartState.Started
                    break
                case StartState.Ready:
                    resolve({...message})
                    if (child.connected) {
                        child.unref()
                        child.disconnect()
                    }
                    break
                case StartState.Failed:
                    reject({...message})
                    if (child.connected) {
                        child.unref()
                        child.disconnect()
                    }
                    break
                default:
                    result.state = StartState.Failed
                    result.status = `Unexpected message: ${message}`
                    reject({...message})
                    if (child.connected) {
                        child.unref()
                        child.disconnect()
                    }
                    break
            }
        })
        process.on("disconnect", () => {
            result.state = StartState.Crashed
            reject({...result})
            child.unref()
        })
        child.send({restart: {appId, token, reason: restartReason}})
    })
}