import { fireEvent, render, screen } from '@testing-library/react';
import { CountriesSelect } from '@/components/CountriesSelect';
import { ControlledPhoneNumberInput } from '@/components/PhoneNumberInput';
import { useBeforeUnloadEvent } from '@/hooks/useBeforeUnloadEvent';
import { useScrollTop } from '@/hooks/useScrollTop';
import { useScopedI18n } from '@/i18n/client';
import { ProfileForm } from './ProfileForm';
import { useProfileFormState } from './useProfileForm/useProfileFormState';
import type { Mock } from 'vitest';
import type { ProfileFormState } from './useProfileForm/types';

vi.mock('next/navigation', () => ({
    useParams: vi.fn().mockReturnValue({ token: 'token' }),
}));

vi.mock('@/i18n/client', () => ({
    useScopedI18n: vi.fn().mockReturnValue((term: string) => term),
    useCurrentLocale: vi.fn().mockReturnValue('en'),
}));

vi.mock('@/hooks/useBeforeUnloadEvent', () => ({
    useBeforeUnloadEvent: vi.fn(),
}));

vi.mock('@/hooks/useScrollTop', () => ({
    useScrollTop: vi.fn(),
}));

const controllerProps = { field: {}, fieldState: {} };

vi.mock('react-hook-form', () => ({
    Controller: vi.fn(({ render }) => render(controllerProps)),
}));

vi.mock('@/components/CountriesSelect', () => ({
    CountriesSelect: vi.fn(() => <div>CountriesSelect</div>),
}));

vi.mock('@/components/PhoneNumberInput', () => ({
    ControlledPhoneNumberInput: vi.fn(() => (
        <div>ControlledPhoneNumberInput</div>
    )),
}));

const formControlRegisterMock = vi.fn((name: string) => ({
    'data-registered': name,
}));

const errors = {
    email: { message: 'email error' },
    phoneNumber: { message: 'phoneNumber error' },
    firstName: { message: 'firstName error' },
    lastName: { message: 'lastName error' },
    address: { message: 'address error' },
    city: { message: 'city error' },
    zipCode: { message: 'zipCode error' },
    dateOfBirth: { message: 'dateOfBirth error' },
    birthday: { message: 'birthday error' },
};

const profileFormStateResult = {
    control: {},
    register: formControlRegisterMock,
    formState: { errors },
    isDirty: false, // custom property
    handleProfileSubmit: vi.fn(), // custom property
};

const updateProfileState = { result: null, error: null, pending: false };

const useProfileFormStateResult = [profileFormStateResult, updateProfileState];

vi.mock('./useProfileForm/useProfileFormState', () => ({
    useProfileFormState: vi.fn(() => useProfileFormStateResult),
}));

describe('<ProfileForm>', () => {
    afterEach(() => {
        vi.clearAllMocks();
    });

    describe('scoped i18n', () => {
        it('should recieve correct arguments', () => {
            render(<ProfileForm />);

            expect(useScopedI18n).toHaveBeenCalledWith(
                'pages.profile.profileTab'
            );
        });
    });

    describe('useProfileFormState', () => {
        it('should be called with correct arguments', () => {
            const DEFAULT_VALUES = {
                email: 'test@email.com',
                phoneNumber: '+37255555555',
                firstName: 'Nicholas',
                lastName: 'Cage',
                dateOfBirth: '7/10/2000',
                birthday: '01/04',
                gender: 'NON_BINARY_OTHER',
                countryCode: 'EE',
                city: 'Tallinn',
                address: 'Laeva tn',
                zipCode: '10000',
            } satisfies ProfileFormState;

            render(<ProfileForm defaultValues={DEFAULT_VALUES} />);

            expect(useProfileFormState).toHaveBeenCalledWith('token', {
                defaultValues: DEFAULT_VALUES,
            });
        });
    });

    describe('useBeforeUnloadEvent', () => {
        it('should be called with correct arguments', () => {
            render(<ProfileForm />);

            expect(useBeforeUnloadEvent).toHaveBeenCalledWith(
                profileFormStateResult.isDirty
            );
        });
    });

    describe('useScrollTop', () => {
        it('should be called with correct arguments', () => {
            render(<ProfileForm />);

            const isError =
                Boolean(updateProfileState.error) ||
                // @ts-expect-error partial type
                Boolean(updateProfileState.result?.error);
            const isSuccess = Boolean(
                // @ts-expect-error partial type
                updateProfileState.result?.data
            );

            expect(useScrollTop).toHaveBeenCalledWith(isSuccess || isError);
        });
    });

    describe('form element', () => {
        it('should call "onSubmit" when submitted', () => {
            const { container } = render(<ProfileForm />);

            const form = container.querySelector('form');
            if (form) {
                fireEvent.submit(form);
            }

            expect(
                profileFormStateResult.handleProfileSubmit
            ).toHaveBeenCalled();
        });
    });

    describe('contacts section', () => {
        it('should render title', () => {
            render(<ProfileForm />);

            expect(screen.getByText('contactsSection.title')).toBeVisible();
        });

        describe('email field', () => {
            it('label should be visible', () => {
                render(<ProfileForm />);

                expect(screen.getByText(/email.label/)).toBeVisible();
            });

            it('input should be visible', () => {
                render(<ProfileForm />);

                expect(
                    screen.getByRole('textbox', {
                        name: /email.label/,
                    })
                ).toBeVisible();
            });

            it('should be registered', () => {
                render(<ProfileForm />);

                const element = screen.getByRole('textbox', {
                    name: /email.label/,
                });

                expect(element).toHaveAttribute('data-registered', 'email');
            });

            it('should show error message', () => {
                render(<ProfileForm />);

                expect(screen.getByText('email error')).toBeVisible();
            });
        });

        describe('phone number field', () => {
            it('label should be visible', () => {
                render(<ProfileForm />);

                expect(screen.getByText('phoneNumber.label')).toBeVisible();
            });

            describe('<ControlledPhoneNumberInput>', () => {
                it('should be called with correct props', () => {
                    render(<ProfileForm />);

                    expect(ControlledPhoneNumberInput).toHaveBeenCalledWith(
                        {
                            errorClassName: 'border-red! outline-red!',
                            field: controllerProps.field,
                            isError: false,
                            internationalCodeLabel:
                                'phoneNumber.countryPlaceholder',
                            locale: 'en',
                            numberInputProps: {
                                id: 'phoneNumber',
                            },
                        },
                        undefined
                    );
                });
            });

            it('should show error message', () => {
                render(<ProfileForm />);

                expect(screen.getByText('phoneNumber error')).toBeVisible();
            });
        });
    });

    describe('about section', () => {
        it('should render title', () => {
            render(<ProfileForm />);

            expect(screen.getByText('aboutSection.title')).toBeVisible();
        });

        describe('firstName field', () => {
            it('label should be visible', () => {
                render(<ProfileForm />);

                expect(screen.getByText('firstName.label')).toBeVisible();
            });

            it('input should be visible', () => {
                render(<ProfileForm />);

                expect(
                    screen.getByRole('textbox', {
                        name: 'firstName.label',
                    })
                ).toBeVisible();
            });

            it('should be registered', () => {
                render(<ProfileForm />);

                const element = screen.getByRole('textbox', {
                    name: 'firstName.label',
                });

                expect(element).toHaveAttribute('data-registered', 'firstName');
            });

            it('should show field error', () => {
                render(<ProfileForm />);

                expect(
                    screen.getByText(errors.firstName.message)
                ).toBeVisible();
            });
        });

        describe('lastName field', () => {
            it('label should be visible', () => {
                render(<ProfileForm />);

                expect(screen.getByText('lastName.label')).toBeVisible();
            });

            it('input should be visible', () => {
                render(<ProfileForm />);

                expect(
                    screen.getByRole('textbox', {
                        name: 'lastName.label',
                    })
                ).toBeVisible();
            });

            it('should be registered', () => {
                render(<ProfileForm />);

                const element = screen.getByRole('textbox', {
                    name: 'lastName.label',
                });

                expect(element).toHaveAttribute('data-registered', 'lastName');
            });

            it('should show field error', () => {
                render(<ProfileForm />);

                expect(screen.getByText(errors.lastName.message)).toBeVisible();
            });
        });

        describe('when "isDateOfBirthAllowed" is "true"', () => {
            describe('dateOfBirth field', () => {
                it('label should be visible', () => {
                    render(<ProfileForm isDateOfBirthAllowed />);

                    expect(screen.getByText('dateOfBirth.label')).toBeVisible();
                });

                it('input should be visible', () => {
                    render(<ProfileForm isDateOfBirthAllowed />);

                    expect(
                        screen.getByLabelText('dateOfBirth.label')
                    ).toBeVisible();
                });

                it('should use "today" as max value', () => {
                    render(<ProfileForm isDateOfBirthAllowed />);

                    expect(
                        screen.getByLabelText('dateOfBirth.label')
                    ).toHaveAttribute(
                        'max',
                        new Date().toISOString().split('T')[0]
                    );
                });

                it('should be registered', () => {
                    render(<ProfileForm isDateOfBirthAllowed />);

                    const element = screen.getByLabelText('dateOfBirth.label');

                    expect(element).toHaveAttribute(
                        'data-registered',
                        'dateOfBirth'
                    );
                });

                it('should show error message', () => {
                    render(<ProfileForm isDateOfBirthAllowed />);

                    expect(screen.getByText('dateOfBirth error')).toBeVisible();
                });
            });

            describe('birthday field', () => {
                it('label should not be in the document', () => {
                    render(<ProfileForm isDateOfBirthAllowed />);

                    expect(
                        screen.queryByText('birthday.label')
                    ).not.toBeInTheDocument();
                });

                it('input should not be in the document', () => {
                    render(<ProfileForm isDateOfBirthAllowed />);

                    expect(
                        screen.queryByLabelText('birthday.label')
                    ).not.toBeInTheDocument();
                });
            });
        });

        describe('when "isDateOfBirthAllowed" is "false"', () => {
            describe('dateOfBirth field', () => {
                it('label should not be in the document', () => {
                    render(<ProfileForm isDateOfBirthAllowed={false} />);

                    expect(
                        screen.queryByText('dateOfBirth.label')
                    ).not.toBeInTheDocument();
                });

                it('input should not be in the document', () => {
                    render(<ProfileForm isDateOfBirthAllowed={false} />);

                    expect(
                        screen.queryByLabelText('dateOfBirth.label')
                    ).not.toBeInTheDocument();
                });
            });

            describe('birthday field', () => {
                it('label should be visible', () => {
                    render(<ProfileForm isDateOfBirthAllowed={false} />);

                    expect(screen.getByText('birthday.label')).toBeVisible();
                });

                it('input should be visible', () => {
                    render(<ProfileForm isDateOfBirthAllowed={false} />);

                    expect(
                        screen.getByPlaceholderText('birthday.placeholder')
                    ).toBeVisible();
                });

                it('should be registered', () => {
                    render(<ProfileForm isDateOfBirthAllowed={false} />);

                    const element = screen.getByPlaceholderText(
                        'birthday.placeholder'
                    );

                    expect(element).toHaveAttribute(
                        'data-registered',
                        'birthday'
                    );
                });

                it('should show error message', () => {
                    render(<ProfileForm isDateOfBirthAllowed={false} />);

                    expect(screen.getByText('birthday error')).toBeVisible();
                });
            });
        });

        describe('gender field', () => {
            it('label should be visible', () => {
                render(<ProfileForm />);

                expect(screen.getByText('gender.label')).toBeVisible();
            });

            it('input should be visible', () => {
                render(<ProfileForm />);

                expect(
                    screen.getByRole('combobox', {
                        name: 'gender.label',
                    })
                ).toBeVisible();
            });

            it('should have all options', () => {
                render(<ProfileForm />);

                expect(
                    screen.getByText('gender.placeholder')
                ).toBeInTheDocument();

                [
                    'gender.options.MALE',
                    'gender.options.FEMALE',
                    'gender.options.NON_BINARY_OTHER',
                    'gender.options.PREFER_NOT_TO_ANSWER',
                ].forEach(name => {
                    expect(screen.getByRole('option', { name })).toBeVisible();
                });
            });

            it('should be registered', () => {
                render(<ProfileForm />);

                const element = screen.getByRole('combobox', {
                    name: 'gender.label',
                });

                expect(element).toHaveAttribute('data-registered', 'gender');
            });
        });
    });

    describe('location section', () => {
        it('should render title', () => {
            render(<ProfileForm />);

            expect(screen.getByText('locationSection.title')).toBeVisible();
        });

        describe('countryCode field', () => {
            it('label should be visible', () => {
                render(<ProfileForm />);

                expect(screen.getByText('countryCode.label')).toBeVisible();
            });

            describe('<CountriesSelect>', () => {
                it('should be called with correct props', () => {
                    render(<ProfileForm />);

                    expect(CountriesSelect).toHaveBeenCalledWith(
                        {
                            id: 'countryCode',
                            lang: 'en',
                            placeholder: 'countryCode.placeholder',
                            'data-registered': 'countryCode',
                        },
                        undefined
                    );
                });
            });
        });

        describe('address field', () => {
            it('label should be visible', () => {
                render(<ProfileForm />);

                expect(screen.getByText('address.label')).toBeVisible();
            });

            it('input should be visible', () => {
                render(<ProfileForm />);

                expect(
                    screen.getByRole('textbox', {
                        name: 'address.label',
                    })
                ).toBeVisible();
            });

            it('should be registered', () => {
                render(<ProfileForm />);

                const element = screen.getByRole('textbox', {
                    name: 'address.label',
                });

                expect(element).toHaveAttribute('data-registered', 'address');
            });

            it('should show field error', () => {
                render(<ProfileForm />);

                expect(screen.getByText(errors.address.message)).toBeVisible();
            });
        });

        describe('city field', () => {
            it('label should be visible', () => {
                render(<ProfileForm />);

                expect(screen.getByText('city.label')).toBeVisible();
            });

            it('input should be visible', () => {
                render(<ProfileForm />);

                expect(
                    screen.getByRole('textbox', {
                        name: 'city.label',
                    })
                ).toBeVisible();
            });

            it('should be registered', () => {
                render(<ProfileForm />);

                const element = screen.getByRole('textbox', {
                    name: 'city.label',
                });

                expect(element).toHaveAttribute('data-registered', 'city');
            });

            it('should show field error', () => {
                render(<ProfileForm />);

                expect(screen.getByText(errors.city.message)).toBeVisible();
            });
        });

        describe('zipCode field', () => {
            it('label should be visible', () => {
                render(<ProfileForm />);

                expect(screen.getByText('zipCode.label')).toBeVisible();
            });

            it('input should be visible', () => {
                render(<ProfileForm />);

                expect(
                    screen.getByRole('textbox', {
                        name: 'zipCode.label',
                    })
                ).toBeVisible();
            });

            it('should be registered', () => {
                render(<ProfileForm />);

                const element = screen.getByRole('textbox', {
                    name: 'zipCode.label',
                });

                expect(element).toHaveAttribute('data-registered', 'zipCode');
            });

            it('should show field error', () => {
                render(<ProfileForm />);

                expect(screen.getByText(errors.zipCode.message)).toBeVisible();
            });
        });
    });

    describe('save button', () => {
        it('should be visible and enabled', () => {
            render(<ProfileForm />);

            expect(screen.getByText('saveButton')).toBeVisible();
            expect(screen.getByText('saveButton')).toBeEnabled();
        });
    });

    describe('loading overlay', () => {
        it('should not be visibile', () => {
            render(<ProfileForm />);

            expect(
                screen.queryByText('loadingOverlay.altText')
            ).not.toBeInTheDocument();
        });

        describe('when profile data is getting saved', () => {
            it('should be visible', () => {
                (useProfileFormState as Mock).mockReturnValueOnce([
                    profileFormStateResult,
                    { ...updateProfileState, pending: true },
                ]);

                render(<ProfileForm />);

                expect(
                    screen.getByText('loadingOverlay.altText')
                ).toBeVisible();
            });
        });
    });

    describe('success alert', () => {
        it('should not be visible', () => {
            render(<ProfileForm />);

            expect(screen.queryByText('savedMessage')).not.toBeInTheDocument();
        });

        describe('when subscriptions were updated', () => {
            it('should be visible', () => {
                (useProfileFormState as Mock).mockReturnValueOnce([
                    profileFormStateResult,
                    { ...updateProfileState, result: { data: {} } },
                ]);

                render(<ProfileForm />);

                expect(screen.getByText('savedMessage')).toBeVisible();
            });
        });
    });

    describe('error alert', () => {
        it('should not be visible', () => {
            render(<ProfileForm />);

            expect(screen.queryByText('errorMessage')).not.toBeInTheDocument();
        });

        describe('when updating profile resolved with error', () => {
            it('should be visible', () => {
                (useProfileFormState as Mock).mockReturnValueOnce([
                    profileFormStateResult,
                    {
                        ...updateProfileState,
                        result: {
                            error: {},
                        },
                    },
                ]);

                render(<ProfileForm />);

                expect(screen.getByText('errorMessage')).toBeVisible();
            });
        });

        describe('when updating profile rejected with error', () => {
            it('should be visible', () => {
                (useProfileFormState as Mock).mockReturnValueOnce([
                    profileFormStateResult,
                    { ...updateProfileState, error: true },
                ]);

                render(<ProfileForm />);

                expect(screen.getByText('errorMessage')).toBeVisible();
            });
        });
    });
});
