import { mockMatchMedia, render, screen, userEvent } from '../test-utils';
import Calendar from './Calendar';

mockMatchMedia();

const user = userEvent.setup();

const initialValue = new Date(2000, 0, 1);

describe('Calendar', () => {
  describe('in day view', () => {
    it.each([
      ['{ArrowLeft}', -1],
      ['{ArrowRight}', +1],
      ['{ArrowUp}', -7],
      ['{ArrowDown}', +7],
    ])("handles '%s' to step %d day(s)", async (text: string, step: number) => {
      const handleChange = jest.fn();
      render(<Calendar value={initialValue} onChange={handleChange} />);

      await user.click(screen.getByRole('button', { name: /selected day/iu }));
      await user.keyboard(text);

      const expected = new Date(initialValue);
      expected.setDate(initialValue.getDate() + step);
      expect(handleChange).toHaveBeenCalledWith(expected);
    });

    it('selects a day on click', async () => {
      const handleChange = jest.fn();
      render(<Calendar value={initialValue} onChange={handleChange} />);

      await user.click(screen.getByText('15'));

      expect(handleChange).toHaveBeenCalledWith(new Date(2000, 0, 15));
    });

    it('calls onSelect instead of onChange when clicking a day', async () => {
      const handleChange = jest.fn();
      const handleSelect = jest.fn();
      render(<Calendar value={initialValue} onChange={handleChange} onSelect={handleSelect} />);

      await user.click(screen.getByText('15'));

      expect(handleSelect).toHaveBeenCalledWith(new Date(2000, 0, 15));
      expect(handleChange).not.toHaveBeenCalled();
    });
  });

  describe('in year view', () => {
    it.each([
      ['{ArrowLeft}', -1],
      ['{ArrowRight}', +1],
      ['{ArrowUp}', -4],
      ['{ArrowDown}', +4],
    ])("handles '%s' to step %d year(s)", async (text: string, step: number) => {
      const handleChange = jest.fn();
      render(<Calendar value={initialValue} onChange={handleChange} />);

      await user.click(screen.getByRole('button', { name: /year view/iu }));
      await user.keyboard(text);

      const expected = new Date(initialValue);
      expected.setFullYear(initialValue.getFullYear() + step);
      expect(handleChange).toHaveBeenCalledWith(expected);
    });
  });

  describe('in month view', () => {
    it.each([
      ['{ArrowLeft}', -1],
      ['{ArrowRight}', +1],
      ['{ArrowUp}', -4],
      ['{ArrowDown}', +4],
    ])("handles '%s' to step %d month(s)", async (text: string, step: number) => {
      const handleChange = jest.fn();
      render(<Calendar value={initialValue} onChange={handleChange} />);

      await user.click(screen.getByRole('button', { name: /year view/iu }));
      await user.keyboard(' ');
      await user.keyboard(text);

      const expected = new Date(initialValue);
      expected.setMonth(initialValue.getMonth() + step);
      expect(handleChange).toHaveBeenCalledWith(expected);
    });
  });

  describe('min/max clamping', () => {
    it('limits navigation to min value', async () => {
      const min = new Date(initialValue);
      min.setDate(min.getDate() - 1);
      const handleChange = jest.fn();
      render(<Calendar value={initialValue} min={min} onChange={handleChange} />);

      await user.click(screen.getByRole('button', { name: /selected day/iu }));
      await user.keyboard('{ArrowLeft}{ArrowLeft}');

      expect(handleChange).toHaveBeenLastCalledWith(min);
    });

    it('limits navigation to max value', async () => {
      const max = new Date(initialValue);
      max.setDate(max.getDate() + 1);
      const handleChange = jest.fn();
      render(<Calendar value={initialValue} max={max} onChange={handleChange} />);

      await user.click(screen.getByRole('button', { name: /selected day/iu }));
      await user.keyboard('{ArrowRight}{ArrowRight}');

      expect(handleChange).toHaveBeenLastCalledWith(max);
    });

    it('clamps value to range when value is outside min/max', () => {
      const min = new Date(2000, 0, 5);
      const max = new Date(2000, 0, 20);
      const handleChange = jest.fn();
      render(<Calendar value={initialValue} min={min} max={max} onChange={handleChange} />);

      expect(handleChange).toHaveBeenCalledWith(min);
    });
  });

  describe('view switching', () => {
    it('switches to year view and back to day view', async () => {
      render(<Calendar value={initialValue} onChange={jest.fn()} />);

      await user.click(screen.getByRole('button', { name: /year view/iu }));
      expect(screen.getByRole('button', { name: /selected year/iu })).toBeInTheDocument();

      await user.click(screen.getByRole('button', { name: /selected year/iu }));
      expect(screen.getByRole('button', { name: /selected month/iu })).toBeInTheDocument();

      await user.click(screen.getByRole('button', { name: /selected month/iu }));
      expect(screen.getByRole('button', { name: /next month/iu })).toBeInTheDocument();
    });
  });
});
