import {
    onBeforeUnmount,
    ref,
    toValue,
} from 'vue';
import type { MaybeRefOrGetter, Ref } from 'vue';
import { useKviewToast } from '@kview/ui-core/vue';

interface ExcelLoginPayload {
    token: string;
    url: string;
}

interface ExcelLoginOptions {
    launchProtocol: (url: string) => void;
    timeoutMs?: number;
}

export interface ApplicationExcelLogin {
    pending: Ref<boolean>;
    request: () => Promise<ExcelLoginPayload | null>;
}

export const buildExcelProtocolUrl = (payload: ExcelLoginPayload): string => (
    `lncom://token=${encodeURIComponent(payload.token)}`
    + `&url=${encodeURIComponent(payload.url)}`
    + `&encoded_url=${encodeURIComponent(payload.url)}`
);

const isExcelLoginPayload = (value: unknown): value is ExcelLoginPayload => {
    if (!value || typeof value !== 'object') {
        return false;
    }

    const payload = value as Partial<ExcelLoginPayload>;

    return typeof payload.token === 'string' && typeof payload.url === 'string';
};

export const useApplicationExcelLogin = (
    endpoint: MaybeRefOrGetter<string>,
    options: ExcelLoginOptions,
): ApplicationExcelLogin => {
    const toast = useKviewToast();
    const pending = ref(false);
    const timeoutMs = options.timeoutMs ?? 10_000;
    let activeRequest: Promise<ExcelLoginPayload | null> | null = null;
    let controller: AbortController | null = null;
    let timeoutId: number | null = null;
    let disposed = false;
    let timedOut = false;

    const finish = (): void => {
        if (timeoutId !== null) {
            window.clearTimeout(timeoutId);
        }

        timeoutId = null;
        controller = null;
        activeRequest = null;
        pending.value = false;
        timedOut = false;
    };

    const request = (): Promise<ExcelLoginPayload | null> => {
        if (activeRequest) {
            return activeRequest;
        }

        const tokenEndpoint = toValue(endpoint).trim();

        if (!tokenEndpoint) {
            toast.error({
                title: 'Excel login failed',
                message: 'The desktop login endpoint is unavailable.',
            });

            return Promise.resolve(null);
        }

        controller = new AbortController();
        pending.value = true;
        toast.info({
            title: 'Excel login',
            message: 'Preparing a secure desktop login token...',
            duration: 5_000,
        });

        timeoutId = window.setTimeout(() => {
            timedOut = true;
            controller?.abort();
        }, timeoutMs);

        activeRequest = fetch(tokenEndpoint, {
            method: 'GET',
            headers: {
                Accept: 'application/json',
                'X-Requested-With': 'XMLHttpRequest',
            },
            credentials: 'same-origin',
            signal: controller.signal,
        }).then(async (response) => {
            if (!response.ok) {
                if (response.status === 503) {
                    const errorPayload: unknown = await response.json().catch(() => null);
                    if (errorPayload && typeof errorPayload === 'object'
                        && 'error' in errorPayload && errorPayload.error === 'excel_login_unavailable') {
                        throw new Error('excel_login_unavailable');
                    }
                }
                throw new Error('Unable to create Excel login token.');
            }

            const payload: unknown = await response.json();

            if (!isExcelLoginPayload(payload)) {
                throw new Error('Invalid Excel login token response.');
            }

            if (!disposed) {
                options.launchProtocol(buildExcelProtocolUrl(payload));
                toast.success({
                    title: 'Excel login ready',
                    message: 'The login request was sent to the Excel add-in.',
                    duration: 5_000,
                });
            }

            return payload;
        }).catch((error: unknown) => {
            if (disposed) {
                return null;
            }

            const aborted = error instanceof DOMException
                ? error.name === 'AbortError'
                : error instanceof Error && error.name === 'AbortError';

            toast.error({
                title: 'Excel login failed',
                message: aborted && timedOut
                    ? 'The Excel add-in login request timed out.'
                    : error instanceof Error && error.message === 'excel_login_unavailable'
                        ? 'Excel login is temporarily unavailable. Please contact your administrator.'
                    : 'Refresh the page and try again.',
                duration: 5_000,
            });

            return null;
        }).finally(finish);

        return activeRequest;
    };

    onBeforeUnmount(() => {
        disposed = true;
        controller?.abort();
        finish();
    });

    return { pending, request };
};
