import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
import { mount, VueWrapper } from '@vue/test-utils';
import { nextTick } from 'vue';
import MPhoneNumber from './MPhoneNumber.vue';
import { isValidPhoneNumber, type CountryCode } from 'libphonenumber-js';

vi.mock('libphonenumber-js', () => ({
  default: vi.fn(),
  isValidPhoneNumber: vi.fn(),
  getCountries: vi.fn(() => ['FR', 'US', 'PT', 'DE', 'ES']),
  getCountryCallingCode: vi.fn((country) => {
    const codes = { FR: '33', US: '1', PT: '351', DE: '49', ES: '34' };
    return codes[country as keyof typeof codes] || '33';
  }),
  getExampleNumber: vi.fn(() => ({
    formatNational: () => '01 23 45 67 89',
  })),
  parsePhoneNumberFromString: vi.fn((value) => {
    if (!value) return null;
    return {
      formatNational: () => {
        if (value === '+33123456789') return '01 23 45 67 89';
        return value;
      },
      number: value.startsWith('+') ? value : `+${value}`,
    };
  }),
}));

vi.mock('@mozaic-ds/icons-vue', async () => {
  const actual = await vi.importActual('@mozaic-ds/icons-vue');
  return {
    ...actual,
    ChevronDown20: {
      name: 'ChevronDown20',
      template: '<svg data-testid="chevron-icon" aria-hidden="true"></svg>',
    },
  };
});

describe('MPhoneNumber', () => {
  let wrapper: VueWrapper<InstanceType<typeof MPhoneNumber>>;

  const defaultProps = {
    id: 'phone-input',
    defaultCountry: 'FR' as const,
    modelValue: '',
  };

  beforeEach(() => {
    wrapper = mount(MPhoneNumber, {
      props: defaultProps,
    });
  });

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

  describe('Rendering', () => {
    it('should render the component', () => {
      expect(wrapper.find('.mc-phone-number-input').exists()).toBe(true);
    });
  });

  describe('Country Selection', () => {
    it('should render country selector and flag by default', () => {
      expect(
        wrapper
          .find('.mc-phone-number-input__select .mc-select__control')
          .exists(),
      ).toBe(true);
      expect(wrapper.find('.mc-phone-number-input__flag').exists()).toBe(true);
    });

    it('should hide country selector when flag prop is false', () => {
      wrapper = mount(MPhoneNumber, {
        props: { ...defaultProps, flag: false },
      });

      expect(
        wrapper.find('.mc-phone-number-input__select-wrapper').classes(),
      ).toContain('mc-phone-number-input__select-wrapper--hidden');
    });

    it('should render all country options', () => {
      const options = wrapper.findAll('option');
      expect(options.length).toBeGreaterThan(1);
      expect(options.some((opt) => opt.text().includes('+33'))).toBe(true);
      expect(options.some((opt) => opt.text().includes('+1'))).toBe(true);
    });

    it('should display correct flag image and alt attribute', () => {
      const flagImage = wrapper.find('img.mc-phone-number-input__flag-image');
      expect(flagImage.exists()).toBe(true);
      expect(flagImage.attributes('src')).toContain('flagcdn.com/fr.svg');
      expect(flagImage.attributes('alt')).toBeDefined();
    });

    it('should render chevron icon with aria-hidden true', () => {
      const chevron = wrapper.find('[data-testid="chevron-icon"]');
      expect(chevron.exists()).toBe(true);
      expect(chevron.attributes('aria-hidden')).toBe('true');
    });
  });

  describe('Phone Input', () => {
    it('should render phone input field with correct id', () => {
      const input = wrapper.find('input[type="tel"]');
      expect(input.exists()).toBe(true);
      expect(input.attributes('id')).toBe(defaultProps.id);
    });

    it('should display country calling code prefix by default', () => {
      expect(
        wrapper.find('.mc-phone-number-input__country-code').exists(),
      ).toBe(true);
      expect(wrapper.find('.mc-phone-number-input__country-code').text()).toBe(
        '+33',
      );
    });

    it('should hide prefix when prefix prop is false', () => {
      wrapper = mount(MPhoneNumber, {
        props: { ...defaultProps, prefix: false },
      });
      expect(
        wrapper.find('.mc-phone-number-input__country-code').exists(),
      ).toBe(false);
    });

    it('should set custom placeholder if provided', () => {
      const placeholder = 'Enter your phone number';
      wrapper = mount(MPhoneNumber, {
        props: { ...defaultProps, placeholder },
      });
      expect(wrapper.find('input[type="tel"]').attributes('placeholder')).toBe(
        placeholder,
      );
    });

    it('should use dynamic placeholder when no custom placeholder provided', () => {
      expect(wrapper.find('input[type="tel"]').attributes('placeholder')).toBe(
        '01 23 45 67 89',
      );
    });
  });

  describe('States', () => {
    it('should be enabled by default', () => {
      const input = wrapper.find('input[type="tel"]');
      const select = wrapper.find('select');
      expect(input.attributes('disabled')).toBeUndefined();
      expect(input.attributes('readonly')).toBeUndefined();
      expect(select.attributes('disabled')).toBeUndefined();
      expect(select.attributes('readonly')).toBeUndefined();
    });

    it('should be disabled when disabled prop is true', () => {
      wrapper = mount(MPhoneNumber, {
        props: { ...defaultProps, disabled: true },
      });
      expect(
        wrapper.find('input[type="tel"]').attributes('disabled'),
      ).toBeDefined();
      expect(wrapper.find('select').attributes('disabled')).toBeDefined();
    });

    it('should be readonly when readonly prop is true', () => {
      wrapper = mount(MPhoneNumber, {
        props: { ...defaultProps, readonly: true },
      });
      expect(
        wrapper.find('input[type="tel"]').attributes('readonly'),
      ).toBeDefined();
      expect(wrapper.find('select').attributes('readonly')).toBeDefined();
    });

    it('should apply invalid class when isInvalid is true', () => {
      wrapper = mount(MPhoneNumber, {
        props: { ...defaultProps, isInvalid: true },
      });
      expect(wrapper.find('.mc-phone-number-input__input').classes()).toContain(
        'is-invalid',
      );
    });
  });

  describe('Sizes', () => {
    it('should have medium size by default', () => {
      expect(wrapper.find('select').classes()).not.toContain('mc-select--s');
      expect(
        wrapper.find('.mc-phone-number-input__input').classes(),
      ).not.toContain('mc-text-input--s');
    });

    it('should apply small size when size prop is s', () => {
      wrapper = mount(MPhoneNumber, {
        props: { ...defaultProps, size: 's' },
      });
      expect(
        wrapper.find('.mc-phone-number-input__select').classes(),
      ).toContain('mc-select--s');
      expect(wrapper.find('.mc-phone-number-input__input').classes()).toContain(
        'mc-text-input--s',
      );
    });
  });

  describe('Events', () => {
    it('should emit update:modelValue with international format on input', async () => {
      const input = wrapper.find('input[type="tel"]');

      await input.setValue('+33123456789');
      await nextTick();

      expect(wrapper.emitted('update:modelValue')).toBeTruthy();

      const emittedVal = wrapper.emitted('update:modelValue')?.[0][0] as string;
      expect(emittedVal.startsWith('+')).toBe(true);
    });

    it('should emit valid event on input', async () => {
      const input = wrapper.find('input[type="tel"]');
      await input.setValue('+33123456789');
      await nextTick();
      expect(wrapper.emitted('valid')).toBeTruthy();
    });

    it('should sanitize input removing invalid characters', async () => {
      const input = wrapper.find('input[type="tel"]');
      await input.setValue('123abc456def+() -');

      expect((input.element as HTMLInputElement).value).toBe('123456+() -');
    });
  });

  describe('Validation', () => {
    beforeEach(() => {
      vi.mocked(isValidPhoneNumber).mockImplementation((number: string) => {
        const digits = number.replace(/\D/g, '');
        return digits.length >= 10;
      });
    });

    it('should mark input invalid if phone number is invalid', async () => {
      wrapper = mount(MPhoneNumber, {
        props: { ...defaultProps, modelValue: '123' },
      });
      await nextTick();
      expect(wrapper.find('.mc-phone-number-input__input').classes()).toContain(
        'is-invalid',
      );
    });

    it('should not mark input invalid if empty', async () => {
      wrapper = mount(MPhoneNumber, {
        props: { ...defaultProps, modelValue: '' },
      });
      await nextTick();
      expect(
        wrapper.find('.mc-phone-number-input__input').classes(),
      ).not.toContain('is-invalid');
    });
  });

  describe('Accessibility', () => {
    it('should set aria-invalid attribute correctly', async () => {
      await wrapper.setProps({ isInvalid: true });
      const input = wrapper.find('input[type="tel"]');
      expect(input.attributes('aria-invalid')).toBeDefined();
    });

    it('should use provided id on input', () => {
      const input = wrapper.find('input[type="tel"]');
      expect(input.attributes('id')).toBe(defaultProps.id);
    });

    it('should set alt attribute on flag image', () => {
      const flagImg = wrapper.find('img.mc-phone-number-input__flag-image');
      expect(flagImg.attributes('alt')).toBeTruthy();
    });

    it('should mark chevron icon aria-hidden as true', () => {
      const chevron = wrapper.find('[data-testid="chevron-icon"]');
      expect(chevron.attributes('aria-hidden')).toBe('true');
    });
  });

  describe('Props reactivity', () => {
    it('should update input when modelValue changes', async () => {
      await wrapper.setProps({ modelValue: '+33123456789' });
      await nextTick();
      const input = wrapper.find('input[type="tel"]');
      expect((input.element as HTMLInputElement).value).toBe('+33123456789');
    });
  });

  describe('Locale', () => {
    it('should use French locale by default', () => {
      const options = wrapper.findAll('option');
      const franceOption = options.find(
        (opt) => opt.attributes('value') === 'FR',
      );
      expect(franceOption?.text()).toContain('France');
    });

    it('should display country names in specified locale', () => {
      wrapper = mount(MPhoneNumber, {
        props: { ...defaultProps, locale: 'en' },
      });
      const options = wrapper.findAll('option');
      const franceOption = options.find(
        (opt) => opt.attributes('value') === 'FR',
      );
      // Country names should be translated according to the locale
      expect(franceOption?.text()).toBeTruthy();
    });

    it('should update country names when locale prop changes', async () => {
      await wrapper.setProps({ locale: 'es' });
      await nextTick();
      const options = wrapper.findAll('option');
      expect(options.length).toBeGreaterThan(1);
    });
  });

  describe('Country Codes', () => {
    it('should display all countries by default', () => {
      const options = wrapper.findAll('option');
      // Minus 1 for the empty hidden option
      expect(options.length - 1).toBe(5); // Mocked to return 5 countries
    });

    it('should display only specified countries when countryCodes prop is provided', () => {
      wrapper = mount(MPhoneNumber, {
        props: {
          ...defaultProps,
          countryCodes: ['FR', 'US', 'GB'] as CountryCode[],
        },
      });
      const options = wrapper.findAll('option');
      // Plus 1 for the empty hidden option
      expect(options.length).toBe(4);
      expect(options.some((opt) => opt.attributes('value') === 'FR')).toBe(
        true,
      );
      expect(options.some((opt) => opt.attributes('value') === 'US')).toBe(
        true,
      );
      expect(options.some((opt) => opt.attributes('value') === 'GB')).toBe(
        true,
      );
    });

    it('should update available countries when countryCodes prop changes', async () => {
      await wrapper.setProps({ countryCodes: ['FR', 'DE'] as CountryCode[] });
      await nextTick();
      const options = wrapper.findAll('option');
      // Plus 1 for the empty hidden option
      expect(options.length).toBe(3);
      expect(options.some((opt) => opt.attributes('value') === 'FR')).toBe(
        true,
      );
      expect(options.some((opt) => opt.attributes('value') === 'DE')).toBe(
        true,
      );
    });

    it('should work with single country in countryCodes', () => {
      wrapper = mount(MPhoneNumber, {
        props: {
          ...defaultProps,
          countryCodes: ['FR'] as CountryCode[],
        },
      });
      const options = wrapper.findAll('option');
      // Plus 1 for the empty hidden option
      expect(options.length).toBe(2);
      expect(options.some((opt) => opt.attributes('value') === 'FR')).toBe(
        true,
      );
    });

    it('should combine locale and countryCodes props', () => {
      wrapper = mount(MPhoneNumber, {
        props: {
          ...defaultProps,
          locale: 'en',
          countryCodes: ['US', 'GB', 'CA'] as CountryCode[],
        },
      });
      const options = wrapper.findAll('option');
      // Plus 1 for the empty hidden option
      expect(options.length).toBe(4);
      expect(options.some((opt) => opt.attributes('value') === 'US')).toBe(
        true,
      );
      expect(options.some((opt) => opt.attributes('value') === 'GB')).toBe(
        true,
      );
      expect(options.some((opt) => opt.attributes('value') === 'CA')).toBe(
        true,
      );
    });
  });
});
