import {
    CLIENT_JOB_EVENT_PREFIX,
    JOB_DELAY_EVENT_PREFIX,
    fetchStatus,
    getConfigurableStatusParameters,
    request,
    setCurrentlyFetchingStatus,
} from "@devowl-wp/real-queue";
import type { ClientJobEvent, JobDelayEvent } from "@devowl-wp/real-queue";
import { applyQueryString } from "@devowl-wp/utils";

import { SCAN_QUEUE_JOB } from "../../types/queue.js";
import { locationRestScannerScanWithoutLoginGet } from "../../wp-api/scannerScanWithoutLogin.get.js";

import type { ScanQueueJobData } from "../../types/queue.js";
import type {
    ParamsRouteScannerScanWithoutLoginGet,
    RequestRouteScannerScanWithoutLoginGet,
    ResponseRouteScannerScanWithoutLoginGet,
} from "../../wp-api/scannerScanWithoutLogin.get.js";

let lastExecutionTimeMs = 0;

/**
 * Add listener for the scanner status and automatically update the UI.
 */
function listenScannerJobExecution() {
    document.addEventListener(`${JOB_DELAY_EVENT_PREFIX}${SCAN_QUEUE_JOB}`, (({
        detail: { isIdle, settings },
    }: CustomEvent<JobDelayEvent<ScanQueueJobData>>) => {
        const isInScannerTab = /page=real-cookie-banner(?:-pro)?-component#\/scanner/.test(window.location.href);

        if (isInScannerTab || isIdle) {
            if (lastExecutionTimeMs > 0 && lastExecutionTimeMs < settings.delay_ms) {
                // When the last execution to scan a page was "fast" enough (lower than our default delay)
                // then we can use the execution time as our delay.
                settings.delay_ms = lastExecutionTimeMs / 2;
            }
        } else {
            settings.delay_ms = 3000;
        }
    }) as any);

    document.addEventListener(`${CLIENT_JOB_EVENT_PREFIX}${SCAN_QUEUE_JOB}`, (({
        detail: {
            job: {
                id,
                data: { url, isLoopback, allowFailure },
                process_total,
                group_position,
                group_total,
                group_uuid,
            },
            resolve,
            reject,
            saveJobResult,
        },
    }: CustomEvent<ClientJobEvent<ScanQueueJobData>>) => {
        const executeThis = async (url: string) => {
            const finalUrl = new URL(url);

            // Do not modify the URL when it e.g. is a redirection and has already the correct query parameters
            // Who knows? Perhaps a webserver redirects to a A-Z ordered query list?!
            const { searchParams } = finalUrl;
            const hasAlreadyCorrectParams =
                searchParams.get("rcb-scan") === "1" && searchParams.get("rcb-scan-job") === `${id}`;

            if (!hasAlreadyCorrectParams) {
                applyQueryString(
                    finalUrl,
                    [
                        {
                            "rcb-scan": `-${window.btoa(JSON.stringify(getConfigurableStatusParameters()))}-`,
                            "rcb-scan-job": id,
                        },
                    ],
                    true,
                );
            }

            const link = `<a href="${url}" target="_blank">${url}</a>`;

            try {
                let status: number;
                let ok: boolean;
                let statusText = "";
                let contentType: string;
                let isCors = false;
                let fetchStatusFrom = () => Promise.resolve("");
                let redirected = false;
                let responseUrl = "";
                if (isLoopback) {
                    const response = await request<
                        RequestRouteScannerScanWithoutLoginGet,
                        ParamsRouteScannerScanWithoutLoginGet,
                        ResponseRouteScannerScanWithoutLoginGet
                    >({
                        location: locationRestScannerScanWithoutLoginGet,
                        params: {
                            url: finalUrl.toString(),
                            jobId: id,
                        },
                    });
                    ({ status, statusText, ok, redirected, responseUrl } = response);
                    contentType = response.headers["content-type"] || undefined;
                    fetchStatusFrom = () => Promise.resolve(window.atob(response.body));
                } else {
                    const response = await window.fetch(finalUrl.toString(), {
                        mode: "no-cors",
                    });
                    const { type, headers } = response;
                    ({ status, statusText, ok, redirected, url: responseUrl } = response);
                    contentType = headers.get("content-type")?.toLowerCase();
                    isCors = type === "opaque";
                    fetchStatusFrom = () => response.text();
                }

                // Check if page could be scanned successfully
                if (redirected) {
                    // We cannot ensure 100% if this site got scanned successfully, let's do it again
                    executeThis(responseUrl);
                } else if (
                    ok ||
                    // See https://developer.mozilla.org/en-US/docs/Web/API/Response/type
                    // We should definitely ignore CORS
                    isCors
                ) {
                    await fetchStatus({
                        from: "html",
                        html: await fetchStatusFrom(),
                    });

                    // Immediate update the status to show at least "1 %" in tab
                    if (group_uuid && (group_position === group_total || group_position === 1)) {
                        fetchStatus(true);
                    }

                    // A cors request never can mark the job as complete, we need to manually do this
                    if (isCors) {
                        await saveJobResult(id, process_total);
                    }

                    // We requested a file (what does this do in our sitemap? Just ignore)
                    if (contentType && !contentType.startsWith("text/")) {
                        await saveJobResult(id, process_total);
                    }

                    resolve();
                } else if ([404, 410].indexOf(status) > -1 || allowFailure) {
                    // We request a site which does no longer exist (perhaps not deleted from sitemap?) -> ignore
                    await saveJobResult(id, process_total);
                    resolve();
                } else {
                    reject({
                        code: `invalid_response_${status}`,
                        message: `${statusText} (${link})`,
                    });
                }
            } catch (e) {
                reject({ code: "unexpected", message: `${e.toString()} (${link})` });
            }
        };

        const start = new Date().getTime();
        setCurrentlyFetchingStatus(true);
        executeThis(url).finally(() => {
            setCurrentlyFetchingStatus(false);
            lastExecutionTimeMs = new Date().getTime() - start;
        });
    }) as any);
}

export { listenScannerJobExecution };
