import { shallowMount, mount } from '@vue/test-utils';
import inputButtonWrapperMixin from '@/mixins/input-button-wrapper-mixin';
import shopListButton from '@/iss-components/shop-list-button/shop-list-button.vue';

const buttonLabelReference = 'span[id="buttonLabelSpan"]';
const buttonLabelCopyReference = 'span[id="buttonLabelSubCopySpan"]';
const availabilityIndicatorBlockReference = 'div[id="availabilityIndicatorBlock"]';
const loaderReference = { ref: 'availabilityIndicatorLoader' };
const availabilityBadgeReference = 'div[id="availabilityBadge"]';
const buttonBodyCopyReference = 'span[id="buttonBodyCopy"]';
const screenReaderOnlySpanReference = '#screenReaderOnlyTextSpan';

describe('Shop list button', () => {
    describe('should render', () => {
        test('correctly with required props', async () => {
            // Arrange
            const wrapper = shallowMount(shopListButton, {
                propsData: {
                    value: 1234,
                    modelValue: 'value of modal',
                    groupName: 'name of group'
                },
                mixins: [inputButtonWrapperMixin]
            });
            await wrapper.vm.$nextTick();

            // Assert
            expect(wrapper.html()).toMatchSnapshot();
        });
        test('correctly with all relevant props', async () => {
            const additionalButtonData = {
                displayAvailabilityIndicators: true,
                availabilityRatingCallback: () => Promise.resolve(null)
            };
            const wrapper = shallowMount(shopListButton, {
                propsData: {
                    buttonLabel: 'Label of button',
                    buttonLabelSubCopy: 'Sub copy of button',
                    selectedValue: 'selected value',
                    buttonBodyCopy: 'button body copy',
                    screenReaderOnlyText: 'screen reader only text',
                    value: 1234,
                    modelValue: 'value of modal',
                    groupName: 'name of group',
                    additionalButtonData
                },
                mixins: [inputButtonWrapperMixin]
            });
            await wrapper.vm.$nextTick();

            // Assert
            expect(wrapper.html()).toMatchSnapshot();
        });
        test('expected data with only required props', async () => {
            // Arrange
            const wrapper = shallowMount(shopListButton, {
                propsData: {
                    value: 1234,
                    modelValue: 'value of modal',
                    groupName: 'name of group'
                },
                mixins: [inputButtonWrapperMixin]
            });
            await wrapper.vm.$nextTick();

            // Assert
            expect(wrapper.vm.$data).toMatchSnapshot();
        });
        test('expected data with all relevant props', async () => {
            // Arrange
            const wrapper = shallowMount(shopListButton, {
                propsData: {
                    value: 1234,
                    modelValue: 'value of modal',
                    groupName: 'name of group',
                    additionalButtonData: {
                        displayAvailabilityIndicators: true,
                        availabilityRatingCallback: () => Promise.resolve(null)
                    }
                },
                mixins: [inputButtonWrapperMixin]
            });
            await wrapper.vm.$nextTick();

            // Assert
            expect(wrapper.vm.$data).toMatchSnapshot();
        });
        describe('button label with expected classes', () => {
            test('when "textPosition" prop provided', async () => {
                // Arrange
                const textPosition = 'positionOfText';
                const wrapper = mount(shopListButton, {
                    propsData: {
                        value: 1234,
                        modelValue: 'value of modal',
                        groupName: 'name of group',
                        buttonLabel: 'label of button',
                        textPosition
                    },
                    mixins: [inputButtonWrapperMixin]
                });
                await wrapper.vm.$nextTick();

                // Act
                const buttonLabel = wrapper.find(buttonLabelReference);

                // Assert
                expect(buttonLabel.exists()).toBeTruthy();
                expect(buttonLabel.classes()).toContain(textPosition);
            });
            test('when "textPosition" prop not provided', async () => {
                // Arrange
                const wrapper = mount(shopListButton, {
                    propsData: {
                        value: 1234,
                        modelValue: 'value of modal',
                        groupName: 'name of group',
                        buttonLabel: 'label of button'
                    },
                    mixins: [inputButtonWrapperMixin]
                });
                await wrapper.vm.$nextTick();

                // Act
                const buttonLabel = wrapper.find(buttonLabelReference);

                // Assert
                expect(buttonLabel.exists()).toBeTruthy();
            });
        });
        describe('button label sub copy with expected classes', () => {
            test('when "textPosition" prop provided', async () => {
                // Arrange
                const textPosition = 'positionOfText';
                const wrapper = mount(shopListButton, {
                    propsData: {
                        value: 1234,
                        modelValue: 'value of modal',
                        groupName: 'name of group',
                        buttonLabel: 'label of button',
                        textPosition
                    },
                    mixins: [inputButtonWrapperMixin]
                });
                await wrapper.vm.$nextTick();

                // Act
                const buttonLabelSubCopy = wrapper.find(buttonLabelCopyReference);

                // Assert
                expect(buttonLabelSubCopy.exists()).toBeTruthy();
                expect(buttonLabelSubCopy.classes().length).toBe(3);
                expect(buttonLabelSubCopy.classes()).toContain('m-0');
                expect(buttonLabelSubCopy.classes()).toContain('button-label-sub-copy');
                expect(buttonLabelSubCopy.classes()).toContain(textPosition);
            });
            test('when "textPosition" prop not provided', async () => {
                // Arrange
                const wrapper = mount(shopListButton, {
                    propsData: {
                        value: 1234,
                        modelValue: 'value of modal',
                        groupName: 'name of group',
                        buttonLabel: 'label of button'
                    },
                    mixins: [inputButtonWrapperMixin]
                });
                await wrapper.vm.$nextTick();

                // Act
                const buttonLabelSubCopy = wrapper.find(buttonLabelCopyReference);

                // Assert
                expect(buttonLabelSubCopy.exists()).toBeTruthy();
                expect(buttonLabelSubCopy.classes().length).toBe(2);
                expect(buttonLabelSubCopy.classes()).toContain('m-0');
                expect(buttonLabelSubCopy.classes()).toContain('button-label-sub-copy');
            });
        });
        describe('availability indicator block with expected when displayAvailabilityIndicators true', () => {
            test('and availability rating is high', async () => {
                // Arrange
                const additionalButtonData = {
                    displayAvailabilityIndicators: true,
                    availabilityRatingCallback: () => Promise.resolve('high')
                };
                const wrapper = mount(shopListButton, {
                    propsData: {
                        value: 1234,
                        modelValue: 'value of modal',
                        groupName: 'name of group',
                        buttonLabel: 'label of button',
                        additionalButtonData
                    },
                    mixins: [inputButtonWrapperMixin]
                });
                await wrapper.vm.$nextTick();

                // Act
                const availabilityIndicatorBlock = wrapper.find(availabilityIndicatorBlockReference);

                // Assert
                expect(availabilityIndicatorBlock.exists()).toBeTruthy();
                expect(availabilityIndicatorBlock.classes()).toContain('availability-indicator');
                expect(availabilityIndicatorBlock.classes()).toContain('rounded-pill');
                expect(availabilityIndicatorBlock.classes()).toContain('green');
            });
            test.each(['low', ''])(
                'and availability rating is not high or null',
                async (availabilityBadge) => {
                    // Arrange
                    const additionalButtonData = {
                        displayAvailabilityIndicators: true,
                        availabilityRatingCallback: () => Promise.resolve(availabilityBadge)
                    };
                    const wrapper = mount(shopListButton, {
                        propsData: {
                            value: 1234,
                            modelValue: 'value of modal',
                            groupName: 'name of group',
                            buttonLabel: 'label of button',
                            additionalButtonData
                        },
                        mixins: [inputButtonWrapperMixin]
                    });
                    await wrapper.vm.$nextTick();

                    // Act
                    const availabilityIndicatorBlock = wrapper.find(availabilityIndicatorBlockReference);

                    // Assert
                    expect(availabilityIndicatorBlock.exists()).toBeTruthy();
                    expect(availabilityIndicatorBlock.classes()).toContain('availability-indicator');
                    expect(availabilityIndicatorBlock.classes()).toContain('rounded-pill');
                    expect(availabilityIndicatorBlock.classes()).toContain('orange');
                }
            );
            test.each([null, undefined])(
                'and availability rating is null',
                async (availabilityRating) => {
                    // Arrange
                    const additionalButtonData = {
                        displayAvailabilityIndicators: true,
                        availabilityRatingCallback: () => Promise.resolve(availabilityRating)
                    };
                    const wrapper = mount(shopListButton, {
                        propsData: {
                            value: 1234,
                            modelValue: 'value of modal',
                            groupName: 'name of group',
                            buttonLabel: 'label of button',
                            additionalButtonData
                        },
                        mixins: [inputButtonWrapperMixin]
                    });
                    await wrapper.vm.$nextTick();

                    // Act
                    const availabilityIndicatorBlock = wrapper.find(availabilityIndicatorBlockReference);

                    // Assert
                    expect(availabilityIndicatorBlock.exists()).toBeTruthy();
                    expect(availabilityIndicatorBlock.classes()).toContain('availability-indicator');
                    expect(availabilityIndicatorBlock.classes()).toContain('rounded-pill');
                    expect(availabilityIndicatorBlock.classes()).toContain('gray');
                }
            );
        });
        test('availability indicator block with expected classes when displayAvailabilityIndicators true', async () => {
            // Arrange
            const additionalButtonData = {
                displayAvailabilityIndicators: true,
                availabilityRatingCallback: () => Promise.resolve({})
            };
            const wrapper = mount(shopListButton, {
                propsData: {
                    value: 1234,
                    modelValue: 'value of modal',
                    groupName: 'name of group',
                    buttonLabel: 'label of button',
                    additionalButtonData
                },
                mixins: [inputButtonWrapperMixin]
            });
            await wrapper.vm.$nextTick();

            // Act
            const availabilityIndicatorBlock = wrapper.find(availabilityIndicatorBlockReference);

            // Assert
            expect(availabilityIndicatorBlock.exists()).toBeTruthy();
            expect(availabilityIndicatorBlock.classes()).toContain('availability-indicator');
            expect(availabilityIndicatorBlock.classes()).toContain('rounded-pill');
        });
        describe('availability badge when displayAvailabilityIndicators true', () => {
            test('and "availabilityRating" is "high"', async () => {
                // Arrange
                const additionalButtonData = {
                    displayAvailabilityIndicators: true,
                    availabilityRatingCallback: () => Promise.resolve('high')
                };
                const wrapper = mount(shopListButton, {
                    propsData: {
                        value: 1234,
                        modelValue: 'value of modal',
                        groupName: 'name of group',
                        buttonLabel: 'label of button',
                        additionalButtonData
                    },
                    mixins: [inputButtonWrapperMixin]
                });
                await wrapper.vm.$nextTick();

                // Act
                const availabilityBadge = wrapper.find(availabilityBadgeReference);

                // Assert
                expect(availabilityBadge.exists()).toBeTruthy();
                expect(availabilityBadge.classes().length).toBe(2);
                expect(availabilityBadge.classes()).toContain('availability-badge');
                expect(availabilityBadge.classes()).toContain('green');
            });
            test.each(['low', ''])(
                'and "availabilityRating" is not "high" or null',
                async (availabilityRating) => {
                    // Arrange
                    const additionalButtonData = {
                        displayAvailabilityIndicators: true,
                        availabilityRatingCallback: () => Promise.resolve(availabilityRating)
                    };
                    const wrapper = mount(shopListButton, {
                        propsData: {
                            value: 1234,
                            modelValue: 'value of modal',
                            groupName: 'name of group',
                            buttonLabel: 'label of button',
                            additionalButtonData
                        },
                        mixins: [inputButtonWrapperMixin]
                    });
                    await wrapper.vm.$nextTick();

                    // Act
                    const availabilityBadge = wrapper.find(availabilityBadgeReference);

                    // Assert
                    expect(availabilityBadge.exists()).toBeTruthy();
                    expect(availabilityBadge.classes().length).toBe(2);
                    expect(availabilityBadge.classes()).toContain('availability-badge');
                    expect(availabilityBadge.classes()).toContain('orange');
                }
            );
        });
        test('button body copy when buttonBodyCopy provided', async () => {
            // Arrange
            const additionalButtonData = {
                displayAvailabilityIndicators: true,
                availabilityRatingCallback: () => Promise.resolve({})
            };
            const wrapper = mount(shopListButton, {
                propsData: {
                    value: 1234,
                    modelValue: 'value of modal',
                    groupName: 'name of group',
                    buttonLabel: 'label of button',
                    additionalButtonData,
                    buttonBodyCopy: 'body copy'
                },
                mixins: [inputButtonWrapperMixin]
            });
            await wrapper.vm.$nextTick();

            // Act
            const buttonBodyCopy = wrapper.find(buttonBodyCopyReference);

            // Assert
            expect(buttonBodyCopy.exists()).toBeTruthy();
            expect(buttonBodyCopy.classes()).toContain('m-0');
            expect(buttonBodyCopy.classes()).toContain('button-body-copy');
        });
        test('screen reader only text when screenReaderOnlyText provided', async () => {
            // Arrange
            const additionalButtonData = {
                displayAvailabilityIndicators: true,
                availabilityRatingCallback: () => Promise.resolve({})
            };
            const screenReaderOnlyText = 'text just for screen reader';
            const wrapper = mount(shopListButton, {
                propsData: {
                    value: 1234,
                    modelValue: 'value of modal',
                    groupName: 'name of group',
                    buttonLabel: 'label of button',
                    additionalButtonData,
                    screenReaderOnlyText
                },
                mixins: [inputButtonWrapperMixin]
            });
            await wrapper.vm.$nextTick();

            // Act
            const screenReaderOnlySpan = wrapper.find(screenReaderOnlySpanReference);

            // Assert
            expect(screenReaderOnlySpan.exists()).toBeTruthy();
            expect(screenReaderOnlySpan.classes()).toContain('sr-only');
            expect(screenReaderOnlySpan.text()).toContain(screenReaderOnlyText);
        });
    });
    describe('should not render', () => {
        test('button body copy when buttonBodyCopy not provided', async () => {
            // Arrange
            const additionalButtonData = {
                displayAvailabilityIndicators: true,
                availabilityRatingCallback: () => Promise.resolve({})
            };
            const wrapper = mount(shopListButton, {
                propsData: {
                    value: 1234,
                    modelValue: 'value of modal',
                    groupName: 'name of group',
                    buttonLabel: 'label of button',
                    additionalButtonData
                },
                mixins: [inputButtonWrapperMixin]
            });
            await wrapper.vm.$nextTick();

            // Act
            const buttonBodyCopy = wrapper.find(buttonBodyCopyReference);

            // Assert
            expect(buttonBodyCopy.exists()).toBeFalsy();
        });
        test('screen reader only text when screenReaderOnlyText not provided', async () => {
            // Arrange
            const additionalButtonData = {
                displayAvailabilityIndicators: true,
                availabilityRatingCallback: () => Promise.resolve({})
            };
            const wrapper = mount(shopListButton, {
                propsData: {
                    value: 1234,
                    modelValue: 'value of modal',
                    groupName: 'name of group',
                    buttonLabel: 'label of button',
                    additionalButtonData
                },
                mixins: [inputButtonWrapperMixin]
            });
            await wrapper.vm.$nextTick();

            // Act
            const screenReaderOnlySpan = wrapper.find(screenReaderOnlySpanReference);

            // Assert
            expect(screenReaderOnlySpan.exists()).toBeFalsy();
        });
    });
    describe('computed', () => {
        describe('isLoaderDisplayed', () => {
            test('should return false when availabilityRating is empty string', async () => {
                // Arrange
                const availabilityRating = '';
                const additionalButtonData = {
                    displayAvailabilityIndicators: true,
                    availabilityRatingCallback: () => Promise.resolve(availabilityRating)
                };
                const wrapper = mount(shopListButton, {
                    propsData: {
                        value: 1234,
                        modelValue: 'value of modal',
                        groupName: 'name of group',
                        buttonLabel: 'label of button',
                        additionalButtonData
                    },
                    mixins: [inputButtonWrapperMixin]
                });
                await wrapper.vm.$nextTick();

                // Act
                const result = wrapper.vm.isLoaderDisplayed;

                // Assert
                expect(result).toBeFalsy();
            });
            test('should return false when availabilityRating is non-empty string', async () => {
                // Arrange
                const availabilityRating = 'not empty string';
                const additionalButtonData = {
                    displayAvailabilityIndicators: true,
                    availabilityRatingCallback: () => Promise.resolve(availabilityRating)
                };
                const wrapper = mount(shopListButton, {
                    propsData: {
                        value: 1234,
                        modelValue: 'value of modal',
                        groupName: 'name of group',
                        buttonLabel: 'label of button',
                        additionalButtonData
                    },
                    mixins: [inputButtonWrapperMixin]
                });
                await wrapper.vm.$nextTick();

                // Act
                const result = wrapper.vm.isLoaderDisplayed;

                // Assert
                expect(result).toBeFalsy();
            });
        });
        describe('availabilityRatingClass', () => {
            test.each([null, undefined])(
                'returns "gray" if availability rating null or undefined',
                async (availabilityRating) => {
                    // Arrange
                    const additionalButtonData = {
                        displayAvailabilityIndicators: true,
                        availabilityRatingCallback: () => Promise.resolve(availabilityRating)
                    };
                    const wrapper = mount(shopListButton, {
                        propsData: {
                            value: 1234,
                            modelValue: 'value of modal',
                            groupName: 'name of group',
                            buttonLabel: 'label of button',
                            additionalButtonData
                        },
                        mixins: [inputButtonWrapperMixin]
                    });
                    await wrapper.vm.$nextTick();
                    const expected = 'gray';

                    // Act
                    const result = wrapper.vm.availabilityRatingClass;

                    // Assert
                    expect(result).toBe(expected);
                }
            );
            test('returns green if availability rating is "high"', async () => {
                // Arrange
                const availabilityRating = 'high';
                const additionalButtonData = {
                    displayAvailabilityIndicators: true,
                    availabilityRatingCallback: () => Promise.resolve(availabilityRating)
                };
                const wrapper = mount(shopListButton, {
                    propsData: {
                        value: 1234,
                        modelValue: 'value of modal',
                        groupName: 'name of group',
                        buttonLabel: 'label of button',
                        additionalButtonData
                    },
                    mixins: [inputButtonWrapperMixin]
                });
                await wrapper.vm.$nextTick();
                const expected = 'green';

                // Act
                const result = wrapper.vm.availabilityRatingClass;

                // Assert
                expect(result).toBe(expected);
            });
            test.each(['', 'not high'])(
                'returns orange if availability not "high"',
                async (availabilityRating) => {
                    // Arrange
                    const additionalButtonData = {
                        displayAvailabilityIndicators: true,
                        availabilityRatingCallback: () => Promise.resolve(availabilityRating)
                    };
                    const wrapper = mount(shopListButton, {
                        propsData: {
                            value: 1234,
                            modelValue: 'value of modal',
                            groupName: 'name of group',
                            buttonLabel: 'label of button',
                            additionalButtonData
                        },
                        mixins: [inputButtonWrapperMixin]
                    });
                    await wrapper.vm.$nextTick();
                    const expected = 'orange';

                    // Act
                    const result = wrapper.vm.availabilityRatingClass;

                    // Assert
                    expect(result).toBe(expected);
                }
            );
        });
        describe('badgeText', () => {
            test.each([null, undefined])(
                'returns empty string if availability rating null or undefined',
                async (availabilityRating) => {
                    // Arrange
                    const additionalButtonData = {
                        displayAvailabilityIndicators: true,
                        availabilityRatingCallback: () => Promise.resolve(availabilityRating)
                    };
                    const wrapper = mount(shopListButton, {
                        propsData: {
                            value: 1234,
                            modelValue: 'value of modal',
                            groupName: 'name of group',
                            buttonLabel: 'label of button',
                            additionalButtonData
                        },
                        mixins: [inputButtonWrapperMixin]
                    });
                    await wrapper.vm.$nextTick();
                    const expected = '';

                    // Act
                    const result = wrapper.vm.badgeText;

                    // Assert
                    expect(result).toBe(expected);
                }
            );
            test('returns "Appts available" if availability rating "high"', async () => {
                // Arrange
                const availabilityRating = 'high';
                const additionalButtonData = {
                    displayAvailabilityIndicators: true,
                    availabilityRatingCallback: () => Promise.resolve(availabilityRating)
                };
                const wrapper = mount(shopListButton, {
                    propsData: {
                        value: 1234,
                        modelValue: 'value of modal',
                        groupName: 'name of group',
                        buttonLabel: 'label of button',
                        additionalButtonData
                    },
                    mixins: [inputButtonWrapperMixin]
                });
                await wrapper.vm.$nextTick();
                const expected = 'Appts available';

                // Act
                const result = wrapper.vm.badgeText;

                // Assert
                expect(result).toBe(expected);
            });
            test.each(['', 'not high'])(
                'returns "Appts low" if availability rating not null or "high"',
                async (availabilityRating) => {
                    // Arrange
                    const additionalButtonData = {
                        displayAvailabilityIndicators: true,
                        availabilityRatingCallback: () => Promise.resolve(availabilityRating)
                    };
                    const wrapper = mount(shopListButton, {
                        propsData: {
                            value: 1234,
                            modelValue: 'value of modal',
                            groupName: 'name of group',
                            buttonLabel: 'label of button',
                            additionalButtonData
                        },
                        mixins: [inputButtonWrapperMixin]
                    });
                    await wrapper.vm.$nextTick();
                    const expected = 'Appts low';

                    // Act
                    const result = wrapper.vm.badgeText;

                    // Assert
                    expect(result).toBe(expected);
                }
            );
        });
        describe('selectedValue', () => {
            test('"get" returns modalValue prop', async () => {
                // Arrange
                const additionalButtonData = {
                    displayAvailabilityIndicators: true,
                    availabilityRatingCallback: () => Promise.resolve({})
                };
                const modelValue = 'value of modal';
                const wrapper = mount(shopListButton, {
                    propsData: {
                        value: 1234,
                        modelValue,
                        groupName: 'name of group',
                        buttonLabel: 'label of button',
                        additionalButtonData
                    },
                    mixins: [inputButtonWrapperMixin]
                });
                await wrapper.vm.$nextTick();

                // Act
                const result = wrapper.vm.selectedValue;

                // Assert
                expect(result).toBe(modelValue);
            });
            test('"set" emits "update:modalValue" event', async () => {
                // Arrange
                const additionalButtonData = {
                    displayAvailabilityIndicators: true,
                    availabilityRatingCallback: () => Promise.resolve({})
                };
                const modelValue = 'value of modal';
                const wrapper = mount(shopListButton, {
                    propsData: {
                        value: 1234,
                        modelValue,
                        groupName: 'name of group',
                        buttonLabel: 'label of button',
                        additionalButtonData
                    },
                    mixins: [inputButtonWrapperMixin]
                });
                await wrapper.vm.$nextTick();
                const newValue = 'new value';

                // Act
                wrapper.vm.selectedValue = newValue;

                // Assert
                expect(wrapper.emitted()['update:modelValue'][0][0]).toEqual(newValue);
            });
        });
    });
    describe('before mount', () => {
        test('should call availabilityRatingCallback with expected data if displayAvailabilityIndicators true', async () => {
            // Arrange
            const availabilityRating = 'rating';
            const availabilityRatingCallback = jest.fn().mockImplementation(() => Promise.resolve(availabilityRating));
            const additionalButtonData = {
                displayAvailabilityIndicators: true,
                availabilityRatingCallback
            };
            const wrapper = mount(shopListButton, {
                propsData: {
                    value: 1234,
                    modelValue: 'value of modal',
                    groupName: 'name of group',
                    buttonLabel: 'label of button',
                    additionalButtonData
                },
                mixins: [inputButtonWrapperMixin]
            });
            await wrapper.vm.$nextTick();

            // Assert
            expect(availabilityRatingCallback).toBeCalledTimes(1);
        });
        test('should not call availabilityRatingCallback if displayAvailabilityIndicators false', async () => {
            // Arrange
            const availabilityRating = 'rating';
            const availabilityRatingCallback = jest.fn().mockImplementation(() => Promise.resolve(availabilityRating));
            const additionalButtonData = {
                displayAvailabilityIndicators: false,
                availabilityRatingCallback
            };
            const wrapper = mount(shopListButton, {
                propsData: {
                    value: 1234,
                    modelValue: 'value of modal',
                    groupName: 'name of group',
                    buttonLabel: 'label of button',
                    additionalButtonData
                },
                mixins: [inputButtonWrapperMixin]
            });
            await wrapper.vm.$nextTick();

            // Assert
            expect(availabilityRatingCallback).toBeCalledTimes(0);
        });
    });
});
