import { shallowMount } from "@vue/test-utils";
import vinLookup from "./vin-lookup.vue";
import { getMountOptions } from "@/helpers/unit-test-helper.js";
import { navigationScenarios } from "@/router/router-constants/navigation-scenarios.js";
import { settleAllPromises } from "@/helpers/layout-helper.js";
import { experimentSettings } from "@/constants/experiments";

import store from "@/store";

jest.mock("@/store", () => ({
    commit: jest.fn(),
    dispatch: jest.fn(),
    getters: {
        vehicle: {
            year: 2019,
            carId: "C00000",
        },
        order: {
            serviceLocation: {
                zipCode: "45253",
            },
            customer: {
                emailAddress: "builddigitaltest@safelite.com",
            },
            referralNumber: "666666",
        },
        payment: {
            insuranceCoverage: {
                isVerified: true,
            },
        },
        damage: {
            glassToReplace: "windshield",
        },
        isHeavyTruckVehicle: true,
    },
}));

// Mock our module for promises.
jest.mock("@/helpers/layout-helper.js", () => ({
    settleAllPromises: jest.fn(),
}));

jest.mock("@/helpers/damage-helper", () => ({
    isGlassAvailableForCarId: jest.fn(() => {
        return Promise.resolve();
    }),
    getIsWindshieldOnly: jest.fn(),
    getDamageString: jest.fn(),
}));

describe("vin-lookup.vue", () => {
    it("Should update the nav-bar forward button when VIN is changed", (done) => {
        //Arrange
        const { wrapper } = setupMocks({});
        //Act
        wrapper.setData({ vin: "newValue" });
        //Assert
        wrapper.vm.$nextTick(() => {
            expect(wrapper.vm.$refs.navbar.updateButtonText).toBeCalled();
            done();
        });
    });

    it("Should call navigateForward() if the store carId matches the vin response carId and forward button is clicked", async () => {
        // Arrange
        const { wrapper } = setupMocks({});
        mockOutPromises({ carId: "C00000" });
        wrapper.vm.navigateForward = jest.fn();

        // Act
        await wrapper.vm.forwardButtonAction();

        //Assert
        expect(wrapper.vm.navigateForward).toHaveBeenCalled();
    });

    it("Should not call navigateForward() if the store carId does not match the vin response carId and forward button is clicked", async () => {
        // Arrange
        const { wrapper } = setupMocks({});
        mockOutPromises({ carId: "C11111" });

        wrapper.vm.vinTouched = true;
        wrapper.vm.vin = "";
        wrapper.vm.initialVin = "foo";
        wrapper.vm.navigateForward = jest.fn();

        // Act
        await wrapper.vm.forwardButtonAction();

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

    it("Should call navigateForward() if the store carId does not match the vin response carId but does match previously enterted carId and forward button is clicked", async () => {
        // Arrange
        const { wrapper } = setupMocks({});
        mockOutPromises({ carId: "C11111" });

        wrapper.vm.lookupVehicle = jest.fn().mockImplementation(() => vinPromise);
        wrapper.vm.navigateForward = jest.fn();
        wrapper.vm.previouslyEnteredCarId = "C11111";

        // Act
        await wrapper.vm.forwardButtonAction();

        //Assert
        expect(wrapper.vm.navigateForward).toHaveBeenCalled();
    });

    it("Should not call navigateForward() if zip service returns a non-serviceable flag", async () => {
        // Arrange
        const { wrapper } = setupMocks({});
        const zipValidationApiResponse = {
            data: {
                isServiceable: false,
            },
        };

        const zipPromise = Promise.resolve(zipValidationApiResponse);

        wrapper.vm.validateZip = jest.fn().mockImplementation(() => zipPromise);
        wrapper.vm.setupUiForNonServiceableZip = jest.fn();
        wrapper.vm.navigateForward = jest.fn();

        wrapper.vm.previouslyEnteredCarId = "new carId";

        // Act
        await wrapper.vm.forwardButtonAction();

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

    it("Should not call navigateForward() when forward button is clicked but lookupVehicle errors out.", async () => {
        // Arrange
        const { wrapper } = setupMocks({});
        wrapper.vm.vinTouched = true;
        wrapper.vm.vin = "foo";
        wrapper.vm.initialVin = "!foo";
        wrapper.vm.navigateForward = jest.fn();

        wrapper.vm.previouslyEnteredCarId = "new carId";

        // Act
        await wrapper.vm.forwardButtonAction();

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

    describe("navigateForward", () => {
        test("carId is different from returned vehicle and selected glass isn't available => continue with different glass", async () => {
            // Arrange
            const { wrapper } = setupMocks({
                customMountOptions: {
                    router: {
                        navigateWithSaving: jest.fn(),
                    },
                },
            });
            wrapper.vm.navigateForwardWithSingleCarMatch = jest.fn();
            wrapper.setData({
                isCarIdDifferent: true,
                isSelectedGlassAvailableForVehicle: false,
            });

            // Act
            await wrapper.vm.navigateForward();

            //Assert
            expect(wrapper.vm.$router.navigateWithSaving).toBeCalledTimes(1);
            expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledWith(
                navigationScenarios.SELECTED_VIN_WITH_MISMATCHED_GLASS,
                wrapper.vm.$route,
                expect.anything(),
                expect.anything()
            );
        });

        test("carId matches => navigateForwardWithSingleCarMatch", async () => {
            // Arrange
            const { wrapper } = setupMocks({});
            wrapper.vm.navigateForwardWithSingleCarMatch = jest.fn();
            wrapper.setData({
                isCarIdDifferent: false,
            });

            // Act
            await wrapper.vm.navigateForward();

            //Assert
            expect(wrapper.vm.navigateForwardWithSingleCarMatch).toBeCalledTimes(1);
        });

        test("selected glass is available for returned vehicle => navigateForwardWithSingleCarMatch", async () => {
            // Arrange
            const { wrapper } = setupMocks({});
            wrapper.vm.navigateForwardWithSingleCarMatch = jest.fn();
            wrapper.setData({
                isSelectedGlassAvailableForVehicle: true,
            });

            // Act
            await wrapper.vm.navigateForward();

            //Assert
            expect(wrapper.vm.navigateForwardWithSingleCarMatch).toBeCalledTimes(1);
        });
    });

    describe("alerts", () => {
        test("Zip is invalid => show AlertInvalidZipWidget", async () => {
            // Arrange
            const { wrapper } = setupMocks({});
            mockOutPromises({ isZipValid: false });
            await wrapper.setData({ serviceZipCode: "11111" });

            // Act
            await wrapper.vm.forwardButtonAction();

            // Assert
            expect(wrapper.vm.displayInvalidZipAlert).toEqual(true);
            expect(wrapper.findComponent({ ref: "alertInvalidZip" }).exists()).toBe(true);
        });

        test("alerts are displayed and continue button is clicked with issues fixed => alerts are reset", async () => {
            // Arrange
            const { wrapper } = setupMocks({});
            mockOutPromises({ isZipValid: true, isZipServiceable: true, carId: "CARID" });
            await wrapper.setData({
                displayInvalidZipAlert: true,
                displayMatchedDifferentVehicleAlert: true,
                displayNonServiceableZipAlert: true,
                displayVinNotFoundAlert: true,
            });

            // sanity check that there are alerts
            expect(wrapper.findAllComponents({ name: "alert" }).length).toBe(4);

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

            // Assert
            expect(wrapper.findAllComponents({ name: "alert" }).length).toBe(0);
        });

        test("Error emitted by textbox-question element => VinScanFailed alert shown.", async () => {
            // Arrange
            const { wrapper } = setupMocks({});

            // Act
            const vinLookup = wrapper.findComponent('[data-test="vin-lookup-component"]');
            vinLookup.trigger("imageLookupError");
            await wrapper.vm.$nextTick();

            // Assert
            expect(wrapper.findAllComponents({ name: "alert" }).length).toBe(1);
        });
    });

    describe("getVinFromImage", () => {
        test("GetVinFromImage resolves with first valid VIN when any vins are returned.", async () => {
            // Arrange
            const responseValue = "testResponse";
            const lookup = jest.fn().mockImplementation(() => {
                return new Promise((resolve, reject) => resolve([responseValue]));
            });

            const image = {};

            const storeMixin = {
                methods: {
                    dispatchStoreAction: lookup,
                },
            };

            const { wrapper } = setupMocks({
                mixins: [storeMixin],
            });

            // Act
            const response = await wrapper.vm.getVinFromImage(image);

            // Assert
            expect(lookup).toHaveBeenCalled();
            expect(response).toEqual(responseValue);
        });

        test("GetVinFromImage rejects if no vins are returned.", async () => {
            // Arrange
            const lookup = jest.fn().mockImplementation(() => {
                return new Promise((resolve, reject) => resolve([]));
            });

            const image = {};

            const storeMixin = {
                methods: {
                    dispatchStoreAction: lookup,
                },
            };

            const { wrapper } = setupMocks({
                mixins: [storeMixin],
            });

            // Act
            const promise = wrapper.vm.getVinFromImage(image);

            // Assert
            await expect(promise).rejects.toEqual("No VINs detected.");
        });

        test("GetVinFromImage rejects if an error occurs.", async () => {
            const lookup = jest.fn().mockImplementation(() => {
                return new Promise((resolve, reject) => reject());
            });

            const image = {};

            const storeMixin = {
                methods: {
                    dispatchStoreAction: lookup,
                },
            };

            const { wrapper } = setupMocks({
                mixins: [storeMixin],
            });

            // Act
            const promise = wrapper.vm.getVinFromImage(image);

            // Assert
            await expect(promise).rejects.toEqual("An error occurred during the lookup.");
        });
    });
});

function setupMocks({ customMountOptions }) {
    const mountOptions = getMountOptions({
        ...customMountOptions,
    });

    // Modify/augment default mount options
    mountOptions.global.mocks["$store"] = store;
    mountOptions.global.mixins = [mockMixin];
    mountOptions["attachTo"] = document.body; // append wrapper to document.body to test DOM methods
    mountOptions.route = { query: {} };

    const wrapper = shallowMount(vinLookup, mountOptions);
    mockOutStubFunctions(wrapper);
    return { wrapper };
}

function mockOutPromises({ carId, isZipValid = true, isZipServiceable = true }) {
    const apiResponses = {
        vehicleLookupResponse: {
            carId: carId,
        },
        zipCodeData: {
            isValid: isZipValid,
            isServiceable: isZipServiceable,
            state: "OH",
        },
    };

    settleAllPromises.mockImplementation(() => apiResponses);
}

function mockOutStubFunctions(wrapper) {
    wrapper.vm.$refs.navbar.updateButtonText = jest.fn();
    wrapper.vm.$refs.navbar.removeLoader = jest.fn();
    wrapper.vm.getZipCodeData = jest
        .fn()
        .mockReturnValue({ isValid: true, isServiceable: true, state: "OH" });
}

const mockMixin = {
    methods: {
        getCmsContent: jest.fn(() => "placeholder CMS content"),
        getSettingValue: jest.fn((settingName) => {
            if (settingName === experimentSettings.IS_EMAIL_OPTIONAL) {
                return "true";
            }
            return "false";
        }),
    },
};
