import { faCog, faCopy, faFilter, faFolder } from "@fortawesome/free-solid-svg-icons";
import { createTestingPinia } from "@pinia/testing";
import { getLocalVue } from "@tests/vitest/helpers";
import { setupMockConfig } from "@tests/vitest/mockConfig";
import { mount } from "@vue/test-utils";
import flushPromises from "flush-promises";
import { PiniaVuePlugin } from "pinia";
import { describe, expect, it, vi } from "vitest";

import type { GalaxyConfiguration } from "@/stores/configurationStore";
import Filtering from "@/utils/filtering";

import type { GridConfig, RowData } from "./configs/types";

import MountTarget from "./GridList.vue";

vi.useFakeTimers();

setupMockConfig({ disabled: false, enabled: true });

vi.mock("vue-router/composables");

const localVue = getLocalVue();
localVue.use(PiniaVuePlugin);

function createTestGrid(): GridConfig {
    return {
        id: "test-grid",
        actions: [
            {
                title: "test",
                icon: faCopy as any,
                handler: vi.fn(),
            },
        ],
        fields: [
            {
                key: "id",
                title: "id",
                type: "text",
            },
            {
                key: "link",
                title: "link",
                type: "link",
            },
            {
                key: "operation",
                title: "operation",
                type: "operations",
                condition: vi.fn(() => true),
                operations: [
                    {
                        title: "operation-title-1",
                        icon: faCog as any,
                        condition: (_: RowData, config: GalaxyConfiguration) => config.value.enabled,
                        handler: vi.fn(),
                    },
                    {
                        title: "operation-title-2",
                        icon: faFilter as any,
                        condition: (_: RowData, config: GalaxyConfiguration) => config.value.disabled,
                        handler: vi.fn(),
                    },
                    {
                        title: "operation-title-3",
                        icon: faFolder as any,
                        condition: (_: RowData, config: GalaxyConfiguration) => config.value.enabled,
                        handler: async () => ({
                            status: "success",
                            message: "Operation-3 has been executed.",
                        }),
                    },
                ],
            },
        ],
        filtering: new Filtering({}, undefined, false, false),
        getData: vi.fn(async (offset: number, limit: number): Promise<[RowData[], number]> => {
            const data: RowData[] = [];
            for (let i = offset; i < offset + limit; i++) {
                data.push({
                    id: `id-${i + 1}`,
                    link: `link-${i + 1}`,
                    operation: `operation-${i + 1}`,
                });
            }
            return [data, 100];
        }),
        plural: "Tests",
        sortBy: "id",
        sortDesc: true,
        sortKeys: ["id"],
        title: "Test",
    };
}

interface TargetProps {
    gridConfig: GridConfig;
    limit?: number;
}

function createTarget(propsData: TargetProps) {
    const pinia = createTestingPinia({ createSpy: vi.fn, stubActions: false });
    return mount(MountTarget as object, {
        localVue,
        propsData,
        pinia,
    });
}

describe("GridList", () => {
    it("basic rendering", async () => {
        const testGrid = createTestGrid();
        const wrapper = createTarget({
            gridConfig: testGrid,
        });
        const findInput = wrapper.find("[data-description='filter text input']");
        expect(findInput.attributes().placeholder).toBe("search tests");
        expect(wrapper.find("[data-description='grid initial loading']").exists()).toBeTruthy();
        const findAction = wrapper.find("[data-description='grid action test']");
        expect(findAction.text()).toBe("test");
        await findAction.trigger("click");
        expect(testGrid.actions![0]!.handler).toHaveBeenCalledTimes(1);
        expect(testGrid.getData).toHaveBeenCalledTimes(1);
        expect(vi.mocked(testGrid.getData).mock.calls[0]!.slice(0, 5)).toEqual([0, 25, "", "id", true]);
        expect(findAction.find("svg").exists()).toBeTruthy();
        await wrapper.vm.$nextTick();
        expect(wrapper.find("[data-description='grid title']").text()).toBe("Test");
        expect(wrapper.find("[data-description='grid cell 0-0']").text()).toBe("id-1");
        expect(wrapper.find("[data-description='grid cell 1-0']").text()).toBe("id-2");
        expect(wrapper.find("[data-description='grid cell 0-1'] > button").text()).toBe("link-1");
        expect(wrapper.find("[data-description='grid cell 1-1'] > button").text()).toBe("link-2");
        const firstHeader = wrapper.find("[data-description='grid header 0']");
        expect(firstHeader.find("button").text()).toBe("id");
        await firstHeader.find("button").trigger("click");
        await wrapper.vm.$nextTick();
        await flushPromises();
        expect(testGrid.getData).toHaveBeenCalledTimes(2);
        expect(vi.mocked(testGrid.getData).mock.calls[1]!.slice(0, 5)).toEqual([0, 25, "", "id", false]);
        expect(firstHeader.find("[data-description='grid sort desc']").exists()).toBeFalsy();
        expect(firstHeader.find("[data-description='grid sort asc']").exists()).toBeTruthy();
        const secondHeader = wrapper.find("[data-description='grid header 1']");
        expect(secondHeader.find("[data-description='grid sort desc']").exists()).toBeFalsy();
        expect(secondHeader.find("[data-description='grid sort asc']").exists()).toBeFalsy();
    });

    it("header rendering", async () => {
        const testGrid = createTestGrid();
        const wrapper = createTarget({
            gridConfig: testGrid,
        });
        await flushPromises();
        for (const [fieldIndex, field] of Object.entries(testGrid.fields)) {
            expect(wrapper.find(`[data-description='grid header ${fieldIndex}']`).text()).toBe(field.title);
        }
    });

    it("operation handling", async () => {
        const testGrid = createTestGrid();
        const wrapper = createTarget({
            gridConfig: testGrid,
        });
        await flushPromises();
        const dropdown = wrapper.find("[data-description='grid cell 0-2']");
        const dropdownItems = dropdown.findAll(".dropdown-item");
        expect(dropdownItems.at(0).text()).toBe("operation-title-1");
        expect(dropdownItems.at(1).text()).toBe("operation-title-3");
        await dropdownItems.at(0).trigger("click");
        const clickHandler = testGrid.fields[2]!.operations![0]!.handler;
        expect(clickHandler).toHaveBeenCalledTimes(1);
        expect(vi.mocked(clickHandler).mock.calls[0]!.slice(0, 1)).toEqual([
            { id: "id-1", link: "link-1", operation: "operation-1" },
        ]);
        await dropdownItems.at(1).trigger("click");
        await flushPromises();
        const alert = wrapper.find(".alert");
        expect(alert.text()).toBe("Operation-3 has been executed.");
        vi.runAllTimers();
        await wrapper.vm.$nextTick();
        expect(wrapper.find(".alert").exists()).toBeFalsy();
    });

    it("filter handling", async () => {
        const testGrid = createTestGrid();
        const wrapper = createTarget({
            gridConfig: testGrid,
        });
        await wrapper.vm.$nextTick();
        const filterInput = wrapper.find("[data-description='filter text input']");
        await filterInput.setValue("filter query");
        vi.runAllTimers();
        await flushPromises();
        expect(vi.mocked(testGrid.getData)).toHaveBeenCalledTimes(2);
        expect(vi.mocked(testGrid.getData).mock.calls[1]!.slice(0, 5)).toEqual([0, 25, "filter query", "id", true]);
    });

    it("pagination", async () => {
        const testGrid = createTestGrid();
        const wrapper = createTarget({
            gridConfig: testGrid,
            limit: 2,
        });
        await flushPromises();
        const pageLinks = wrapper.findAll(".page-link");
        await pageLinks.at(4).trigger("click");
        expect(wrapper.find("[data-description='grid cell 0-0']").text()).toBe("id-5");
        expect(wrapper.find("[data-description='grid cell 1-0']").text()).toBe("id-6");
    });
});
