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

import sampleInvocation from "@/components/Workflow/test/json/invocation.json";
import { useUserStore } from "@/stores/userStore";

import WorkflowNavigationTitle from "./WorkflowNavigationTitle.vue";

// Constants
const WORKFLOW_OWNER = "test-user";
const OTHER_USER = "other-user";
const UNIMPORTABLE_WORKFLOW_ID = "invalid-workflow-id";
const UNIMPORTABLE_WORKFLOW_INSTANCE_ID = "invalid-instance-id";
const SAMPLE_WORKFLOW = {
    id: "workflow-id",
    name: "workflow-name",
    owner: WORKFLOW_OWNER,
    version: 1,
};
const IMPORT_ERROR_MESSAGE = "Failed to import workflow";

const SELECTORS = {
    WORKFLOW_HEADING: "[data-description='workflow heading']",
    ACTIONS_BUTTON_GROUP: "[data-button-group]",
    EDIT_WORKFLOW_BUTTON: `[data-button-edit][title='Edit Workflow']`,
    IMPORT_WORKFLOW_BUTTON: "[data-description='import workflow button']",
    EXECUTE_WORKFLOW_BUTTON: "[data-description='execute workflow button']",
    ROUTE_TO_RERUN_BUTTON: "[data-button-rerun][title='Rerun Workflow with same inputs']",
    ALERT_MESSAGE: "balert-stub",
};

// Mock the copyWorkflow function for importing a workflow
vi.mock("./workflows.services", () => ({
    copyWorkflow: vi.fn().mockImplementation(async (workflowId: string) => {
        if (workflowId === UNIMPORTABLE_WORKFLOW_ID) {
            throw new Error(IMPORT_ERROR_MESSAGE);
        }
        return SAMPLE_WORKFLOW;
    }),
}));

// 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((instanceId: string) => {
                if (instanceId === UNIMPORTABLE_WORKFLOW_INSTANCE_ID) {
                    return { ...SAMPLE_WORKFLOW, id: UNIMPORTABLE_WORKFLOW_ID };
                }
                return SAMPLE_WORKFLOW;
            }),
        }),
    };
});

const localVue = getLocalVue();

/**
 * Mounts the WorkflowNavigationTitle 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 associated with the invocation
 * @param unimportableWorkflow Whether the workflow import should fail
 * @returns The wrapper object
 */
async function mountWorkflowNavigationTitle(
    version: "run_form" | "invocation",
    ownsWorkflow = true,
    unimportableWorkflow = false,
) {
    let workflowId: string;
    let invocation;
    if (version === "invocation") {
        workflowId = !unimportableWorkflow ? sampleInvocation.workflow_id : UNIMPORTABLE_WORKFLOW_INSTANCE_ID;
        invocation = {
            ...sampleInvocation,
            workflow_id: workflowId,
        };
    } else {
        workflowId = !unimportableWorkflow ? SAMPLE_WORKFLOW.id : UNIMPORTABLE_WORKFLOW_INSTANCE_ID;
        invocation = undefined;
    }

    const wrapper = shallowMount(WorkflowNavigationTitle as object, {
        propsData: {
            invocation,
            workflowId,
        },
        localVue,
        pinia: createTestingPinia({ createSpy: vi.fn }),
    });

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

    return { wrapper };
}

describe("WorkflowNavigationTitle renders", () => {
    it("the workflow name in header and run button in actions; invocation version", async () => {
        const { wrapper } = await mountWorkflowNavigationTitle("invocation");

        const heading = wrapper.find(SELECTORS.WORKFLOW_HEADING);
        expect(heading.text()).toContain(`Invoked Workflow: ${SAMPLE_WORKFLOW.name}`);
        expect(heading.text()).toContain(`(Version: ${SAMPLE_WORKFLOW.version + 1})`);

        const rerunButton = wrapper.find(SELECTORS.ROUTE_TO_RERUN_BUTTON);
        expect(rerunButton.attributes("title")).toContain("Rerun");
    });

    it("the workflow name in header and run button in actions; run form version", async () => {
        const { wrapper } = await mountWorkflowNavigationTitle("run_form");

        const heading = wrapper.find(SELECTORS.WORKFLOW_HEADING);
        expect(heading.text()).toContain(`Workflow: ${SAMPLE_WORKFLOW.name}`);
        expect(heading.text()).toContain(`(Version: ${SAMPLE_WORKFLOW.version + 1})`);

        const runButton = wrapper.find(SELECTORS.EXECUTE_WORKFLOW_BUTTON);
        expect(runButton.attributes("title")).toContain("Run");
    });

    it("edit button if user owns the workflow", async () => {
        async function findEditButton(version: "invocation" | "run_form") {
            const { wrapper } = await mountWorkflowNavigationTitle(version);
            const actionsGroup = wrapper.find(SELECTORS.ACTIONS_BUTTON_GROUP);

            const editButton = actionsGroup.find(SELECTORS.EDIT_WORKFLOW_BUTTON);
            expect(editButton.attributes("to")).toBe(
                `/workflows/edit?id=${SAMPLE_WORKFLOW.id}&version=${SAMPLE_WORKFLOW.version}`,
            );
        }
        await findEditButton("invocation");
        await findEditButton("run_form");
    });

    it("import button instead if user does not own the workflow", async () => {
        async function findImportButton(version: "invocation" | "run_form") {
            const { wrapper } = await mountWorkflowNavigationTitle(version, false);
            const actionsGroup = wrapper.find(SELECTORS.ACTIONS_BUTTON_GROUP);
            const importButton = actionsGroup.find(SELECTORS.IMPORT_WORKFLOW_BUTTON);
            expect(importButton.exists()).toBe(true);
        }
        await findImportButton("invocation");
        await findImportButton("run_form");
    });
});

describe("Importing a workflow in WorkflowNavigationTitle", () => {
    // We only need to test the `invocation` version because the button is the same in both versions

    it("should show a confirmation dialog when the import is successful", async () => {
        const { wrapper } = await mountWorkflowNavigationTitle("invocation", false);
        const actionsGroup = wrapper.find(SELECTORS.ACTIONS_BUTTON_GROUP);
        const importButton = actionsGroup.find(SELECTORS.IMPORT_WORKFLOW_BUTTON);

        // Cannot `.trigger("click")` on `AsyncButton` because it is a stubbed custom component
        await importButton.props().action();
        await flushPromises();

        const alert = wrapper.find(SELECTORS.ALERT_MESSAGE);
        expect(alert.attributes("variant")).toBe("info");
        expect(alert.text()).toContain(`Workflow ${SAMPLE_WORKFLOW.name} imported successfully`);
    });

    it("should show an error dialog when the import fails", async () => {
        const { wrapper } = await mountWorkflowNavigationTitle("invocation", false, true);
        const actionsGroup = wrapper.find(SELECTORS.ACTIONS_BUTTON_GROUP);
        const importButton = actionsGroup.find(SELECTORS.IMPORT_WORKFLOW_BUTTON);

        // Cannot `.trigger("click")` on `AsyncButton` because it is a stubbed custom component
        await importButton.props().action();
        await flushPromises();

        const alert = wrapper.find(SELECTORS.ALERT_MESSAGE);
        expect(alert.attributes("variant")).toBe("danger");
        expect(alert.text()).toContain(IMPORT_ERROR_MESSAGE);
    });
});
