import React from 'react';
import { fireEvent, render, screen, within } from '@testing-library/react';
import { testComponent } from 'lib/test-utils/common';
import { PhoneInput } from '../phoneInput';
import {
    formatAsYouType,
    getPhoneCountryOptions,
    isTooLong,
    parsePhoneInput,
    stripFormatting,
} from '../utils';

describe('<PhoneInput>', () => {
    testComponent(PhoneInput);

    test('formats user input as a US national number by default', () => {
        const onChange = vi.fn();
        render(<PhoneInput onChange={onChange} />);
        const input = screen.getByTestId('PhoneInput-number') as HTMLInputElement;

        fireEvent.change(input, { target: { value: '2015550123' } });

        expect(input.value).toBe('(201) 555-0123');
        expect(onChange).toHaveBeenCalledWith(
            expect.objectContaining({
                country: 'US',
                value: '(201) 555-0123',
                e164: '+12015550123',
                isValid: true,
            })
        );
    });

    test('rejects non-digit characters via the formatter', () => {
        render(<PhoneInput />);
        const input = screen.getByTestId('PhoneInput-number') as HTMLInputElement;

        fireEvent.change(input, { target: { value: 'abc201def555ghi0123' } });

        expect(input.value).toBe('(201) 555-0123');
    });

    test('caps input at the country max digit count', () => {
        render(<PhoneInput />);
        const input = screen.getByTestId('PhoneInput-number') as HTMLInputElement;

        fireEvent.change(input, { target: { value: '2015550123' } });
        expect(input.value).toBe('(201) 555-0123');

        // 11th digit gets cut off (US max national is 10).
        fireEvent.change(input, { target: { value: '20155501234' } });
        expect(input.value).toBe('(201) 555-0123');
    });

    test('formats a 10-digit US number as national, not trunk-prefixed', () => {
        render(<PhoneInput />);
        const input = screen.getByTestId('PhoneInput-number') as HTMLInputElement;
        // Regression: AsYouType would emit `1 (234) 567-890` for `1234567890`; the manual `+1` formatter must emit `(123) 456-7890`.
        fireEvent.change(input, { target: { value: '1234567890' } });
        expect(input.value).toBe('(123) 456-7890');
    });

    test('+1 numbers cap by digit count, keeping the leading digit (no trunk strip on type)', () => {
        render(<PhoneInput />);
        const input = screen.getByTestId('PhoneInput-number') as HTMLInputElement;
        // 11 typed digits: the 11th is dropped, the leading 1 stays a real digit.
        fireEvent.change(input, { target: { value: '12345678900' } });
        expect(input.value).toBe('(123) 456-7890');
    });

    test('typing a leading + keeps it visible, then switches country once it resolves', () => {
        const onChange = vi.fn();
        render(<PhoneInput onChange={onChange} />);
        const input = screen.getByTestId('PhoneInput-number') as HTMLInputElement;

        // Partial international prefix stays visible instead of being swallowed.
        fireEvent.change(input, { target: { value: '+47' } });
        expect(input.value).toBe('+47');

        // Once enough digits resolve the country, it switches to NO and reformats.
        fireEvent.change(input, { target: { value: '+4791234567' } });
        const lastCall = onChange.mock.calls[onChange.mock.calls.length - 1][0];
        expect(lastCall.country).toBe('NO');
        expect(input.value.replace(/\D/g, '')).toBe('91234567');
    });

    test('detects the country from a pasted E.164 number', () => {
        const onChange = vi.fn();
        render(<PhoneInput onChange={onChange} />);
        const input = screen.getByTestId('PhoneInput-number') as HTMLInputElement;

        fireEvent.change(input, { target: { value: '+442083661177' } });

        expect(onChange).toHaveBeenCalledWith(
            expect.objectContaining({
                country: 'GB',
                e164: '+442083661177',
                isValid: true,
            })
        );
    });

    test('reformats an E.164 initial value on mount', () => {
        render(<PhoneInput value="+442083661177" />);
        const input = screen.getByTestId('PhoneInput-number') as HTMLInputElement;
        expect(input.value).not.toBe('+442083661177');
        expect(input.value).toMatch(/20.*8366.*1177/);
    });

    test('backspacing a formatter char actually shortens the value', () => {
        // Regression: typing `619` formats to `(619)`. Backspace removed the
        // `)` and AsYouType would re-emit it, freezing the input. The fix strips
        // one digit when only a formatting char was deleted.
        render(<PhoneInput />);
        const input = screen.getByTestId('PhoneInput-number') as HTMLInputElement;

        fireEvent.change(input, { target: { value: '619' } });
        expect(input.value).toBe('(619)');

        fireEvent.change(input, { target: { value: '(619' } });
        expect(input.value).toBe('(61');
    });

    test('seeds defaultValue/defaultCountry without locking the input', () => {
        // Uncontrolled mode with a seed: user must still be able to keep typing.
        render(<PhoneInput defaultCountry="ES" defaultValue="612 34" />);
        const input = screen.getByTestId('PhoneInput-number') as HTMLInputElement;
        expect(input.value.replace(/\D/g, '')).toBe('61234');

        fireEvent.change(input, { target: { value: '612345678' } });
        expect(input.value.replace(/\D/g, '')).toBe('612345678');
    });

    test('shows the controlled country in the trigger', () => {
        render(<PhoneInput country="GB" />);
        const trigger = screen.getByTestId('PhoneInput-country');
        expect(within(trigger).getByAltText('United Kingdom')).toBeInTheDocument();
    });

    test('reformats the visible value when the user changes the country', () => {
        const onChange = vi.fn();
        render(<PhoneInput onChange={onChange} />);
        const input = screen.getByTestId('PhoneInput-number') as HTMLInputElement;

        fireEvent.change(input, { target: { value: '2083661177' } });
        expect(input.value).toBe('(208) 366-1177');

        fireEvent.click(screen.getByTestId('SuiteSelectInput'));
        const filter = screen.getByPlaceholderText('Search value') as HTMLInputElement;
        fireEvent.change(filter, { target: { value: 'united kin' } });
        fireEvent.click(screen.getByText(/United Kingdom \(\+44\)/));

        const lastCall = onChange.mock.calls[onChange.mock.calls.length - 1][0];
        expect(lastCall.country).toBe('GB');
        expect(lastCall.value).toMatch(/2083661177|20.*8366.*1177/);
    });

    test('keeps all digits on country change and flags invalid via blur, not truncation', () => {
        // Switching to a country with a shorter max keeps the digits intact (no silent truncation); validity is surfaced via the blur message, not by dropping data.
        const onChange = vi.fn();
        render(<PhoneInput onChange={onChange} />);
        const input = screen.getByTestId('PhoneInput-number') as HTMLInputElement;
        fireEvent.change(input, { target: { value: '2015550123' } });

        fireEvent.click(screen.getByTestId('SuiteSelectInput'));
        const filter = screen.getByPlaceholderText('Search value') as HTMLInputElement;
        fireEvent.change(filter, { target: { value: 'norway' } });
        fireEvent.click(screen.getByText(/Norway \(\+47\)/));

        const lastCall = onChange.mock.calls[onChange.mock.calls.length - 1][0];
        expect(lastCall.country).toBe('NO');
        expect(lastCall.value.replace(/\D/g, '').length).toBe(10);
        expect(lastCall.isValid).toBe(false);
    });

    test('surfaces an error when controlled country and E.164 value disagree', () => {
        // Consumer wires `country` and `value` to separate controlled sources;
        // a pasted UK number under a US selection should not silently render a
        // US flag on a UK number — the discrepancy is surfaced as an error.
        render(<PhoneInput country="US" value="+442083661177" />);
        expect(screen.getByText("This number doesn't match the selected country.")).toBeVisible();
    });

    test('shows the error prop with aria-invalid on the input', () => {
        render(<PhoneInput error="Required" />);
        const input = screen.getByRole('textbox', { name: 'Phone Number' });
        expect(screen.getByText('Required')).toBeVisible();
        expect(input).toHaveAttribute('aria-invalid', 'true');
    });

    test('validates on blur when no explicit error is passed', () => {
        render(<PhoneInput />);
        const input = screen.getByTestId('PhoneInput-number') as HTMLInputElement;

        fireEvent.change(input, { target: { value: '123' } });
        fireEvent.blur(input);

        expect(screen.getByText('Please enter a valid phone number.')).toBeVisible();
    });

    test('disables the trigger and input when disabled', () => {
        render(<PhoneInput disabled />);
        expect(screen.getByTestId('SuiteSelectInput')).toHaveClass('disabled');
        expect(screen.getByTestId('PhoneInput-number')).toBeDisabled();
    });

    test('calls onFocus and onBlur on the underlying number input', () => {
        const onFocus = vi.fn();
        const onBlur = vi.fn();
        render(<PhoneInput onFocus={onFocus} onBlur={onBlur} />);
        const input = screen.getByTestId('PhoneInput-number');
        fireEvent.focus(input);
        expect(onFocus).toHaveBeenCalled();
        fireEvent.blur(input);
        expect(onBlur).toHaveBeenCalled();
    });
});

describe('PhoneInput utils', () => {
    test('getPhoneCountryOptions includes calling codes and is sorted by name', () => {
        const options = getPhoneCountryOptions();
        expect(options.length).toBeGreaterThan(200);
        expect(options.find((o) => o.code === 'US')?.callingCode).toBe('1');
        const names = options.map((o) => o.name);
        expect(names).toEqual([...names].sort((a, b) => a.localeCompare(b)));
    });

    test('formatAsYouType produces national-only formatting as the user types', () => {
        expect(formatAsYouType('2015550', 'US')).toBe('(201) 555-0');
    });

    test('formatAsYouType shows the opening paren early for +1 countries', () => {
        expect(formatAsYouType('2', 'US')).toBe('(2');
        expect(formatAsYouType('23', 'US')).toBe('(23');
        expect(formatAsYouType('234', 'US')).toBe('(234)');
        expect(formatAsYouType('6', 'ES')).toBe('6');
    });

    test('formatAsYouType groups trunk-prefix countries via the parsed national fallback', () => {
        // GB national format needs the trunk 0, which AsYouType only applies once
        // the number is complete and valid — fall back to the parsed national form.
        expect(formatAsYouType('2083661177', 'GB')).toBe('020 8366 1177');
    });

    test('stripFormatting keeps digits and only a single leading plus', () => {
        expect(stripFormatting('+1 (201) 555-0123')).toBe('+12015550123');
        expect(stripFormatting('abc')).toBe('');
        expect(stripFormatting('')).toBe('');
        // a `+` that is not at position 0 is dropped
        expect(stripFormatting('12+34')).toBe('1234');
        expect(stripFormatting('+12+34')).toBe('+1234');
    });

    test('isTooLong is false for empty input (so backspace-to-empty works)', () => {
        expect(isTooLong('', 'ES')).toBe(false);
        expect(isTooLong('', 'US')).toBe(false);
    });

    test('isTooLong blocks only over-max input, per country', () => {
        // US: 10 valid, 11 over max.
        expect(isTooLong('2015550123', 'US')).toBe(false);
        expect(isTooLong('20155501234', 'US')).toBe(true);
        // ES: single valid length 9, 10+ over max.
        expect(isTooLong('612345678', 'ES')).toBe(false);
        expect(isTooLong('6123456789', 'ES')).toBe(true);
    });

    test('isTooLong does NOT block lengths inside a country length gap', () => {
        // Canada allows a 7-digit local form and a 10-digit form; 8–9 sit in
        // the gap (INVALID_LENGTH) and must stay typeable on the way to 10.
        expect(isTooLong('61355019', 'CA')).toBe(false); // 8, in the gap
        expect(isTooLong('613550199', 'CA')).toBe(false); // 9, in the gap
        expect(isTooLong('6135501999', 'CA')).toBe(false); // 10, valid
        expect(isTooLong('61355019999', 'CA')).toBe(true); // 11, over max
        // Norway: 8-digit numbers must be reachable through the shorter gap.
        expect(isTooLong('21234567', 'NO')).toBe(false); // 8, valid
    });

    test('formatAsYouType keeps national digits that collide with the calling code', () => {
        // Kazakhstan's calling code is 7 and its national numbers can start
        // with 7 — the leading digit must not be eaten as a calling-code prefix.
        expect(formatAsYouType('7710009998', 'KZ').replace(/\D/g, '')).toBe('7710009998');
    });

    test('isTooLong strips the calling code only from explicit E.164 input', () => {
        expect(isTooLong('+12015550123', 'US')).toBe(false); // E.164, 10 national
        expect(isTooLong('+120155501234', 'US')).toBe(true); // E.164, 11 national
    });

    test('parsePhoneInput returns isValid, e164, and a national-format display value', () => {
        const parsed = parsePhoneInput('+442083661177', 'GB');
        expect(parsed.isValid).toBe(true);
        expect(parsed.e164).toBe('+442083661177');
        expect(parsed.value.startsWith('+')).toBe(false);
        expect(parsed.value.replace(/\D/g, '')).toBe('02083661177');
    });

    test('parsePhoneInput marks empty, garbage, and short inputs as invalid with no e164', () => {
        for (const raw of ['', 'abc', '123']) {
            const parsed = parsePhoneInput(raw, 'US');
            expect(parsed.isValid).toBe(false);
            expect(parsed.e164).toBeUndefined();
        }
    });
});
