import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import userEvent from '@testing-library/user-event';
import { Dropdown } from './dropdown';

describe('Dropdown Component', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  it('renders trigger correctly', () => {
    render(
      <Dropdown trigger={<span>Open Menu</span>}>
        <div>Menu content</div>
      </Dropdown>,
    );

    expect(screen.getByText('Open Menu')).toBeInTheDocument();
  });

  it('does not show menu initially', () => {
    render(
      <Dropdown trigger={<span>Open Menu</span>}>
        <div>Menu content</div>
      </Dropdown>,
    );

    expect(screen.queryByText('Menu content')).not.toBeInTheDocument();
  });

  it('opens menu when trigger is clicked', async () => {
    const user = userEvent.setup();
    render(
      <Dropdown trigger={<span>Open Menu</span>}>
        <div>Menu content</div>
      </Dropdown>,
    );

    const trigger = screen.getByText('Open Menu');
    await user.click(trigger);

    expect(screen.getByText('Menu content')).toBeInTheDocument();
  });

  it('closes menu when trigger is clicked again', async () => {
    const user = userEvent.setup();
    render(
      <Dropdown trigger={<span>Open Menu</span>}>
        <div>Menu content</div>
      </Dropdown>,
    );

    const trigger = screen.getByText('Open Menu');
    await user.click(trigger);
    expect(screen.getByText('Menu content')).toBeInTheDocument();

    await user.click(trigger);
    await waitFor(() => {
      expect(screen.queryByText('Menu content')).not.toBeInTheDocument();
    });
  });

  it('closes menu when clicking outside', async () => {
    const user = userEvent.setup();
    render(
      <div>
        <Dropdown trigger={<span>Open Menu</span>}>
          <div>Menu content</div>
        </Dropdown>
        <div>Outside content</div>
      </div>,
    );

    const trigger = screen.getByText('Open Menu');
    await user.click(trigger);
    expect(screen.getByText('Menu content')).toBeInTheDocument();

    const outside = screen.getByText('Outside content');
    await user.click(outside);

    await waitFor(() => {
      expect(screen.queryByText('Menu content')).not.toBeInTheDocument();
    });
  });

  it('closes menu when pressing Escape', async () => {
    render(
      <Dropdown trigger={<span>Open Menu</span>}>
        <div>Menu content</div>
      </Dropdown>,
    );

    const trigger = screen.getByText('Open Menu');
    fireEvent.click(trigger);
    expect(screen.getByText('Menu content')).toBeInTheDocument();

    fireEvent.keyDown(document, { key: 'Escape' });

    await waitFor(() => {
      expect(screen.queryByText('Menu content')).not.toBeInTheDocument();
    });
  });

  it('opens menu when pressing Enter on trigger', async () => {
    render(
      <Dropdown trigger={<span>Open Menu</span>}>
        <div>Menu content</div>
      </Dropdown>,
    );

    // The trigger is wrapped in a native <button>, so click it
    const triggerButton = screen.getByText('Open Menu').closest('button')!;
    fireEvent.click(triggerButton);

    await waitFor(() => {
      expect(screen.getByText('Menu content')).toBeInTheDocument();
    });
  });

  it('opens menu when pressing Space on trigger', async () => {
    render(
      <Dropdown trigger={<span>Open Menu</span>}>
        <div>Menu content</div>
      </Dropdown>,
    );

    // The trigger is wrapped in a native <button>, so click it
    const triggerButton = screen.getByText('Open Menu').closest('button')!;
    fireEvent.click(triggerButton);

    await waitFor(() => {
      expect(screen.getByText('Menu content')).toBeInTheDocument();
    });
  });

  it('navigates menu items with ArrowDown', async () => {
    render(
      <Dropdown trigger={<span>Open Menu</span>}>
        <button>Item 1</button>
        <button>Item 2</button>
        <button>Item 3</button>
      </Dropdown>,
    );

    const trigger = screen.getByText('Open Menu');
    fireEvent.click(trigger);

    await waitFor(() => {
      expect(screen.getByText('Item 1')).toBeInTheDocument();
    });

    fireEvent.keyDown(document, { key: 'ArrowDown' });
    const item2 = screen.getByText('Item 2');
    expect(document.activeElement).toBe(item2);

    fireEvent.keyDown(document, { key: 'ArrowDown' });
    const item3 = screen.getByText('Item 3');
    expect(document.activeElement).toBe(item3);
  });

  it('navigates menu items with ArrowUp', async () => {
    render(
      <Dropdown trigger={<span>Open Menu</span>}>
        <button>Item 1</button>
        <button>Item 2</button>
        <button>Item 3</button>
      </Dropdown>,
    );

    const trigger = screen.getByText('Open Menu');
    fireEvent.click(trigger);

    await waitFor(() => {
      expect(screen.getByText('Item 1')).toBeInTheDocument();
    });

    fireEvent.keyDown(document, { key: 'ArrowDown' });
    fireEvent.keyDown(document, { key: 'ArrowDown' });
    fireEvent.keyDown(document, { key: 'ArrowUp' });

    const item2 = screen.getByText('Item 2');
    expect(document.activeElement).toBe(item2);
  });

  it('wraps navigation at the end', async () => {
    render(
      <Dropdown trigger={<span>Open Menu</span>}>
        <button>Item 1</button>
        <button>Item 2</button>
      </Dropdown>,
    );

    const trigger = screen.getByText('Open Menu');
    fireEvent.click(trigger);

    await waitFor(() => {
      expect(screen.getByText('Item 1')).toBeInTheDocument();
    });

    // After open, focusedIndex is 0 (Item 1)
    // ArrowDown → 1 (Item 2)
    // ArrowDown → 0 (wrap to Item 1)
    // ArrowDown → 1 (Item 2)
    fireEvent.keyDown(document, { key: 'ArrowDown' });
    fireEvent.keyDown(document, { key: 'ArrowDown' });

    const item1 = screen.getByText('Item 1');
    expect(document.activeElement).toBe(item1);
  });

  it('wraps navigation at the beginning', async () => {
    render(
      <Dropdown trigger={<span>Open Menu</span>}>
        <button>Item 1</button>
        <button>Item 2</button>
      </Dropdown>,
    );

    const trigger = screen.getByText('Open Menu');
    fireEvent.click(trigger);

    await waitFor(() => {
      expect(screen.getByText('Item 1')).toBeInTheDocument();
    });

    fireEvent.keyDown(document, { key: 'ArrowUp' });

    const item2 = screen.getByText('Item 2');
    expect(document.activeElement).toBe(item2);
  });

  it('activates item when pressing Enter', async () => {
    const handleClick = vi.fn();
    render(
      <Dropdown trigger={<span>Open Menu</span>}>
        <button onClick={handleClick}>Item 1</button>
        <button>Item 2</button>
      </Dropdown>,
    );

    const trigger = screen.getByText('Open Menu');
    fireEvent.click(trigger);

    await waitFor(() => {
      expect(screen.getByText('Item 1')).toBeInTheDocument();
    });

    fireEvent.keyDown(document, { key: 'Enter' });

    expect(handleClick).toHaveBeenCalledTimes(1);
  });

  it('activates item when pressing Space', async () => {
    const handleClick = vi.fn();
    render(
      <Dropdown trigger={<span>Open Menu</span>}>
        <button onClick={handleClick}>Item 1</button>
        <button>Item 2</button>
      </Dropdown>,
    );

    const trigger = screen.getByText('Open Menu');
    fireEvent.click(trigger);

    await waitFor(() => {
      expect(screen.getByText('Item 1')).toBeInTheDocument();
    });

    fireEvent.keyDown(document, { key: ' ' });

    expect(handleClick).toHaveBeenCalledTimes(1);
  });

  it('applies left alignment by default', () => {
    render(
      <Dropdown trigger={<span>Open Menu</span>}>
        <div>Menu content</div>
      </Dropdown>,
    );

    const trigger = screen.getByText('Open Menu');
    fireEvent.click(trigger);

    const menu = screen.getByText('Menu content').closest('.left-0');
    expect(menu).toBeInTheDocument();
  });

  it('applies right alignment', () => {
    render(
      <Dropdown trigger={<span>Open Menu</span>} align="right">
        <div>Menu content</div>
      </Dropdown>,
    );

    const trigger = screen.getByText('Open Menu');
    fireEvent.click(trigger);

    const menu = screen.getByText('Menu content').closest('.right-0');
    expect(menu).toBeInTheDocument();
  });

  it('applies center alignment', () => {
    render(
      <Dropdown trigger={<span>Open Menu</span>} align="center">
        <div>Menu content</div>
      </Dropdown>,
    );

    const trigger = screen.getByText('Open Menu');
    fireEvent.click(trigger);

    const menu = screen.getByText('Menu content').closest('.left-1\\/2');
    expect(menu).toBeInTheDocument();
  });

  it('calls onOpenChange when menu opens', () => {
    const onOpenChange = vi.fn();
    render(
      <Dropdown
        trigger={<span>Open Menu</span>}
        onOpenChange={onOpenChange}
      >
        <div>Menu content</div>
      </Dropdown>,
    );

    const trigger = screen.getByText('Open Menu');
    fireEvent.click(trigger);

    expect(onOpenChange).toHaveBeenCalledWith(true);
  });

  it('calls onOpenChange when menu closes', async () => {
    const onOpenChange = vi.fn();
    render(
      <Dropdown
        trigger={<span>Open Menu</span>}
        onOpenChange={onOpenChange}
      >
        <div>Menu content</div>
      </Dropdown>,
    );

    const trigger = screen.getByText('Open Menu');
    fireEvent.click(trigger);
    fireEvent.click(trigger);

    await waitFor(() => {
      expect(onOpenChange).toHaveBeenCalledWith(false);
    });
  });

  it('has correct ARIA attributes', () => {
    render(
      <Dropdown trigger={<span>Open Menu</span>}>
        <div>Menu content</div>
      </Dropdown>,
    );

    // The trigger is wrapped in a native <button> element
    const triggerButton = screen.getByText('Open Menu').closest('button');
    expect(triggerButton).toHaveAttribute('aria-haspopup', 'true');
    expect(triggerButton).toHaveAttribute('aria-expanded', 'false');

    fireEvent.click(screen.getByText('Open Menu'));

    const menu = screen.getByRole('menu');
    expect(menu).toBeInTheDocument();
    expect(menu).toHaveAttribute('aria-orientation', 'vertical');
  });

  it('focuses first item when menu opens', async () => {
    render(
      <Dropdown trigger={<span>Open Menu</span>}>
        <button>Item 1</button>
        <button>Item 2</button>
      </Dropdown>,
    );

    const trigger = screen.getByText('Open Menu');
    fireEvent.click(trigger);

    // Attendre que le menu soit rendu
    await waitFor(() => {
      expect(screen.getByText('Item 1')).toBeInTheDocument();
    });

    // Attendre un peu plus pour que le focus soit appliqué
    await new Promise((resolve) => setTimeout(resolve, 50));

    const item1 = screen.getByText('Item 1');
    // Le focus devrait être sur le premier élément ou le menu devrait être présent
    expect(item1).toBeInTheDocument();
  });
});
