import "@/composables/__mocks__/filter";

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

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

import { generateNewBroadcast, generateRandomString } from "./test.utils";

import BroadcastForm from "./BroadcastForm.vue";

const SUBJECT_INPUT_SELECTOR = "#broadcast-subject";
const MESSAGE_INPUT_SELECTOR = "#broadcast-message";
const ACTION_LINK_SELECTOR = "#create-action-link";
const SUBMIT_BUTTON_SELECTOR = "#broadcast-submit";
const PUBLISHED_WARNING_SELECTOR = "#broadcast-published-warning";

const localVue = getLocalVue(true);

async function mountBroadcastForm(props?: object) {
    const pinia = createTestingPinia({ createSpy: vi.fn });
    setActivePinia(pinia);

    const mockRouter = {
        push: vi.fn(),
    };

    const wrapper = mount(BroadcastForm as object, {
        propsData: {
            ...props,
        },
        localVue,
        pinia,
        stubs: {
            FontAwesomeIcon: true,
            FormElement: true,
            GDateTime: true,
            LoadingSpan: true,
            BroadcastContainer: true,
        },
        mocks: {
            $router: mockRouter,
        },
    });

    await flushPromises();

    return { wrapper, mockRouter };
}

const { server, http } = useServerMock();

describe("BroadcastForm.vue", () => {
    function expectSubmitButton(wrapper: Wrapper<Vue>, enabled: boolean) {
        expect(wrapper.find(SUBMIT_BUTTON_SELECTOR).exists()).toBeTruthy();
        expect(wrapper.find(SUBMIT_BUTTON_SELECTOR).attributes("aria-disabled")).toBe(enabled ? undefined : "true");
        expect(wrapper.find(SUBMIT_BUTTON_SELECTOR).attributes("data-title")).toBe(
            enabled ? "" : "Please fill all required fields",
        );
    }

    async function createBroadcast(
        wrapper: Wrapper<Vue>,
        mockRouter: { push: ReturnType<typeof vi.fn> },
        actionsLink = false,
    ) {
        server.use(
            http.post("/api/notifications/broadcast", ({ response }) => {
                // We use untyped here because we don't care about the response
                return response.untyped(HttpResponse.json({}, { status: 200 }));
            }),
        );

        expectSubmitButton(wrapper, false);

        await wrapper.find(SUBJECT_INPUT_SELECTOR).setValue("Test subject");
        await wrapper.find(MESSAGE_INPUT_SELECTOR).setValue("Test message");

        if (actionsLink) {
            await wrapper.find(ACTION_LINK_SELECTOR).trigger("click");

            await flushPromises();

            await wrapper.find("#broadcast-action-link-name-0").setValue("Test link name");
            await wrapper.find("#broadcast-action-link-link-0").setValue("https://test.link");
        }

        expectSubmitButton(wrapper, true);

        await wrapper.find(SUBMIT_BUTTON_SELECTOR).trigger("click");

        await flushPromises();

        expect(mockRouter.push).toHaveBeenCalledTimes(1);
        expect(mockRouter.push).toHaveBeenCalledWith("/admin/notifications");
    }

    it("should render form with empty fields and submit button disabled", async () => {
        const { wrapper } = await mountBroadcastForm();

        expectSubmitButton(wrapper, false);
    });

    it("should create broadcast when all inputs are defined", async () => {
        const { wrapper, mockRouter } = await mountBroadcastForm();

        await createBroadcast(wrapper, mockRouter);
    });

    it("should create broadcast with a link when all inputs are defined", async () => {
        const { wrapper, mockRouter } = await mountBroadcastForm();

        await createBroadcast(wrapper, mockRouter, true);
    });

    it("should get broadcast data when id is defined", async () => {
        const FAKE_BROADCAST = generateNewBroadcast({ actionLink: true });
        server.use(
            http.get("/api/notifications/broadcast/{notification_id}", ({ response }) => {
                return response(200).json(FAKE_BROADCAST);
            }),
        );

        const { wrapper } = await mountBroadcastForm({ id: FAKE_BROADCAST.id });

        await flushPromises();

        expect((wrapper.find(SUBJECT_INPUT_SELECTOR).element as HTMLInputElement).value).toBe(
            FAKE_BROADCAST.content.subject,
        );
        expect((wrapper.find(MESSAGE_INPUT_SELECTOR).element as HTMLInputElement).value).toBe(
            FAKE_BROADCAST.content.message,
        );
        expect(wrapper.find("#broadcast-action-link-name-0").exists()).toBeTruthy();
        expect(wrapper.find("#broadcast-action-link-link-0").exists()).toBeTruthy();
    });

    it("should update broadcast when id is defined and all inputs are defined", async () => {
        const FAKE_BROADCAST = generateNewBroadcast({ actionLink: true });
        server.use(
            http.get("/api/notifications/broadcast/{notification_id}", ({ response }) => {
                return response(200).json(FAKE_BROADCAST);
            }),

            http.put("/api/notifications/broadcast/{notification_id}", ({ response }) => {
                return response(204).empty();
            }),
        );

        const { wrapper, mockRouter } = await mountBroadcastForm({ id: FAKE_BROADCAST.id });

        const newSubject = generateRandomString();
        await wrapper.find(SUBJECT_INPUT_SELECTOR).setValue(newSubject);

        expectSubmitButton(wrapper, true);

        await wrapper.find(SUBMIT_BUTTON_SELECTOR).trigger("click");

        await flushPromises();

        expect(mockRouter.push).toHaveBeenCalledTimes(1);
        expect(mockRouter.push).toHaveBeenCalledWith("/admin/notifications");
    });

    it("should not show the published warning when editing a scheduled broadcast", async () => {
        const FAKE_BROADCAST = generateNewBroadcast({ published: false });
        server.use(
            http.get("/api/notifications/broadcast/{notification_id}", ({ response }) => {
                return response(200).json(FAKE_BROADCAST);
            }),
        );

        const { wrapper } = await mountBroadcastForm({ id: FAKE_BROADCAST.id });

        expect(wrapper.find(PUBLISHED_WARNING_SELECTOR).exists()).toBeFalsy();
    });

    it("should show the published published when editing a published broadcast", async () => {
        const FAKE_BROADCAST = generateNewBroadcast({ published: true });
        server.use(
            http.get("/api/notifications/broadcast/{notification_id}", ({ response }) => {
                return response(200).json(FAKE_BROADCAST);
            }),
        );

        const { wrapper } = await mountBroadcastForm({ id: FAKE_BROADCAST.id });

        expect(wrapper.find(PUBLISHED_WARNING_SELECTOR).exists()).toBeTruthy();
    });
});
