import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { CustomInput, CustomInputContext } from './CustomInput';
import type { ControllerRenderProps } from 'react-hook-form';

describe('<CustomInput>', () => {
    const mockFieldProps: ControllerRenderProps<
        Record<string, string>,
        string
    > = {
        onChange: vi.fn(),
        onBlur: vi.fn(),
        value: '',
        name: 'phone',
        ref: vi.fn(),
    };

    const user = userEvent.setup();

    afterEach(() => {
        vi.clearAllMocks();
    });

    it('should throw error when used outside CustomInputContext', () => {
        const consoleErrorSpy = vi
            .spyOn(console, 'error')
            .mockImplementation(() => {});

        expect(() => {
            render(<CustomInput />);
        }).toThrow(
            '<CustomInput> must be used within a <ControlledPhoneNumberInput>'
        );

        consoleErrorSpy.mockRestore();
    });

    it('should render input with provided props', () => {
        render(
            <CustomInputContext.Provider value={mockFieldProps}>
                <CustomInput
                    data-testid="custom-input"
                    placeholder="Enter phone"
                    className="phone-input"
                />
            </CustomInputContext.Provider>
        );

        const input = screen.getByTestId('custom-input');
        expect(input).toBeInTheDocument();
        expect(input).toHaveAttribute('placeholder', 'Enter phone');
        expect(input).toHaveClass('phone-input');
    });

    it('should use props.value when fieldProps.value does not include asterisk', () => {
        render(
            <CustomInputContext.Provider value={mockFieldProps}>
                <CustomInput data-testid="custom-input" value="12345" />
            </CustomInputContext.Provider>
        );

        const input = screen.getByTestId('custom-input') as HTMLInputElement;
        expect(input.value).toBe('12345');
    });

    it('should use fieldProps.value when it includes asterisk', () => {
        const fieldPropsWithAsterisk = {
            ...mockFieldProps,
            value: '+1***********',
        };

        render(
            <CustomInputContext.Provider value={fieldPropsWithAsterisk}>
                <CustomInput data-testid="custom-input" value="12345" />
            </CustomInputContext.Provider>
        );

        const input = screen.getByTestId('custom-input') as HTMLInputElement;
        expect(input.value).toBe('+1***********');
    });

    it('should call fieldProps.onChange when input value includes asterisk', async () => {
        const fieldPropsWithAsterisk = {
            ...mockFieldProps,
            value: '',
        };

        render(
            <CustomInputContext.Provider value={fieldPropsWithAsterisk}>
                <CustomInput data-testid="custom-input" />
            </CustomInputContext.Provider>
        );

        const input = screen.getByTestId('custom-input');
        await user.type(input, '+1***');

        expect(fieldPropsWithAsterisk.onChange).toHaveBeenCalled();
    });

    it('should call props.onChange when input value does not include asterisk', async () => {
        const onChangeMock = vi.fn();

        render(
            <CustomInputContext.Provider value={mockFieldProps}>
                <CustomInput
                    data-testid="custom-input"
                    onChange={onChangeMock}
                />
            </CustomInputContext.Provider>
        );

        const input = screen.getByTestId('custom-input');
        await user.type(input, '12345');

        expect(onChangeMock).toHaveBeenCalled();
        expect(mockFieldProps.onChange).not.toHaveBeenCalled();
    });

    it('should call props.onKeyDown when fieldProps.value does not include asterisk', async () => {
        const onKeyDownMock = vi.fn();

        render(
            <CustomInputContext.Provider value={mockFieldProps}>
                <CustomInput
                    data-testid="custom-input"
                    onKeyDown={onKeyDownMock}
                />
            </CustomInputContext.Provider>
        );

        const input = screen.getByTestId('custom-input');
        await user.type(input, 'a');

        expect(onKeyDownMock).toHaveBeenCalled();
    });

    it('should not call props.onKeyDown when fieldProps.value includes asterisk', async () => {
        const onKeyDownMock = vi.fn();
        const fieldPropsWithAsterisk = {
            ...mockFieldProps,
            value: '+1***********',
        };

        render(
            <CustomInputContext.Provider value={fieldPropsWithAsterisk}>
                <CustomInput
                    data-testid="custom-input"
                    onKeyDown={onKeyDownMock}
                />
            </CustomInputContext.Provider>
        );

        const input = screen.getByTestId('custom-input');
        await user.type(input, 'a');

        expect(onKeyDownMock).not.toHaveBeenCalled();
    });

    it('should handle ref forwarding', () => {
        const ref = vi.fn();

        render(
            <CustomInputContext.Provider value={mockFieldProps}>
                <CustomInput ref={ref} data-testid="custom-input" />
            </CustomInputContext.Provider>
        );

        expect(ref).toHaveBeenCalledWith(expect.any(HTMLInputElement));
    });

    it('should spread all input attributes correctly', () => {
        render(
            <CustomInputContext.Provider value={mockFieldProps}>
                <CustomInput
                    data-testid="custom-input"
                    type="tel"
                    disabled
                    autoComplete="tel"
                    aria-label="Phone number"
                />
            </CustomInputContext.Provider>
        );

        const input = screen.getByTestId('custom-input');
        expect(input).toHaveAttribute('type', 'tel');
        expect(input).toBeDisabled();
        expect(input).toHaveAttribute('autocomplete', 'tel');
        expect(input).toHaveAttribute('aria-label', 'Phone number');
    });
});
