import { IJob, JobContext } from "@simeonradivoev/gameflow-sdk";
import secrets from "../secrets";
import open from "open";
import z from "zod";
import { delay } from "@/shared/utils";
import { plugins } from "../app";


interface TwitchDevice
{
    device_code: string,
    expires_in: number,
    expires_at: Date,
    started_at: Date,
    interval: number,
    user_code: string,
    verification_uri: string;
}

type States = "Retrieving Device" | "Waiting For Authentication";

export default class TwitchLoginJob implements IJob<z.infer<typeof TwitchLoginJob.dataSchema>, States>
{
    twitchScopes = "analytics:read:extensions analytics:read:games user:read:email";
    device?: TwitchDevice;
    clientId: string;
    openInBrowser: boolean;
    static id = 'twitch-login-job' as const;
    static dataSchema = z.object({ expires_at: z.date(), started_at: z.date(), url: z.url(), user_code: z.string() }).or(z.undefined());

    constructor(clientId: string, openInBrowser: boolean)
    {
        this.clientId = clientId;
        this.openInBrowser = openInBrowser;
    }

    exposeData = (): z.infer<typeof TwitchLoginJob.dataSchema> => this.device ? ({
        expires_at: this.device.expires_at,
        started_at: this.device.started_at,
        url: this.device.verification_uri,
        user_code: this.device.user_code
    }) : undefined;

    async start (context: JobContext<TwitchLoginJob, z.infer<typeof TwitchLoginJob.dataSchema>, States>): Promise<any>
    {
        context.setProgress(0, "Retrieving Device");
        let res = await fetch("https://id.twitch.tv/oauth2/device", {
            method: "POST",
            body: new URLSearchParams({
                client_id: this.clientId,
                scopes: this.twitchScopes
            }),
            signal: context.abortSignal
        });

        const device: TwitchDevice = await res.json();
        const expiredTimeout = setTimeout(() => context.abort('expired'), device.expires_in * 1000);
        device.expires_at = new Date(new Date().getTime() + device.expires_in * 1000);
        device.started_at = new Date();
        this.device = device;

        try
        {
            if (this.openInBrowser)
                open(device.verification_uri);
            this.device = device;
            context.setProgress(50, "Waiting For Authentication");

            while (true)
            {
                if (context.abortSignal.aborted) break;
                await delay(device.interval * 1000, context.abortSignal);

                res = await fetch("https://id.twitch.tv/oauth2/token", {
                    method: "POST",
                    body: new URLSearchParams({
                        client_id: this.clientId,
                        scopes: this.twitchScopes,
                        device_code: this.device.device_code,
                        grant_type: "urn:ietf:params:oauth:grant-type:device_code"
                    }),
                    signal: context.abortSignal
                });

                if (res.status === 200)
                {
                    const data: {
                        access_token: string,
                        expires_in: number,
                        refresh_token: string,
                        scope: string[],
                        token_type: string;
                    } = await res.json();

                    secrets.set({ service: 'gamflow_twitch', name: 'access_token', value: data.access_token });
                    secrets.set({ service: 'gamflow_twitch', name: 'refresh_token', value: data.refresh_token });
                    secrets.set({ service: 'gamflow_twitch', name: 'expires_in', value: new Date(new Date().getTime() + data.expires_in).toString() });

                    await plugins.hooks.auth.loginComplete.promise({ service: 'twitch' });
                    break;
                }
                else if (res.status !== 400)
                {
                    console.error(res.statusText);
                    break;
                }
            }

        } finally
        {
            clearTimeout(expiredTimeout);
        }
    }

}