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

import { useServerMock } from "@/api/client/__mocks__/index";
import { useUserStore } from "@/stores/userStore";

import WorkflowInvocationShare from "./WorkflowInvocationShare.vue";
import GModal from "@/components/BaseComponents/GModal.vue";

// Constants
const WORKFLOW_OWNER = "test-user";
const OTHER_USER = "other-user";
const TEST_WORKFLOW = {
    id: "workflow-id",
    name: "workflow-name",
    owner: WORKFLOW_OWNER,
    version: 1,
    importable: false,
    published: false,
    users_shared_with: [],
    title: "workflow-title",
};
const SHARED_WORKFLOW_ID = "shared-workflow-id";
const TEST_HISTORY = {
    id: "test-history-id",
    name: "test-history-name",
    archived: false,
};
const TEST_HISTORY_POST_SHARE = {
    ...TEST_HISTORY,
    importable: true,
    published: false,
    users_shared_with: [],
    title: "history-title",
};
const SHARE_SUCCESS_MSG = "Workflow and history are now shareable.";
const CLIPBOARD_MSG = "The link to the invocation has been copied to your clipboard.";

const SELECTORS = {
    SHARE_ICON_BUTTON: "[data-button-share]",
} as const;

// Mock the toast composable to track the messages
const MSG = 0;
const TYPE = 1;
const toastMock = vi.fn((message, type: "success" | "info") => {
    return { message, type };
});
vi.mock("@/composables/toast", () => ({
    Toast: {
        success: vi.fn().mockImplementation((message) => {
            toastMock(message, "success");
        }),
        info: vi.fn().mockImplementation((message) => {
            toastMock(message, "info");
        }),
    },
}));

// Mock "@/utils/clipboard"
const writeText = vi.fn();
Object.defineProperty(navigator, "clipboard", {
    writable: true,
    configurable: true,
    value: {
        writeText,
    },
});

// Mock the workflow store to return the sample workflow
vi.mock("@/stores/workflowStore", async () => {
    const originalModule = await vi.importActual("@/stores/workflowStore");
    return {
        ...originalModule,
        useWorkflowStore: () => ({
            ...(originalModule as any).useWorkflowStore(),
            getStoredWorkflowByInstanceId: vi.fn().mockImplementation((workflowId: string) => {
                return {
                    ...TEST_WORKFLOW,
                    id: workflowId === SHARED_WORKFLOW_ID ? SHARED_WORKFLOW_ID : TEST_WORKFLOW.id,
                    importable: workflowId === SHARED_WORKFLOW_ID,
                };
            }),
        }),
    };
});

// Mock the history store to return the sample history
vi.mock("@/stores/historyStore", async () => {
    const originalModule = await vi.importActual("@/stores/historyStore");
    return {
        ...originalModule,
        useHistoryStore: () => ({
            ...(originalModule as any).useHistoryStore(),
            getHistoryById: vi.fn().mockImplementation((historyId: string) => {
                return {
                    ...TEST_HISTORY,
                    id: historyId,
                    importable: historyId === `${TEST_HISTORY.id}-importable`,
                };
            }),
            getHistoryNameById: vi.fn().mockImplementation(() => {
                return TEST_HISTORY.name;
            }),
        }),
    };
});

const { server, http } = useServerMock();

const localVue = getLocalVue();

/**
 * Mounts the WorkflowInvocationShare component with props/stores adjusted given the parameters
 * @param ownsWorkflow Whether the user owns the workflow associated with the invocation
 * @param bothShareable Whether the workflow and history are already shareable
 * @returns The wrapper object
 */
async function mountWorkflowInvocationShare(ownsWorkflow = true, bothShareable = false) {
    server.use(
        http.put("/api/workflows/{workflow_id}/enable_link_access", ({ response }) => {
            return response(200).json({
                ...TEST_WORKFLOW,
                importable: true,
            });
        }),

        http.put("/api/histories/{history_id}/enable_link_access", ({ response }) => {
            return response(200).json(TEST_HISTORY_POST_SHARE);
        }),
    );

    const wrapper = mount(WorkflowInvocationShare as object, {
        propsData: {
            invocationId: "invocation-id",
            workflowId: bothShareable ? SHARED_WORKFLOW_ID : TEST_WORKFLOW.id,
            historyId: bothShareable ? `${TEST_HISTORY.id}-importable` : TEST_HISTORY.id,
        },
        stubs: {
            FontAwesomeIcon: true,
            BModal: true,
        },
        localVue,
        pinia: createTestingPinia({ createSpy: vi.fn }),
    });

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

    await flushPromises();

    return { wrapper };
}

async function openShareModal(wrapper: Wrapper<Vue>) {
    await wrapper.find(SELECTORS.SHARE_ICON_BUTTON).trigger("click");
}

describe("WorkflowInvocationShare", () => {
    beforeEach(() => {
        toastMock.mockClear();
        (navigator.clipboard.writeText as ReturnType<typeof vi.fn>).mockResolvedValue(undefined);
    });

    it("opens the modal with the expected history and workflow information", async () => {
        const { wrapper } = await mountWorkflowInvocationShare();

        // Initially, the modal is not visible and opens when the button is clicked
        expect(wrapper.findComponent(GModal).props("show")).toBeFalsy();
        await openShareModal(wrapper);
        expect(wrapper.findComponent(GModal).props("show")).toBeTruthy();

        expect(wrapper.findComponent(GModal).text()).toContain(TEST_WORKFLOW.name);
        expect(wrapper.findComponent(GModal).text()).toContain(TEST_HISTORY.name);
    });

    it("shares the workflow and history when the share button is clicked, and copies link", async () => {
        const { wrapper } = await mountWorkflowInvocationShare();

        await openShareModal(wrapper);

        // Click the share button in the modal
        wrapper.findComponent(GModal).vm.$emit("ok");
        await flushPromises();

        // We have 2 toasts
        const toasts = toastMock.mock.calls;
        expect(toasts.length).toBe(2);

        // The first one is the success message for sharing the workflow and history
        expect(toasts[0]![MSG]).toBe(SHARE_SUCCESS_MSG);
        expect(toasts[0]![TYPE]).toBe("success");

        // The second one is the copied link message
        expect(toasts[1]![MSG]).toBe(CLIPBOARD_MSG);
        expect(toasts[1]![TYPE]).toBe("info");
    });

    it("renders nothing when the user does not own the workflow", async () => {
        const { wrapper } = await mountWorkflowInvocationShare(false);
        expect(wrapper.find(SELECTORS.SHARE_ICON_BUTTON).exists()).toBe(false);
        expect(wrapper.findComponent(GModal).exists()).toBeFalsy();
    });

    it("just copies link and does not open modal if both workflow and history are already shareable", async () => {
        const { wrapper } = await mountWorkflowInvocationShare(true, true);

        // Initially, the modal is not visible and this time remains closed when the button is clicked
        expect(wrapper.findComponent(GModal).props("visible")).toBeFalsy();
        await openShareModal(wrapper);
        expect(wrapper.findComponent(GModal).props("visible")).toBeFalsy();

        // Instead we already have a singular toast with the link copied message
        const toasts = toastMock.mock.calls;
        expect(toasts.length).toBe(1);
        expect(toasts[0]![MSG]).toBe(CLIPBOARD_MSG);
        expect(toasts[0]![TYPE]).toBe("info");
    });
});
