import {beforeEach, expect, it, vi} from "vitest";
import PostSubmit from "./PostSubmit";
import Form from "../../Form";
import FormBuilder from "../../../../../FormBuilder";
import EventHandler from "../../../../../Utility/EventHandler";
import InnerNode from "../../../../../Data/InnerNode";

beforeEach(() => {
    // noinspection JSConstantReassignment
    global.sessionStorage = {
        getItem: vi.fn() as unknown,
        setItem: vi.fn() as unknown
    } as Storage;

    // @ts-ignore
    // noinspection JSConstantReassignment
    global.window = {
        addEventListener: vi.fn() as unknown,
        removeEventListener: vi.fn() as unknown,
    } as Window;

    global.console.error = vi.fn();

    // @ts-ignore
    global.HTMLElement = class HTMLElement {
        tagName = "DIV";
    }

    // @ts-ignore
    global.HTMLFormElement = class HTMLFormElement extends HTMLElement{
        setAttribute = vi.fn();
        submit = vi.fn();
        tagName = "FORM";
    };
});

it('registers listeners', async () => {
    //Arrange
    const form = getMockForm();

    //Act
    new PostSubmit(form);

    //Assert
    expect(form.getFormBuilder().onComponentsCreated.add)
        .toHaveBeenCalledWith(expect.any(Function));

    expect(form.onFormChange.add)
        .toHaveBeenCalledWith(expect.any(Function));
});

it('does not load data on components created if data does not exist', async () => {
    //Arrange
    const form = getMockForm();
    form.name = "test";
    sessionStorage.getItem = vi.fn(() => null);
    form.getFormBuilder().onComponentsCreated.add = callable => callable(null);

    //Act
    new PostSubmit(form);

    //Assert
    expect(sessionStorage.getItem)
        .toHaveBeenCalledWith("planaday-form-test");
    expect(form.getDataNode().value)
        .toEqual(undefined);
});

it('loads saved data on components created if exists', async () => {
    //Arrange
    const form = getMockForm();
    form.name = "test";
    sessionStorage.getItem = vi.fn(() => JSON.stringify({test: "test"}));
    form.getFormBuilder().onComponentsCreated.add = callable => callable(null);

    //Act
    new PostSubmit(form);

    //Assert
    expect(sessionStorage.getItem)
        .toHaveBeenCalledWith("planaday-form-test");
    expect(form.getDataNode().value)
        .toEqual({test: "test"});
});

it('adds leave check to form', async () => {
    //Arrange
    const form = getMockForm();
    form.onFormChange.add = callable => callable(null);

    //Act
    const postSubmit = new PostSubmit(form);

    //Assert
    expect(window.addEventListener)
        .toHaveBeenCalledWith("beforeunload", postSubmit.leaveCheck);
});

it('prevents default on leave check', async () => {
    //Arrange
    const form = getMockForm();
    const postSubmit = new PostSubmit(form);
    const event = {preventDefault: vi.fn()} as any;

    //Act
    postSubmit.leaveCheck(event);

    //Assert
    expect(event.preventDefault)
        .toHaveBeenCalled();
    expect(event.returnValue)
        .toEqual(true);
});

it('saves data on submit', async () => {
    //Arrange
    const form = getMockForm();
    const postSubmit = new PostSubmit(form);
    form.name = "test";
    form.getDataNode().value = {test: "test"};

    //Act
    postSubmit.submit({test: "test"});

    //Assert
    expect(sessionStorage.setItem)
        .toHaveBeenCalledWith("planaday-form-test", JSON.stringify({test: "test"}));
});

it('removes leave check on submit', async () => {
    //Arrange
    const form = getMockForm();
    const postSubmit = new PostSubmit(form);

    //Act
    postSubmit.submit({});

    //Assert
    expect(window.removeEventListener)
        .toHaveBeenCalledWith("beforeunload", postSubmit.leaveCheck);
});

it('submits form with post method to set url', async () => {
    //Arrange
    const form = getMockForm();
    const postSubmit = new PostSubmit(form);
    postSubmit.url = "test";

    //Act
    postSubmit.submit({});

    //Assert
    expect(form.getPlacedHtml()[0].setAttribute)
        .toHaveBeenCalledWith("method", "post");
    expect(form.getPlacedHtml()[0].setAttribute)
        .toHaveBeenCalledWith("action", "test");
    // @ts-ignore
    expect(form.getPlacedHtml()[0].submit)
        .toHaveBeenCalled();
});

it('logs error if form has not been placed yet', async () => {
    //Arrange
    const form = getMockForm();
    const postSubmit = new PostSubmit(form);
    form.getPlacedHtml = () => [];

    //Act
    postSubmit.submit({});

    //Assert
    expect(console.error)
        .toHaveBeenCalledWith("Form has not been placed yet, cannot submit with post");
    expect(window.removeEventListener)
        .toBeCalledTimes(0);
});

function getMockForm(): Form {
    let dataNode = {} as InnerNode;

    let formNode = new HTMLFormElement();

    let formBuilder = {} as FormBuilder;
    formBuilder.onComponentsCreated = getMockEvent();

    let form = {} as Form;
    form.getFormBuilder = () => formBuilder;
    form.onFormChange = getMockEvent();
    form.getDataNode = () => dataNode;
    form.getPlacedHtml = () => [formNode];
    return form;
}

function getMockEvent(): EventHandler<any> {
    let event = {} as EventHandler<any>;
    event.add = vi.fn();
    event.emit = vi.fn();
    return event;
}