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

import { HttpResponse, useServerMock } from "@/api/client/__mocks__";

import MountTarget from "./MarkdownGalaxy.vue";

const { server, http } = useServerMock();

// mock routes
vi.mock("@/utils/redirect", () => ({
    withPrefix: vi.fn((url) => url),
}));

vi.mock("@/composables/config", () => ({
    useConfig: vi.fn(() => ({
        config: {
            version_major: "test_version",
        },
        isConfigLoaded: true,
    })),
}));

vi.mock("@/stores/invocationStore", () => ({
    useInvocationStore: vi.fn(() => ({
        getInvocationById: () => null,
        getInvocationLoadError: () => null,
        isLoadingInvocation: () => false,
    })),
}));

vi.mock("@/stores/workflowStore", () => ({
    useWorkflowStore: vi.fn(() => ({
        fetchWorkflowForInstanceIdCached: vi.fn(() => Promise.resolve()),
        getStoredWorkflowIdByInstanceId: () => null,
    })),
}));

const localVue = getLocalVue();
const pinia = createTestingPinia({ createSpy: vi.fn, stubActions: false });

let postRequests = [];

beforeEach(() => {
    postRequests = [];
});

function mountComponent(propsData = {}, options = {}) {
    const handlers = [
        http.get("/api/histories/test_history_id", ({ response }) =>
            response(200).json({ id: "test_history_id", name: "history_name" }),
        ),
    ];

    if (options.enableHistoryPost) {
        handlers.push(
            http.untyped.post("/api/histories", async ({ request }) => {
                const data = await request.json();
                postRequests.push({ url: request.url, data });
                return HttpResponse.json({});
            }),
        );
    }

    server.use(...handlers);

    return mount(MountTarget, {
        localVue,
        pinia,
        propsData,
        stubs: {
            FontAwesomeIcon: true,
        },
    });
}

describe("MarkdownContainer", () => {
    it("Renders version", async () => {
        const version = "test_version";
        const wrapper = mountComponent({
            content: "generate_galaxy_version()",
        });
        const versionEl = wrapper.find(".galaxy-version");
        expect(versionEl.exists()).toBe(true);
        expect(versionEl.text()).toContain(version);

        // test collapsing
        const nolink = wrapper.find("a");
        expect(nolink.exists()).toBe(false);
        const collapse = "Click here to expand/collapse";
        await wrapper.setProps({ content: `generate_galaxy_version(collapse="${collapse}")` });
        const link = wrapper.find("a");
        expect(link.text()).toBe(collapse);
        const container = wrapper.find(".collapse");
        expect(container.attributes("style")).toBe("display: none;");
        await link.trigger("click");
        // After click, style attribute is removed
        expect(container.attributes("style")).toBeFalsy();
    });

    it("Renders time stamp", async () => {
        const time = new Date();
        vi.useFakeTimers();
        vi.setSystemTime(time);
        const wrapper = mountComponent({
            content: "generate_time()",
        });
        const version = wrapper.find(".galaxy-time");
        expect(version.exists()).toBe(true);
        expect(version.text()).toBe(time.toUTCString());
        vi.useRealTimers();
    });

    it("Renders history link", async () => {
        const wrapper = mountComponent(
            {
                content: "history_link(history_id=test_history_id)",
            },
            {
                enableHistoryPost: true,
            },
        );
        expect(wrapper.find("a").text()).toBe("Click to Import History: ...");
        await flushPromises();
        const link = wrapper.find("a");
        expect(link.text()).toBe("Click to Import History: history_name");
        await link.trigger("click");
        await flushPromises();
        expect(postRequests.length).toBe(1);
        expect(postRequests[0].data.history_id).toBe("test_history_id");
        const error = wrapper.find(".text-success");
        const message = error.find("span");
        expect(message.text()).toBe("Successfully Imported History: history_name!");
    });

    it("Renders history link (with failing import error message)", async () => {
        const wrapper = mountComponent({
            content: "history_link(history_id=test_history_id)",
        });
        await wrapper.find("a").trigger("click");
        await flushPromises();
        const error = wrapper.find(".text-danger");
        const message = error.find("span");
        expect(message.text()).toBe("Failed to handle History: history_name!");
    });

    it("Renders error for invalid directive syntax", async () => {
        const wrapper = mountComponent({
            content: "not_valid_content(",
        });
        const alert = wrapper.find(".alert-danger");
        expect(alert.exists()).toBe(true);
        expect(alert.text()).toContain("The directive provided below is invalid");
    });

    it("Renders error for invalid component type", async () => {
        const wrapper = mountComponent({
            content: "unknown_component()",
        });
        const alert = wrapper.find(".alert-danger");
        expect(alert.text()).toContain("Invalid component type");
    });

    it("Renders error for missing required label", async () => {
        const wrapper = mountComponent({
            content: "tool_a(input=foo)",
            labels: [{ type: "input", label: "NotFoo" }],
        });
        const alert = wrapper.find(".alert-danger");
        expect(alert.text()).toContain("Invalid component type tool_a");
    });

    it("Renders info alert if labels exist but no invocation_id is present", async () => {
        const wrapper = mountComponent({
            content: "history_dataset_display(input=foo)",
            labels: [
                { type: "input", label: "foo" },
                { type: "output", label: "bar" },
            ],
        });
        await flushPromises();
        const alert = wrapper.find(".alert-info");
        expect(alert.text()).toContain("Data for rendering not yet available for");
    });

    it("Renders danger alert if more than one label exists", async () => {
        const wrapper = mountComponent({
            content: "history_dataset_display(input=foo, output=bar)",
            labels: [
                { type: "input", label: "foo" },
                { type: "output", label: "bar" },
            ],
        });
        await flushPromises();
        const alert = wrapper.find(".alert-danger");
        expect(alert.text()).toMatch(/Invalid or missing label for\s*history_dataset_display/);
    });

    it("Renders loading span while invocation is loading", async () => {
        const { useInvocationStore } = await import("@/stores/invocationStore");
        vi.mocked(useInvocationStore).mockReturnValueOnce({
            getInvocationById: () => null,
            getInvocationLoadError: () => null,
            isLoadingInvocation: vi.fn(() => true),
        });
        const wrapper = mountComponent({
            content: "history_dataset_display(invocation_id=123, input=foo)",
            labels: [
                { type: "input", label: "foo" },
                { type: "output", label: "bar" },
            ],
        });
        await flushPromises();
        expect(wrapper.findComponent({ name: "LoadingSpan" }).exists()).toBe(true);
    });

    it("Handles invocation fetching and workflow ID resolution", async () => {
        const invocation = { workflow_id: "wf123", inputs: {}, outputs: {} };
        const { useInvocationStore } = await import("@/stores/invocationStore");
        const { useWorkflowStore } = await import("@/stores/workflowStore");
        const fetchWorkflowMock = vi.fn(() => Promise.resolve());
        vi.mocked(useInvocationStore).mockReturnValueOnce({
            getInvocationById: () => invocation,
            getInvocationLoadError: () => null,
            isLoadingInvocation: () => false,
        });
        vi.mocked(useWorkflowStore).mockReturnValueOnce({
            fetchWorkflowForInstanceIdCached: fetchWorkflowMock,
            getStoredWorkflowIdByInstanceId: () => "wf123",
        });
        mountComponent({
            content: "tool_a(invocation_id=123, input=foo, output=bar)",
            labels: [
                { type: "input", label: "foo" },
                { type: "output", label: "bar" },
            ],
        });
        await flushPromises();
        expect(fetchWorkflowMock).toHaveBeenCalledWith("wf123");
    });
});
