import { createTestingPinia } from "@pinia/testing";
import { getFakeRegisteredUser } from "@tests/test-data";
import { getLocalVue } from "@tests/vitest/helpers";
import { mount } from "@vue/test-utils";
import flushPromises from "flush-promises";
import { describe, expect, it, vi } from "vitest";

import type { AnyHistory } from "@/api";
import { useServerMock } from "@/api/client/__mocks__";
import { useHistoryStore } from "@/stores/historyStore";
import { useUserStore } from "@/stores/userStore";

import WorkflowAnnotation from "./WorkflowAnnotation.vue";

// Constants
const WORKFLOW_OWNER = "test-user";
const OTHER_USER = "other-user";
const WORKFLOW_UPDATE_TIME = "2023-01-01T00:00:00.000Z";
const INVOCATION_TIME = "2024-01-01T00:00:00.000Z";
const SAMPLE_WORKFLOW = {
    id: "workflow-id",
    name: "workflow-name",
    owner: WORKFLOW_OWNER,
    version: 1,
    update_time: WORKFLOW_UPDATE_TIME,
};
const OTHER_USER_WORKFLOW_ID = "other-user-workflow-id";
const SAMPLE_RUN_COUNT = 100;
const TEST_HISTORY_ID = "test-history-id";
const TEST_HISTORY = {
    id: TEST_HISTORY_ID,
    genome_build: "?",
    name: "fake-history-name",
};

const SELECTORS = {
    RUN_COUNT: ".workflow-invocations-count",
    INDICATORS_LINK: '[data-description="published owner badge"]',
    SWITCH_TO_HISTORY_LINK: "[data-description='switch to history link']",
    TIME_INFO: '[data-description="workflow annotation time info"]',
    DATE: '[data-description="workflow annotation date"]',
};

// Mock the workflow store to return the sample workflow
vi.mock("@/stores/workflowStore", async () => {
    const originalModule = (await vi.importActual("@/stores/workflowStore")) as any;
    return {
        ...originalModule,
        useWorkflowStore: () => ({
            ...originalModule.useWorkflowStore(),
            getStoredWorkflowByInstanceId: vi.fn().mockImplementation((id: string) => {
                if (id === OTHER_USER_WORKFLOW_ID) {
                    return { ...SAMPLE_WORKFLOW, id: OTHER_USER_WORKFLOW_ID, published: true };
                }
                return SAMPLE_WORKFLOW;
            }),
        }),
    };
});

const localVue = getLocalVue();
const { server, http } = useServerMock();

/**
 * Mounts the WorkflowAnnotation component with props/stores adjusted given the parameters
 * @param version The version of the component to mount (`run_form` or `invocation` view)
 * @param ownsWorkflow Whether the user owns the workflow
 * @returns The wrapper object
 */
async function mountWorkflowAnnotation(version: "run_form" | "invocation", ownsWorkflow = true) {
    server.use(
        http.get("/api/histories/{history_id}", ({ response }) => {
            return response(200).json(TEST_HISTORY);
        }),
    );
    server.use(
        http.get("/api/workflows/{workflow_id}/counts", ({ response }) => {
            return response(200).json({ scheduled: SAMPLE_RUN_COUNT });
        }),
    );

    const wrapper = mount(WorkflowAnnotation as object, {
        propsData: {
            workflowId: ownsWorkflow ? SAMPLE_WORKFLOW.id : OTHER_USER_WORKFLOW_ID,
            historyId: TEST_HISTORY_ID,
            invocationCreateTime: version === "invocation" ? INVOCATION_TIME : undefined,
            showDetails: version === "run_form",
        },
        localVue,
        pinia: createTestingPinia({ createSpy: vi.fn }),
        stubs: {
            FontAwesomeIcon: true,
        },
    });

    const historyStore = useHistoryStore();
    historyStore.storedHistories = { [TEST_HISTORY_ID]: TEST_HISTORY as AnyHistory };
    historyStore.setCurrentHistoryId(TEST_HISTORY_ID);

    const userStore = useUserStore();
    userStore.currentUser = getFakeRegisteredUser({
        username: ownsWorkflow ? WORKFLOW_OWNER : OTHER_USER,
    });

    await flushPromises();

    return { wrapper };
}

describe("WorkflowAnnotation renders", () => {
    it("the run count and history, not indicators if owned not published", async () => {
        async function checkHasRunCount(version: "run_form" | "invocation") {
            const { wrapper } = await mountWorkflowAnnotation(version);

            const runCount = wrapper.find(SELECTORS.RUN_COUNT);
            expect(runCount.text()).toContain("workflow runs:");
            expect(runCount.text()).toContain(SAMPLE_RUN_COUNT.toString());

            if (version === "run_form") {
                expect(wrapper.find(SELECTORS.SWITCH_TO_HISTORY_LINK).exists()).toBe(false);
            } else {
                expect(wrapper.find(SELECTORS.SWITCH_TO_HISTORY_LINK).text()).toContain(TEST_HISTORY.name);
            }

            // Since this is the user's own workflow, the indicators link
            // (to view all published workflows by the owner) should not be present
            expect(wrapper.find(SELECTORS.INDICATORS_LINK).exists()).toBe(false);
        }
        await checkHasRunCount("run_form");
        await checkHasRunCount("invocation");
    });

    it("workflow indicators if the user does not own the workflow", async () => {
        // we assume it is another user's published workflow in this case

        async function checkHasIndicators(version: "run_form" | "invocation") {
            const { wrapper } = await mountWorkflowAnnotation(version, false);

            const indicatorsLink = wrapper.find(SELECTORS.INDICATORS_LINK);
            expect(indicatorsLink.text()).toBe(WORKFLOW_OWNER);
            expect(indicatorsLink.attributes("title")).toContain(`Published by '${WORKFLOW_OWNER}'`);
        }
        await checkHasIndicators("run_form");
        await checkHasIndicators("invocation");
    });

    it("renders time since edit if run form view", async () => {
        const { wrapper } = await mountWorkflowAnnotation("run_form");

        const timeInfo = wrapper.find(SELECTORS.TIME_INFO);
        expect(timeInfo.text()).toContain("edited");
        expect(timeInfo.find(SELECTORS.DATE).attributes("title")).toBe(WORKFLOW_UPDATE_TIME);
    });

    it("renders time since invocation if invocation view", async () => {
        const { wrapper } = await mountWorkflowAnnotation("invocation");

        const timeInfo = wrapper.find(SELECTORS.TIME_INFO);
        expect(timeInfo.text()).toContain("invoked");
        expect(timeInfo.find(SELECTORS.DATE).attributes("title")).toBe(INVOCATION_TIME);
    });
});
