// Components
import addressQuestions from "@/fmg-components/address-questions/address-questions";
import alert from "@/ux-components/alert/alert";

// Supporting Files
import { mount, shallowMount } from "@vue/test-utils";
import { getMountOptions } from "@/helpers/unit-test-helper.js";
import { storeMutations } from "@/constants/store-mutations";
import store from "@/store";

let autocompleteElement;
describe("address-questions.vue", () => {
    beforeEach(() => {
        // Create the `addressField1` element (autocomplete's input)
        autocompleteElement = document.createElement("input");
        autocompleteElement.getPlace = jest.fn();
        document.getElementById = jest.fn().mockReturnValue(autocompleteElement);
        document.getElementsByClassName = jest
            .fn()
            .mockReturnValue([document.createElement("div")]);
    });

    describe("initial state", () => {
        test("Should only show the street address field", () => {
            // Arrange
            const { wrapper } = setupMocks({});

            // Assert
            const streetAddressField = wrapper.findComponent({ ref: "autocomplete" });
            const cityField = wrapper.findComponent({ ref: "city" });
            const stateField = wrapper.findComponent({ ref: "state" });
            const zipCodeField = wrapper.findComponent({ ref: "zipCode" });

            expect(streetAddressField.exists()).toBe(true);
            expect(streetAddressField.isVisible()).toBe(true);
            expect(cityField.exists()).toBe(true);
            expect(cityField.isVisible()).toBe(false);
            expect(stateField.exists()).toBe(true);
            expect(stateField.isVisible()).toBe(false);
            expect(zipCodeField.exists()).toBe(true);
            expect(zipCodeField.isVisible()).toBe(false);

            const alerts = wrapper.findAllComponents(alert);
            expect(alerts.length).toEqual(0);
        });

        test("Should render addressQuestions sub-components (textbox-questions and dropdown-questions)", async () => {
            // Arrange
            const { wrapper } = setupMocks({});

            // Act
            const streetAddress = wrapper.findComponent({ ref: "autocomplete" });
            const city = wrapper.findComponent({ ref: "city" });
            const state = wrapper.findComponent({ ref: "state" });
            const zipCode = wrapper.findComponent({ ref: "zipCode" });

            // Assert
            expect(streetAddress.exists()).toBe(true);
            expect(city.exists()).toBe(true);
            expect(state.exists()).toBe(true);
            expect(zipCode.exists()).toBe(true);
        });

        test("Should render apartmentNumberOrBusinessName textbox-question when captureApartmentNumberOrBusinessName = true", async () => {
            // Arrange
            const { wrapper } = setupMocks({
                props: {
                    modelValue: {
                        streetAddress: "",
                        apartmentNumberOrBusinessName: "",
                        city: "",
                        state: "",
                        zipCode: "",
                    },
                    captureApartmentNumberOrBusinessName: true,
                },
            });

            await wrapper.setData({
                showAddressFields: true,
            });

            // Act
            const streetAddress = wrapper.findComponent({ ref: "autocomplete" });
            const apartmentNumberOrBusinessName = wrapper.findComponent({
                ref: "apartmentNumberOrBusinessName",
            });
            const city = wrapper.findComponent({ ref: "city" });
            const state = wrapper.findComponent({ ref: "state" });
            const zipCode = wrapper.findComponent({ ref: "zipCode" });

            // Assert
            expect(streetAddress.exists()).toBe(true);
            expect(apartmentNumberOrBusinessName.exists()).toBe(true);
            expect(apartmentNumberOrBusinessName.isVisible()).toBe(true);
            expect(city.exists()).toBe(true);
            expect(state.exists()).toBe(true);
            expect(zipCode.exists()).toBe(true);
        });

        test("Should *not* render apartmentNumberOrBusinessName textbox-question when captureApartmentNumberOrBusinessName = false", async () => {
            // Arrange
            const { wrapper } = setupMocks({
                props: {
                    modelValue: {
                        streetAddress: "",
                        apartmentNumberOrBusinessName: "",
                        city: "",
                        state: "",
                        zipCode: "",
                    },
                    captureApartmentNumberOrBusinessName: false,
                },
            });

            await wrapper.setData({
                showAddressFields: true,
            });

            // Act
            const streetAddress = wrapper.findComponent({ ref: "autocomplete" });
            const apartmentNumberOrBusinessName = wrapper.findComponent({
                ref: "apartmentNumberOrBusinessName",
            });
            const city = wrapper.findComponent({ ref: "city" });
            const state = wrapper.findComponent({ ref: "state" });
            const zipCode = wrapper.findComponent({ ref: "zipCode" });

            // Assert
            expect(streetAddress.exists()).toBe(true);
            expect(apartmentNumberOrBusinessName.exists()).toBe(true);
            expect(apartmentNumberOrBusinessName.isVisible()).toBe(false);
            expect(city.exists()).toBe(true);
            expect(state.exists()).toBe(true);
            expect(zipCode.exists()).toBe(true);
        });

        test("Should set this.showAddressFields to true when the model is prepopulated", async () => {
            // Arrange / Act
            const { wrapper } = setupMocks({
                props: {
                    modelValue: {
                        streetAddress: "foo",
                        city: "foo",
                        state: "foo",
                        zipCode: "55555",
                    },
                },
            });

            await wrapper.vm.$nextTick();

            // Assert
            expect(wrapper.vm.showAddressFields).toBe(true);
        });
    });

    describe("happy paths", () => {
        test("full street address is passed in => address fields are displayed", async () => {
            // Arrange/Act
            const { wrapper } = setupMocks({
                props: {
                    modelValue: {
                        streetAddress: "12345 Test Road",
                        city: "Tests",
                        state: "OH",
                        zipCode: "12312",
                    },
                },
            });

            await wrapper.vm.$nextTick();

            // Assert
            const cityField = wrapper.findComponent({ ref: "city" });
            const stateField = wrapper.findComponent({ ref: "state" });
            const zipField = wrapper.findComponent({ ref: "zipCode" });
            expect(cityField.exists()).toBeTruthy();
            expect(cityField.isVisible()).toBeTruthy();
            expect(stateField.exists()).toBeTruthy();
            expect(cityField.isVisible()).toBeTruthy();
            expect(zipField.exists()).toBeTruthy();
            expect(cityField.isVisible()).toBeTruthy();
        });

        test("full street address is passed in => don't load Google Autocomplete script", async () => {
            // Arrange/Act
            const { wrapper } = setupMocks({
                props: {
                    modelValue: {
                        streetAddress: "12345 Test Road",
                        city: "Tests",
                        state: "OH",
                        zipCode: "12312",
                    },
                },
            });

            await wrapper.vm.$nextTick();

            // Assert
            expect(wrapper.vm.$loadScript).not.toHaveBeenCalled();
        });

        // test("address field is typed into => disable autofill", async () => { // TEMPORARY
        //     // Arrange
        //     let inputEventCallbackFunction;
        //     autocompleteElement.addEventListener = jest
        //         .fn()
        //         .mockImplementation((eventName, callbackFunction) => {
        //             if (eventName == "input") {
        //                 inputEventCallbackFunction = callbackFunction;
        //             }
        //         });
        //     const { wrapper } = setupMocks({});
        //     await wrapper.vm.$nextTick();

        //     const e = {
        //         inputType: "insertText",
        //     };

        //     // Act
        //     inputEventCallbackFunction(e);
        //     await wrapper.vm.$nextTick();

        //     // Assert
        //     expect(autocompleteElement.getAttribute("autocomplete")).toEqual("new-password");
        // });

        test("street address is entered, user chooses good result from autocomplete results => other fields are filled in", async () => {
            // Arrange
            const { wrapper } = setupMocks({});
            await wrapper.setData({
                addressModel: {
                    streetAddress: "123 Test Street",
                },
            });

            const selectedPlace = {
                address_components: [
                    {
                        long_name: "1234",
                        short_name: "1234",
                        types: ["street_number"],
                    },
                    {
                        long_name: "Test Road",
                        short_name: "Test Road",
                        types: ["route"],
                    },
                    {
                        long_name: "East Columbus",
                        short_name: "Columbus",
                        types: ["neighborhood", "political"],
                    },
                    {
                        long_name: "Columbus",
                        short_name: "Columbus",
                        types: ["locality", "political"],
                    },
                    {
                        long_name: "Franklin County",
                        short_name: "Franklin County",
                        types: ["administrative_area_level_2", "political"],
                    },
                    {
                        long_name: "Ohio",
                        short_name: "OH",
                        types: ["administrative_area_level_1", "political"],
                    },
                    {
                        long_name: "United States",
                        short_name: "US",
                        types: ["country", "political"],
                    },
                    {
                        long_name: "43215",
                        short_name: "43215",
                        types: ["postal_code"],
                    },
                ],
            };

            // Act
            autocompleteElement.dispatchEvent(
                new CustomEvent("place_changed", { detail: selectedPlace })
            );

            // Assert
            wrapper.vm.$nextTick(function () {
                const addressModel = wrapper.vm.addressModel;
                expect(addressModel.streetAddress).toEqual("1234 Test Road");
                expect(addressModel.city).toEqual("Columbus");
                expect(addressModel.state).toEqual("OH");
                expect(addressModel.zipCode).toEqual("43215");
            });
        });

        // test("street address is entered, but user clicks away => first result is selected and other fields are filled in", async () => {
        //     // Arrange
        //     let changeEventCallbackFunction;
        //     autocompleteElement.addEventListener = jest
        //         .fn()
        //         .mockImplementation((eventName, callbackFunction) => {
        //             if (eventName == "change") {
        //                 changeEventCallbackFunction = callbackFunction;
        //             }
        //         });

        //     const { wrapper } = setupMocks({
        //         querySelectorFunction: function (query) {
        //             if (query == ".pac-container .pac-item > .pac-item-query") {
        //                 let element = document.createElement("div");
        //                 element.textContent = "123 Test Street";
        //                 return element;
        //             } else if (query == ".pac-container .pac-item > span:nth-child(3)") {
        //                 let element = document.createElement("div");
        //                 element.textContent = "Columbus, OH 43230";
        //                 return element;
        //             }
        //         },
        //         geocoderResult: {
        //             address_components: [
        //                 {
        //                     long_name: "1234",
        //                     short_name: "1234",
        //                     types: ["street_number"],
        //                 },
        //                 {
        //                     long_name: "Test Road",
        //                     short_name: "Test Road",
        //                     types: ["route"],
        //                 },
        //                 {
        //                     long_name: "East Columbus",
        //                     short_name: "Columbus",
        //                     types: ["neighborhood", "political"],
        //                 },
        //                 {
        //                     long_name: "Columbus",
        //                     short_name: "Columbus",
        //                     types: ["locality", "political"],
        //                 },
        //                 {
        //                     long_name: "Franklin County",
        //                     short_name: "Franklin County",
        //                     types: ["administrative_area_level_2", "political"],
        //                 },
        //                 {
        //                     long_name: "Ohio",
        //                     short_name: "OH",
        //                     types: ["administrative_area_level_1", "political"],
        //                 },
        //                 {
        //                     long_name: "United States",
        //                     short_name: "US",
        //                     types: ["country", "political"],
        //                 },
        //                 {
        //                     long_name: "43215",
        //                     short_name: "43215",
        //                     types: ["postal_code"],
        //                 },
        //             ],
        //         },
        //     });

        //     let noMatchAlert = wrapper.findComponent({ ref: "alertNoMatchWarning" });
        //     let verificationAlert = wrapper.findComponent({ ref: "alertVerificationWarning" });
        //     expect(noMatchAlert.exists()).toBeFalsy();
        //     expect(verificationAlert.exists()).toBeFalsy();

        //     await wrapper.vm.$nextTick();

        //     // Act
        //     changeEventCallbackFunction();

        //     await wrapper.vm.$nextTick();

        //     // Assert
        //     const addressModel = wrapper.vm.addressModel;
        //     expect(addressModel.streetAddress).toEqual("1234 Test Road");
        //     expect(addressModel.city).toEqual("Columbus");
        //     expect(addressModel.state).toEqual("OH");
        //     expect(addressModel.zipCode).toEqual("43215");
        // });
    });

    describe("alerts", () => {
        test("user enters address that yields no autocomplete results and pressed enter or tab => show noMatch alert", async () => {
            // Arrange
            let changeEventCallbackFunction;
            autocompleteElement.addEventListener = jest
                .fn()
                .mockImplementation((eventName, callbackFunction) => {
                    if (eventName == "keydown") {
                        changeEventCallbackFunction = callbackFunction;
                    }
                });

            const { wrapper } = setupMocks({});

            let noMatchAlert = wrapper.findComponent({ ref: "alertNoMatchWarning" });
            expect(noMatchAlert.exists()).toBeFalsy();

            await wrapper.vm.$nextTick();

            const e = {
                code: "Enter",
            };

            // Act
            //autocompleteElement.dispatchEvent(new Event("keydown"), e);
            changeEventCallbackFunction(e);

            await wrapper.vm.$nextTick();

            // Assert
            expect(wrapper.vm.displayNoMatchWarning).toBeTruthy();
            noMatchAlert = wrapper.findComponent({ ref: "alertNoMatchWarning" });
            expect(noMatchAlert.exists()).toBeTruthy();
            expect(noMatchAlert.isVisible()).toBeTruthy();
        });

        // test("user enters address that yields autocomplete results, but doesn't select => show verification alert", async () => {
        //     // Arrange
        //     let changeEventCallbackFunction;
        //     autocompleteElement.addEventListener = jest
        //         .fn()
        //         .mockImplementation((eventName, callbackFunction) => {
        //             if (eventName == "change") {
        //                 changeEventCallbackFunction = callbackFunction;
        //             }
        //         });

        //     const { wrapper } = setupMocks({
        //         querySelectorFunction: function (query) {
        //             if (query == ".pac-container .pac-item > .pac-item-query") {
        //                 let element = document.createElement("div");
        //                 element.textContent = "123 Test Street";
        //                 return element;
        //             } else if (query == ".pac-container .pac-item > span:nth-child(3)") {
        //                 let element = document.createElement("div");
        //                 element.textContent = "Columbus, OH 43230";
        //                 return element;
        //             }
        //         },
        //     });

        //     let noMatchAlert = wrapper.findComponent({ ref: "alertNoMatchWarning" });
        //     let verificationAlert = wrapper.findComponent({ ref: "alertVerificationWarning" });
        //     expect(noMatchAlert.exists()).toBeFalsy();
        //     expect(verificationAlert.exists()).toBeFalsy();

        //     await wrapper.vm.$nextTick();

        //     // Act
        //     changeEventCallbackFunction();

        //     await wrapper.vm.$nextTick();

        //     // Assert
        //     verificationAlert = wrapper.findComponent({ ref: "alertVerificationWarning" });
        //     expect(wrapper.vm.displayVerificationWarning).toBeTruthy();
        //     expect(verificationAlert.exists()).toBeTruthy();
        //     expect(verificationAlert.isVisible()).toBeTruthy();
        //     noMatchAlert = wrapper.findComponent({ ref: "alertNoMatchWarning" });
        //     expect(wrapper.vm.displayNoMatchWarning).toBeFalsy();
        //     expect(noMatchAlert.exists()).toBeFalsy();
        // });

        describe("noMatch alert is cleared on address change", () => {
            test("user sees noMatch warning and modifies street address => noMatch warning is removed", async () => {
                // Arrange
                const { wrapper } = setupMocks({
                    props: {
                        modelValue: {
                            streetAddress: "LS",
                            city: "",
                            state: "",
                            zipCode: "",
                        },
                    },
                });

                // Initial condition to display noMatch warning
                await wrapper.setData({
                    matchFound: false,
                });

                // Act
                wrapper.vm.addressModel.streetAddress = "LJS";
                await wrapper.vm.$nextTick();

                // Assert
                let noMatchAlert = wrapper.findComponent({ ref: "alertNoMatchWarning" });
                expect(noMatchAlert.exists()).toBeFalsy();
            });

            test("user sees noMatch warning and enters city => noMatch warning is removed", async () => {
                // Arrange
                const { wrapper } = setupMocks({
                    props: {
                        modelValue: {
                            streetAddress: "",
                            city: "LS",
                            state: "",
                            zipCode: "",
                        },
                    },
                });

                // Initial condition to display noMatch warning
                await wrapper.setData({
                    matchFound: false,
                });

                // Act
                wrapper.vm.addressModel.city = "LJS";
                await wrapper.vm.$nextTick();

                // Assert
                let noMatchAlert = wrapper.findComponent({ ref: "alertNoMatchWarning" });
                expect(noMatchAlert.exists()).toBeFalsy();
            });

            test("user sees noMatch warning and enters state => noMatch warning is removed", async () => {
                // Arrange
                const { wrapper } = setupMocks({
                    props: {
                        modelValue: {
                            streetAddress: "",
                            city: "",
                            state: "LS",
                            zipCode: "",
                        },
                    },
                });

                // Initial condition to display noMatch warning
                await wrapper.setData({
                    matchFound: false,
                });

                // Act
                wrapper.vm.addressModel.state = "LJS";
                await wrapper.vm.$nextTick();

                // Assert
                let noMatchAlert = wrapper.findComponent({ ref: "alertNoMatchWarning" });
                expect(noMatchAlert.exists()).toBeFalsy();
            });

            test("user sees noMatch warning and enters zip code => noMatch warning is removed", async () => {
                // Arrange
                const { wrapper } = setupMocks({
                    props: {
                        modelValue: {
                            streetAddress: "",
                            city: "",
                            state: "",
                            zipCode: "LS",
                        },
                    },
                });

                // Initial condition to display noMatch warning
                await wrapper.setData({
                    matchFound: false,
                });

                // Act
                wrapper.vm.addressModel.zipCode = "LJS";
                await wrapper.vm.$nextTick();

                // Assert
                let noMatchAlert = wrapper.findComponent({ ref: "alertNoMatchWarning" });
                expect(noMatchAlert.exists()).toBeFalsy();
            });
        });
    });
});

function setupMocks({
    mountOptions,
    props,
    isShallowMount = true,
    querySelectorFunction,
    geocoderResult = ["1234 Test Street"],
    matchFound = false,
}) {
    store.commit(storeMutations.RESET_STATE);

    const resultingMountOptions = getMountOptions({
        ...mountOptions,
        router: {
            navigate: jest.fn(),
            navigate: jest.fn(),
        },
        loadScript: jest.fn().mockResolvedValue(),
    });

    if (props) resultingMountOptions.propsData = props;

    if (isShallowMount) {
        resultingMountOptions.global.stubs = {
            ...(resultingMountOptions.global.stubs ?? {}),
            textboxQuestion: {
                template: `<span></span>`,
                methods: {
                    handleChange: jest.fn(),
                },
            },
            dropdownQuestion: {
                template: `<span></span>`,
                methods: {
                    handleChange: jest.fn(),
                },
            },
        };
    }

    const wrapper = isShallowMount
        ? shallowMount(addressQuestions, resultingMountOptions)
        : mount(addressQuestions, resultingMountOptions);

    window.google = {
        maps: {
            event: {
                addListener: jest
                    .fn()
                    .mockImplementation((element, eventName, callbackFunction) => {
                        function interceptedCallbackFunction(e) {
                            callbackFunction(e.detail);
                        }
                        // selectedPlace = "Woogly";
                        element.addEventListener(eventName, interceptedCallbackFunction);
                    }),
                removeListener: jest.fn(),
                clearInstanceListeners: jest.fn(),
                trigger: jest.fn().mockImplementation((element, eventName) => {
                    wrapper.vm.matchFound = matchFound;
                }),
            },
            places: {
                Autocomplete: jest.fn().mockImplementation((el) => el),
            },
            Geocoder: class Geocoder {
                // constructor();
                geocode(request, callback) {
                    callback([geocoderResult], true);
                }
            },
            GeocoderStatus: {
                OK: true,
            },
        },
    };

    document.querySelector = jest.fn().mockImplementation((query) => {
        let result = null;
        if (query == ".pac-container") result = document.createElement("div");
        else if (querySelectorFunction) {
            result = querySelectorFunction(query);
        }

        return result ?? null;
    });

    return { wrapper };
}
