import { suppressDebugConsole } from "@tests/vitest/helpers";
import flushPromises from "flush-promises";
import { beforeEach, describe, expect, it } from "vitest";

import { useServerMock } from "@/api/client/__mocks__";
import { useTaskMonitor } from "@/composables/taskMonitor";

import type { StoredTaskStatus } from "./genericTaskMonitor";

const PENDING_TASK_ID = "pending-fake-task-id";
const COMPLETED_TASK_ID = "completed-fake-task-id";
const FAILED_TASK_ID = "failed-fake-task-id";
const REQUEST_FAILED_TASK_ID = "request-failed-fake-task-id";

const { server, http } = useServerMock();

describe("useTaskMonitor", () => {
    beforeEach(() => {
        server.use(
            http.get("/api/tasks/{task_id}/state", ({ response, params }) => {
                switch (params.task_id) {
                    case PENDING_TASK_ID:
                        return response(200).json("PENDING");

                    case COMPLETED_TASK_ID:
                        return response(200).json("SUCCESS");

                    case FAILED_TASK_ID:
                        return response(200).json("FAILURE");

                    case REQUEST_FAILED_TASK_ID:
                        return response("5XX").json({ err_msg: "Request failed", err_code: 500 }, { status: 500 });

                    default:
                        return response("4XX").json({ err_msg: "Not found", err_code: 404 }, { status: 404 });
                }
            }),
            http.get("/api/tasks/{task_id}/result", ({ response, params }) => {
                switch (params.task_id) {
                    case PENDING_TASK_ID:
                        return response(200).json({ state: "PENDING", result: "" });

                    case COMPLETED_TASK_ID:
                        return response(200).json({ state: "SUCCESS", result: "" });

                    case FAILED_TASK_ID:
                        return response(200).json({ state: "FAILURE", result: "The failure reason" });

                    case REQUEST_FAILED_TASK_ID:
                        return response("5XX").json({ err_msg: "Request failed", err_code: 500 }, { status: 500 });

                    default:
                        return response("4XX").json({ err_msg: "Not found", err_code: 404 }, { status: 404 });
                }
            }),
        );
    });

    it("should indicate the task is running when it is still pending", async () => {
        const { waitForTask, isRunning, taskStatus } = useTaskMonitor();

        expect(isRunning.value).toBe(false);
        waitForTask(PENDING_TASK_ID);
        await flushPromises();
        expect(isRunning.value).toBe(true);
        expect(taskStatus.value).toBe("PENDING");
    });

    it("should indicate the task is successfully completed when the state is SUCCESS", async () => {
        const { waitForTask, isRunning, isCompleted, taskStatus } = useTaskMonitor();

        expect(isCompleted.value).toBe(false);
        waitForTask(COMPLETED_TASK_ID);
        await flushPromises();
        expect(isCompleted.value).toBe(true);
        expect(isRunning.value).toBe(false);
        expect(taskStatus.value).toBe("SUCCESS");
    });

    it("should indicate the task has failed when the state is FAILED", async () => {
        const { waitForTask, isRunning, hasFailed, taskStatus, failureReason } = useTaskMonitor();

        expect(hasFailed.value).toBe(false);
        waitForTask(FAILED_TASK_ID);
        await flushPromises();
        expect(hasFailed.value).toBe(true);
        expect(isRunning.value).toBe(false);
        expect(taskStatus.value).toBe("FAILURE");
        expect(failureReason.value).toBe("The failure reason");
    });

    it("should indicate the task status request failed when the request failed", async () => {
        const { waitForTask, requestHasFailed, isRunning, isCompleted, taskStatus } = useTaskMonitor();
        suppressDebugConsole();

        expect(requestHasFailed.value).toBe(false);
        waitForTask(REQUEST_FAILED_TASK_ID);
        await flushPromises();
        expect(requestHasFailed.value).toBe(true);
        expect(isRunning.value).toBe(false);
        expect(isCompleted.value).toBe(false);
        expect(taskStatus.value).toBe("Request failed");
    });

    it("should load the status from the stored monitoring data", async () => {
        const { loadStatus, isRunning, isCompleted, hasFailed, taskStatus } = useTaskMonitor();
        const expectedStatus = "SUCCESS";
        const storedStatus: StoredTaskStatus = {
            taskStatus: expectedStatus,
        };

        loadStatus(storedStatus);

        expect(taskStatus.value).toBe(expectedStatus);
        expect(isRunning.value).toBe(false);
        expect(isCompleted.value).toBe(true);
        expect(hasFailed.value).toBe(false);
    });

    it("should load the status from the stored monitoring data with failure reason", async () => {
        const { loadStatus, isRunning, isCompleted, hasFailed, taskStatus, failureReason } = useTaskMonitor();
        const expectedStatus = "FAILURE";
        const expectedFailureReason = "The stored failure reason";
        const storedStatus: StoredTaskStatus = {
            taskStatus: expectedStatus,
            failureReason: expectedFailureReason,
        };

        loadStatus(storedStatus);

        expect(taskStatus.value).toBe(expectedStatus);
        expect(isRunning.value).toBe(false);
        expect(isCompleted.value).toBe(false);
        expect(hasFailed.value).toBe(true);
        expect(failureReason.value).toBe(expectedFailureReason);
    });

    describe("isFinalState", () => {
        it("should indicate is final state when the task is completed", async () => {
            const { waitForTask, isFinalState, isRunning, isCompleted, hasFailed, taskStatus } = useTaskMonitor();

            expect(isFinalState(taskStatus.value)).toBe(false);
            waitForTask(COMPLETED_TASK_ID);
            await flushPromises();
            expect(isFinalState(taskStatus.value)).toBe(true);
            expect(isRunning.value).toBe(false);
            expect(isCompleted.value).toBe(true);
            expect(hasFailed.value).toBe(false);
        });

        it("should indicate is final state when the task has failed", async () => {
            const { waitForTask, isFinalState, isRunning, isCompleted, hasFailed, taskStatus } = useTaskMonitor();

            expect(isFinalState(taskStatus.value)).toBe(false);
            waitForTask(FAILED_TASK_ID);
            await flushPromises();
            expect(isFinalState(taskStatus.value)).toBe(true);
            expect(isRunning.value).toBe(false);
            expect(isCompleted.value).toBe(false);
            expect(hasFailed.value).toBe(true);
        });
    });
});
