import flushPromises from "flush-promises";
import { createPinia, setActivePinia } from "pinia";
import { beforeEach, describe, expect, it, vi } from "vitest";

import { getWorkflowFull } from "@/components/Workflow/workflows.services";
import { useWorkflowStore } from "@/stores/workflowStore";

// Mock `getWorkflowFull` function
vi.mock("@/components/Workflow/workflows.services", () => ({
    getWorkflowFull: vi.fn(),
}));

const mockWorkflow = {
    id: "workflow-123",
    name: "Test Workflow",
    version: 1,
    steps: {},
};

describe("useWorkflowStore", () => {
    let workflowStore: ReturnType<typeof useWorkflowStore>;

    beforeEach(() => {
        setActivePinia(createPinia());
        workflowStore = useWorkflowStore();
        vi.clearAllMocks();
    });

    describe("getFullWorkflowCached", () => {
        it("should fetch workflow when not cached", async () => {
            vi.mocked(getWorkflowFull).mockResolvedValue(mockWorkflow);

            const result = await workflowStore.getFullWorkflowCached("workflow-123", 1);
            await flushPromises();

            expect(getWorkflowFull).toHaveBeenCalledTimes(1);
            expect(getWorkflowFull).toHaveBeenCalledWith("workflow-123", 1);
            expect(result).toEqual(mockWorkflow);
        });

        it("should return cached workflow on subsequent calls", async () => {
            vi.mocked(getWorkflowFull).mockResolvedValue(mockWorkflow);

            // First call - should fetch
            const result1 = await workflowStore.getFullWorkflowCached("workflow-123", 1);
            await flushPromises();
            expect(getWorkflowFull).toHaveBeenCalledTimes(1);
            expect(result1).toEqual(mockWorkflow);

            // Second call - should return cached
            const result2 = await workflowStore.getFullWorkflowCached("workflow-123", 1);
            await flushPromises();

            // Still only one API call
            expect(getWorkflowFull).toHaveBeenCalledTimes(1);
            expect(result2).toEqual(mockWorkflow);
        });

        it("should prevent duplicate concurrent requests for same workflow", async () => {
            // Create a promise that we can control when it resolves
            let resolveWorkflow: (value: any) => void;
            const workflowPromise = new Promise((resolve) => {
                resolveWorkflow = resolve;
            });
            vi.mocked(getWorkflowFull).mockReturnValue(workflowPromise);

            // Start two concurrent requests for the same workflow
            const promise1 = workflowStore.getFullWorkflowCached("workflow-123", 1);
            const promise2 = workflowStore.getFullWorkflowCached("workflow-123", 1);

            // At this point, getWorkflowFull should only be called once
            expect(getWorkflowFull).toHaveBeenCalledTimes(1);

            // Resolve the workflow promise
            resolveWorkflow!(mockWorkflow);
            await flushPromises();

            // Both promises should resolve with the same workflow
            const [result1, result2] = await Promise.all([promise1, promise2]);
            expect(result1).toEqual(mockWorkflow);
            expect(result2).toEqual(mockWorkflow);

            // Still only one API call
            expect(getWorkflowFull).toHaveBeenCalledTimes(1);
        });

        it("should allow concurrent requests for different workflows", async () => {
            const mockWorkflow1 = { ...mockWorkflow, id: "workflow-1" };
            const mockWorkflow2 = { ...mockWorkflow, id: "workflow-2" };

            vi.mocked(getWorkflowFull).mockResolvedValueOnce(mockWorkflow1);
            vi.mocked(getWorkflowFull).mockResolvedValueOnce(mockWorkflow2);

            // Start concurrent requests for different workflows
            const [result1, result2] = await Promise.all([
                workflowStore.getFullWorkflowCached("workflow-1"),
                workflowStore.getFullWorkflowCached("workflow-2"),
            ]);
            await flushPromises();

            // Should make two separate API calls
            expect(getWorkflowFull).toHaveBeenCalledTimes(2);
            expect(result1).toEqual(mockWorkflow1);
            expect(result2).toEqual(mockWorkflow2);
        });

        it("should allow concurrent requests for different versions of same workflow", async () => {
            const mockWorkflowV1 = { ...mockWorkflow, version: 1 };
            const mockWorkflowV2 = { ...mockWorkflow, version: 2 };

            vi.mocked(getWorkflowFull).mockResolvedValueOnce(mockWorkflowV1);
            vi.mocked(getWorkflowFull).mockResolvedValueOnce(mockWorkflowV2);

            // Start concurrent requests for different versions
            const [result1, result2] = await Promise.all([
                workflowStore.getFullWorkflowCached("workflow-123", 1),
                workflowStore.getFullWorkflowCached("workflow-123", 2),
            ]);
            await flushPromises();

            // Should make two separate API calls
            expect(getWorkflowFull).toHaveBeenCalledTimes(2);
            expect(result1).toEqual(mockWorkflowV1);
            expect(result2).toEqual(mockWorkflowV2);
        });

        it("should deduplicate multiple concurrent requests", async () => {
            // Mock API response which we can resolve later
            let resolveWorkflow: (value: any) => void;
            const workflowPromise = new Promise((resolve) => {
                resolveWorkflow = resolve;
            });
            vi.mocked(getWorkflowFull).mockReturnValue(workflowPromise);

            // Start 5 concurrent requests
            const promises = [
                workflowStore.getFullWorkflowCached("workflow-123", 1),
                workflowStore.getFullWorkflowCached("workflow-123", 1),
                workflowStore.getFullWorkflowCached("workflow-123", 1),
                workflowStore.getFullWorkflowCached("workflow-123", 1),
                workflowStore.getFullWorkflowCached("workflow-123", 1),
            ];

            // Only one API call should be made
            expect(getWorkflowFull).toHaveBeenCalledTimes(1);

            // Resolving the API call
            resolveWorkflow!(mockWorkflow);
            await flushPromises();

            // All 5 promises should resolve with the same result
            const results = await Promise.all(promises);
            results.forEach((result) => {
                expect(result).toEqual(mockWorkflow);
            });

            // Still only one API call total
            expect(getWorkflowFull).toHaveBeenCalledTimes(1);
        });
    });
});
