import { useContext } from 'react';
import { render } from '@testing-library/react';
import { ControlledPhoneNumberInput } from './ControlledPhoneNumberInput';
import { CustomInput, CustomInputContext } from './CustomInput';
import { PhoneNumberInput } from './PhoneNumberInput';
import type { ControllerRenderProps } from 'react-hook-form';
import type { ExpectedAny } from '@/types';

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

describe('<ControlledPhoneNumberInput>', () => {
    const mockField: ControllerRenderProps<Record<string, string>, string> = {
        onChange: vi.fn(),
        onBlur: vi.fn(),
        value: '+1234567890',
        name: 'phoneNumber',
        ref: vi.fn(),
    };

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

    it('should call <PhoneNumberInput> with correct props', () => {
        render(
            <ControlledPhoneNumberInput
                field={mockField}
                countryLabel="Country"
                phoneLabel="Phone"
                placeholder="Enter phone number"
            />
        );

        expect(PhoneNumberInput).toHaveBeenCalledWith(
            expect.objectContaining({
                innerRef: mockField.ref,
                value: '+1234567890',
                onChange: expect.any(Function),
                onBlur: mockField.onBlur,
                inputComponent: CustomInput,
                countryLabel: 'Country',
                phoneLabel: 'Phone',
                placeholder: 'Enter phone number',
            }),
            undefined
        );
    });

    it('should pass isError prop to PhoneNumberInput', () => {
        render(<ControlledPhoneNumberInput field={mockField} isError />);

        expect(PhoneNumberInput).toHaveBeenCalledWith(
            expect.objectContaining({
                isError: true,
            }),
            undefined
        );
    });

    it('should pass undefined as value when field value includes asterisk', () => {
        const fieldWithAsterisk = {
            ...mockField,
            value: '+1***********',
        };

        render(<ControlledPhoneNumberInput field={fieldWithAsterisk} />);

        expect(PhoneNumberInput).toHaveBeenCalledWith(
            expect.objectContaining({
                value: undefined,
            }),
            undefined
        );
    });

    it('should pass field value as value when it does not include asterisk', () => {
        render(<ControlledPhoneNumberInput field={mockField} />);

        expect(PhoneNumberInput).toHaveBeenCalledWith(
            expect.objectContaining({
                value: '+1234567890',
            }),
            undefined
        );
    });

    it('should call field.onChange with null when safeHandleChange receives undefined', () => {
        render(<ControlledPhoneNumberInput field={mockField} />);

        const { onChange } = vi.mocked(PhoneNumberInput).mock.calls[0][0];
        onChange(undefined);

        expect(mockField.onChange).toHaveBeenCalledWith(null);
    });

    it('should call field.onChange with value when safeHandleChange receives a string', () => {
        render(<ControlledPhoneNumberInput field={mockField} />);

        const { onChange } = vi.mocked(PhoneNumberInput).mock.calls[0][0];
        onChange('+1987654321' as ExpectedAny);

        expect(mockField.onChange).toHaveBeenCalledWith('+1987654321');
    });

    it('should spread additional props to PhoneNumberInput', () => {
        render(
            <ControlledPhoneNumberInput
                field={mockField}
                countryLabel="Country"
                phoneLabel="Phone"
                extLabel="Extension"
                internationalCodeLabel="International"
                placeholder="Enter phone"
                errorClassName="error-class"
            />
        );

        expect(PhoneNumberInput).toHaveBeenCalledWith(
            expect.objectContaining({
                countryLabel: 'Country',
                phoneLabel: 'Phone',
                extLabel: 'Extension',
                internationalCodeLabel: 'International',
                placeholder: 'Enter phone',
                errorClassName: 'error-class',
            }),
            undefined
        );
    });

    it('should exclude onChange, onBlur, value, ref from fieldProps spread', () => {
        render(<ControlledPhoneNumberInput field={mockField} />);

        const calledProps = vi.mocked(PhoneNumberInput).mock.calls[0][0];

        expect(calledProps).toHaveProperty('name', 'phoneNumber');
        expect(calledProps).not.toHaveProperty('ref');
    });

    it('should provide field to <CustomInputContext>', () => {
        const TestComponent = () => {
            const contextValue = useContext(CustomInputContext);
            return <div data-testid="context-value">{contextValue?.name}</div>;
        };

        vi.mocked(PhoneNumberInput).mockImplementation(() => {
            return <TestComponent />;
        });

        const { getByTestId } = render(
            <ControlledPhoneNumberInput field={mockField} />
        );

        const contextElement = getByTestId('context-value');
        expect(contextElement.textContent).toBe('phoneNumber');
    });

    it('should handle empty string value', () => {
        const fieldWithEmptyValue = {
            ...mockField,
            value: '',
        };

        render(<ControlledPhoneNumberInput field={fieldWithEmptyValue} />);

        expect(PhoneNumberInput).toHaveBeenCalledWith(
            expect.objectContaining({
                value: '',
            }),
            undefined
        );
    });

    it('should handle undefined value', () => {
        const fieldWithUndefinedValue = {
            ...mockField,
            value: undefined,
        };

        render(<ControlledPhoneNumberInput field={fieldWithUndefinedValue} />);

        expect(PhoneNumberInput).toHaveBeenCalledWith(
            expect.objectContaining({
                value: undefined,
            }),
            undefined
        );
    });

    it('should handle value with partial asterisk', () => {
        const fieldWithPartialAsterisk = {
            ...mockField,
            value: '+1234*67890',
        };

        render(<ControlledPhoneNumberInput field={fieldWithPartialAsterisk} />);

        expect(PhoneNumberInput).toHaveBeenCalledWith(
            expect.objectContaining({
                value: undefined,
            }),
            undefined
        );
    });
});
