import React from 'react';
import { render, screen, waitFor } from '@testing-library/react';
import { userEvent } from '@testing-library/user-event';
import { describe, test, expect, vi } from 'vitest';
import GuidanceItemSelect from './GuidanceItemSelect.js';
import { type SimpleInsight } from '../Context.js';

vi.mock('../utilities/insight.js', () => ({
  updateInsight: vi.fn(() => Promise.resolve()),
}));

const { updateInsight } = await import('../utilities/insight.js');

const createInsight = (overrides: Partial<SimpleInsight> = {}): SimpleInsight => ({
  key: 'audience',
  initialValue: 'Developers',
  value: 'Designers',
  options: ['Developers', 'Designers', 'Managers'],
  ...overrides,
});

const mockProps = {
  draftId: 'draft-1',
  insight: createInsight(),
  refreshInsights: vi.fn(() => Promise.resolve()),
  label: 'Audience',
};

describe('GuidanceItemSelect', () => {
  describe('Basic Rendering', () => {
    test('renders the initial value with top pages text', () => {
      render(<GuidanceItemSelect {...mockProps} />);

      expect(screen.getByText('Developers')).toBeInTheDocument();
      expect(screen.getByText(/Top pages:/)).toBeInTheDocument();
    });

    test('renders the current value with label', () => {
      render(<GuidanceItemSelect {...mockProps} />);

      expect(screen.getByText(/Audience:/)).toBeInTheDocument();
      expect(screen.getByText(/Designers/)).toBeInTheDocument();
    });

    test('renders customize button', () => {
      render(<GuidanceItemSelect {...mockProps} />);

      expect(screen.getByRole('button', { name: /customize audience/i })).toBeInTheDocument();
    });

    test('uses custom topPagesText', () => {
      render(<GuidanceItemSelect {...mockProps} topPagesText="Top results" />);

      expect(screen.getByText(/Top results:/)).toBeInTheDocument();
    });
  });

  describe('Edit Mode', () => {
    test('shows select dropdown when customize is clicked', async () => {
      const user = userEvent.setup();
      render(<GuidanceItemSelect {...mockProps} />);
      expect(screen.queryByRole('combobox')).not.toBeInTheDocument();

      await user.click(screen.getByRole('button', { name: /customize audience/i }));

      expect(screen.getByRole('combobox')).toBeInTheDocument();
      expect(screen.getAllByRole('option')).toHaveLength(3);
    });

    test('hides customize button when in edit mode', async () => {
      const user = userEvent.setup();
      render(<GuidanceItemSelect {...mockProps} />);
      expect(screen.getByRole('button', { name: /customize audience/i })).toBeInTheDocument();
      await user.click(screen.getByRole('button', { name: /customize audience/i }));
      expect(screen.queryByRole('button', { name: /customize audience/i })).not.toBeInTheDocument();
    });

    test('applies editing CSS class when in edit mode', async () => {
      const user = userEvent.setup();
      const { container } = render(<GuidanceItemSelect {...mockProps} />);

      // eslint-disable-next-line testing-library/no-container, testing-library/no-node-access -- checking CSS class state
      expect(container.querySelector('.guidance-item-select--is-editing')).not.toBeInTheDocument();

      await user.click(screen.getByRole('button', { name: /customize audience/i }));

      // eslint-disable-next-line testing-library/no-container, testing-library/no-node-access -- checking CSS class state
      expect(container.querySelector('.guidance-item-select--is-editing')).toBeInTheDocument();
    });

    test('shows save button when in edit mode', async () => {
      const user = userEvent.setup();
      render(<GuidanceItemSelect {...mockProps} />);
      expect(screen.queryByRole('button', { name: /save audience/i })).not.toBeInTheDocument();
      await user.click(screen.getByRole('button', { name: /customize audience/i }));
      expect(screen.getByRole('button', { name: /save audience/i })).toBeInTheDocument();
    });

    test('renders all options in the select', async () => {
      const user = userEvent.setup();
      render(<GuidanceItemSelect {...mockProps} />);

      await user.click(screen.getByRole('button', { name: /customize audience/i }));

      const options = screen.getAllByRole('option');
      expect(options[0]).toHaveTextContent('Developers');
      expect(options[1]).toHaveTextContent('Designers');
      expect(options[2]).toHaveTextContent('Managers');
    });

    test('selects insight.value as the default option', async () => {
      const user = userEvent.setup();
      render(<GuidanceItemSelect {...mockProps} />);

      await user.click(screen.getByRole('button', { name: /customize audience/i }));

      expect(screen.getByRole('combobox')).toHaveValue('Designers');
    });

    test('allows changing the selected value', async () => {
      const user = userEvent.setup();
      render(<GuidanceItemSelect {...mockProps} />);

      await user.click(screen.getByRole('button', { name: /customize audience/i }));
      await user.selectOptions(screen.getByRole('combobox'), 'Managers');

      expect(screen.getByRole('combobox')).toHaveValue('Managers');
    });
  });

  describe('Cancel', () => {
    test('shows cancel button when in edit mode', async () => {
      const user = userEvent.setup();
      render(<GuidanceItemSelect {...mockProps} />);

      expect(screen.queryByRole('button', { name: /cancel/i })).not.toBeInTheDocument();

      await user.click(screen.getByRole('button', { name: /customize audience/i }));

      expect(screen.getByRole('button', { name: /cancel/i })).toBeInTheDocument();
    });

    test('clicking cancel closes edit mode', async () => {
      const user = userEvent.setup();
      render(<GuidanceItemSelect {...mockProps} />);

      await user.click(screen.getByRole('button', { name: /customize audience/i }));
      expect(screen.getByRole('combobox')).toBeInTheDocument();

      await user.click(screen.getByRole('button', { name: /cancel/i }));

      expect(screen.queryByRole('combobox')).not.toBeInTheDocument();
      expect(screen.getByRole('button', { name: /customize audience/i })).toBeInTheDocument();
      expect(screen.queryByRole('button', { name: /cancel/i })).not.toBeInTheDocument();
      expect(screen.queryByRole('button', { name: /save audience/i })).not.toBeInTheDocument();
      expect(screen.queryByRole('button', { name: /reset/i })).not.toBeInTheDocument();
    });

    test('clicking cancel does not save changes', async () => {
      const user = userEvent.setup();
      render(<GuidanceItemSelect {...mockProps} />);

      await user.click(screen.getByRole('button', { name: /customize audience/i }));

      await user.selectOptions(screen.getByRole('combobox'), 'Managers');

      await user.click(screen.getByRole('button', { name: /cancel/i }));

      expect(screen.queryByText('Managers')).not.toBeInTheDocument();
      expect(updateInsight).not.toHaveBeenCalled();
      expect(mockProps.refreshInsights).not.toHaveBeenCalled();
    });

    test('removes editing CSS class after cancel', async () => {
      const user = userEvent.setup();
      const { container } = render(<GuidanceItemSelect {...mockProps} />);

      await user.click(screen.getByRole('button', { name: /customize audience/i }));
      // eslint-disable-next-line testing-library/no-container, testing-library/no-node-access -- checking CSS class state
      expect(container.querySelector('.guidance-item-select--is-editing')).toBeInTheDocument();

      await user.click(screen.getByRole('button', { name: /cancel/i }));
      // eslint-disable-next-line testing-library/no-container, testing-library/no-node-access -- checking CSS class state
      expect(container.querySelector('.guidance-item-select--is-editing')).not.toBeInTheDocument();
    });
  });

  describe('Saving', () => {
    test('calls updateInsight and refreshInsights on save', async () => {
      const user = userEvent.setup();
      const props = mockProps;

      render(<GuidanceItemSelect {...props} />);

      await user.click(screen.getByRole('button', { name: /customize audience/i }));
      await user.click(screen.getByRole('button', { name: /save audience/i }));

      await waitFor(() => {
        expect(updateInsight).toHaveBeenCalledWith({
          draftId: 'draft-1',
          type: 'audience',
          value: 'Designers',
        });
      });

      await waitFor(() => {
        expect(props.refreshInsights).toHaveBeenCalled();
      });
    });

    test('disables customize button while saving', async () => {
      let resolveUpdate: () => void;
      (updateInsight as ReturnType<typeof vi.fn>).mockImplementation(
        () => new Promise<void>((resolve) => (resolveUpdate = resolve)),
      );

      const user = userEvent.setup();
      const props = mockProps;

      render(<GuidanceItemSelect {...props} />);

      await user.click(screen.getByRole('button', { name: /customize audience/i }));
      await user.click(screen.getByRole('button', { name: /save audience/i }));

      // While saving, the customize button should be disabled
      const customizeButton = screen.getByRole('button', { name: /customize audience/i });
      expect(customizeButton).toBeDisabled();

      // Resolve and clean up
      resolveUpdate!();
      await waitFor(() => {
        expect(customizeButton).not.toBeDisabled();
      });
    });

    test('applies saving CSS class while saving', async () => {
      let resolveUpdate: () => void;
      (updateInsight as ReturnType<typeof vi.fn>).mockImplementation(
        () => new Promise<void>((resolve) => (resolveUpdate = resolve)),
      );

      const user = userEvent.setup();
      const { container } = render(<GuidanceItemSelect {...mockProps} />);

      await user.click(screen.getByRole('button', { name: /customize audience/i }));
      await user.click(screen.getByRole('button', { name: /save audience/i }));

      // eslint-disable-next-line testing-library/no-container, testing-library/no-node-access -- checking CSS class state
      expect(container.querySelector('.guidance-item-select--is-saving')).toBeInTheDocument();

      resolveUpdate!();
      await waitFor(() => {
        // eslint-disable-next-line testing-library/no-container, testing-library/no-node-access -- checking CSS class state
        expect(container.querySelector('.guidance-item-select--is-saving')).not.toBeInTheDocument();
      });
    });

    test('does not call updateInsight when value is empty', async () => {
      (updateInsight as ReturnType<typeof vi.fn>).mockClear();

      const user = userEvent.setup();
      const props = mockProps;
      props.insight = createInsight({ value: '', initialValue: '' });

      render(<GuidanceItemSelect {...props} />);

      await user.click(screen.getByRole('button', { name: /customize audience/i }));
      await user.click(screen.getByRole('button', { name: /save audience/i }));

      await waitFor(() => {
        expect(props.refreshInsights).toHaveBeenCalled();
      });

      expect(updateInsight).not.toHaveBeenCalled();
    });
  });

  describe('Empty Options', () => {
    test('renders no options when insight has no options', async () => {
      const user = userEvent.setup();
      const props = mockProps;
      props.insight = createInsight({ options: [] });

      render(<GuidanceItemSelect {...props} />);

      await user.click(screen.getByRole('button', { name: /customize audience/i }));

      expect(screen.queryAllByRole('option')).toHaveLength(0);
    });
  });

  describe('Label Formatting', () => {
    test('lowercases the label in button text', () => {
      render(<GuidanceItemSelect {...mockProps} label="Journey Stage" />);

      expect(screen.getByRole('button', { name: /customize journey stage/i })).toBeInTheDocument();
    });
  });
});
