import { describe, test, expect, vi } from 'vitest';
import { render, screen, waitFor } from '@testing-library/react';
import { type UserEvent, userEvent } from '@testing-library/user-event';
import ContentGeneratorTextArea from './ContentGenerator.js';

vi.mock('../../assets/lightbulb.svg?react', () => ({
  default: () => <div data-testid="lightbulb-icon">Lightbulb</div>,
}));

const clickLightbulb = async (user: UserEvent) => {
  const lightbulb = await screen.findByLabelText('Generate content with AI');
  await user.click(lightbulb);
};

describe('ContentGeneratorTextArea', () => {
  test('renders lightbulb button after editor toolbar mounts', async () => {
    render(<ContentGeneratorTextArea draftId="12345" />);

    await waitFor(() => {
      expect(screen.getByLabelText('Generate content with AI')).toBeInTheDocument();
    });
  });

  test('opens menu when lightbulb is clicked', async () => {
    const user = userEvent.setup();
    render(<ContentGeneratorTextArea draftId="12345" />);
    await clickLightbulb(user);

    expect(screen.getByTestId('content-generator__context-menu')).toBeInTheDocument();
    expect(screen.getByText('Replace with generated outline')).toBeInTheDocument();
    expect(screen.getByText('Replace with generated draft')).toBeInTheDocument();
  });

  test('closes menu when lightbulb is clicked again', async () => {
    const user = userEvent.setup();
    render(<ContentGeneratorTextArea draftId="12345" />);
    await clickLightbulb(user);

    expect(screen.getByText('Replace with generated outline')).toBeInTheDocument();
    const lightbulb = await screen.findByLabelText('Generate content with AI');
    await user.click(lightbulb);
    expect(screen.queryByText('Replace with generated outline')).not.toBeInTheDocument();
  });

  test('displays streaming overlay when loading', async () => {
    const user = userEvent.setup();
    render(<ContentGeneratorTextArea draftId="12345" />);
    await clickLightbulb(user);

    const generateOutline = screen.getByText('Replace with generated outline');
    await user.click(generateOutline);

    const messageElement = screen.getByText(/Generating content/i);
    expect(messageElement).toBeInTheDocument();
    await waitFor(
      () => {
        expect(screen.queryByText(/Generating content/i)).not.toBeInTheDocument();
      },
      { timeout: 3000 },
    );
  });

  test('generates text when "Generate outline" is clicked', async () => {
    const user = userEvent.setup();
    render(<ContentGeneratorTextArea draftId="12345" />);
    await clickLightbulb(user);

    const generateOutline = screen.getByText('Replace with generated outline');
    await user.click(generateOutline);

    const messageElement = screen.getByText(/Generating content/i);
    expect(messageElement).toBeInTheDocument();

    await waitFor(() => {
      const editableArea = screen.getByRole('textbox');
      expect(editableArea.textContent).toContain(
        'Strategic overview: This content focuses specifically on the unique cultural significance',
      );
    });
  });

  test('generates text when "Generate draft" is clicked', async () => {
    const user = userEvent.setup();
    render(<ContentGeneratorTextArea draftId="12345" />);
    await clickLightbulb(user);

    const generateDraft = screen.getByText('Replace with generated draft');
    await user.click(generateDraft);
    const generateContentButton = screen.getByRole('button', { name: 'Generate content' });
    await user.click(generateContentButton);

    const messageElement = screen.getByText(/Generating content/i);
    expect(messageElement).toBeInTheDocument();

    await waitFor(() => {
      const editableArea = screen.getByRole('textbox');
      expect(editableArea.textContent).toContain(
        'Philadelphia sports culture is legendary for its passionate fans and compelling traditions',
      );
    });
  });

  test('generates text with user options when "Generate draft" is clicked', async () => {
    const user = userEvent.setup();
    const fetchSpy = vi.spyOn(global, 'fetch');
    render(<ContentGeneratorTextArea draftId="0ef76729-8512-432b-8b72-8a1bc47e4d60" />);
    await clickLightbulb(user);

    const generateDraft = screen.getByText('Replace with generated draft');
    await user.click(generateDraft);

    // Fill in tone and style
    const toneAndStyleInput = screen.getByPlaceholderText('Describe the voice of your content');
    await user.type(toneAndStyleInput, 'over excited');

    // Fill in terms to exclude (tag input)
    const tagInput = screen.getByRole('textbox', { name: '' });
    await user.type(tagInput, 'orange{Enter}');

    // Fill in additional instructions
    const additionalInstructionsInput = screen.getByPlaceholderText(
      'What else should we know about your content?',
    );
    await user.type(additionalInstructionsInput, 'the audience is non-US');

    const generateContentButton = screen.getByRole('button', { name: 'Generate content' });
    await user.click(generateContentButton);

    // Verify the POST request was made with correct URL and payload
    await waitFor(() => {
      expect(fetchSpy).toHaveBeenCalledWith(
        '/conductor/proxy/v3/draft-generation/content-generation?draft_id=0ef76729-8512-432b-8b72-8a1bc47e4d60&cache=false',
        expect.objectContaining({
          method: 'POST',
          body: JSON.stringify({
            voice_and_tone: 'over excited',
            terms_to_exclude: ['orange'],
            additional_instructions: 'the audience is non-US',
          }),
          headers: expect.objectContaining({
            'Content-Type': 'application/json',
          }),
        }),
      );
    });
  });

  test('closes menu after "Generate outline" is clicked', async () => {
    const user = userEvent.setup();
    render(<ContentGeneratorTextArea draftId="12345" />);
    await clickLightbulb(user);

    await user.click(screen.getByText('Replace with generated outline'));

    expect(screen.queryByText('Replace with generated outline')).not.toBeInTheDocument();
  });

  test('closes menu after "Generate draft" is clicked', async () => {
    const user = userEvent.setup();
    render(<ContentGeneratorTextArea draftId="12345" />);
    await clickLightbulb(user);

    await user.click(screen.getByText('Replace with generated draft'));

    expect(screen.queryByText('Replace with generated draft')).not.toBeInTheDocument();
  });

  test('calls cancel when Cancel button is clicked in streaming overlay', async () => {
    const user = userEvent.setup();
    render(<ContentGeneratorTextArea draftId="12345" />);
    await clickLightbulb(user);

    await user.click(screen.getByText('Replace with generated outline'));

    const cancelButton = screen.getByRole('button', { name: 'Cancel' });
    await user.click(cancelButton);

    const editableArea = screen.getByRole('textbox');
    expect(editableArea.textContent).not.toContain(
      'enduring role in Philadelphia sports and fandom',
    );
  });

  test('displays error overlay when there is an error', async () => {
    const user = userEvent.setup();
    render(<ContentGeneratorTextArea draftId="12345-bad" />);
    await clickLightbulb(user);

    const generateOutline = screen.getByText('Replace with generated outline');
    await user.click(generateOutline);

    expect(screen.getByText('Error')).toBeInTheDocument();
    expect(screen.getByText(/There was an error generating content/)).toBeInTheDocument();
    expect(screen.getByRole('button', { name: 'Close' })).toBeInTheDocument();
  });

  test('calls cancel when Close button is clicked in error overlay', async () => {
    const user = userEvent.setup();
    render(<ContentGeneratorTextArea draftId="12345-bad" />);
    await clickLightbulb(user);

    const generateOutline = screen.getByText('Replace with generated outline');
    await user.click(generateOutline);

    expect(screen.getByText('Error')).toBeInTheDocument();
    expect(screen.getByText(/There was an error generating content/)).toBeInTheDocument();
    expect(screen.getByRole('button', { name: 'Close' })).toBeInTheDocument();

    const cancelButton = screen.getByRole('button', { name: 'Close' });
    await user.click(cancelButton);
    expect(screen.queryByText('Error')).not.toBeInTheDocument();
  });

  test('applies streaming class when loading', async () => {
    const user = userEvent.setup();
    const { container } = render(<ContentGeneratorTextArea draftId="12345" />);
    await clickLightbulb(user);

    const generateOutline = screen.getByText('Replace with generated outline');
    await user.click(generateOutline);

    const mainDiv = container.querySelector('.content-generator');
    expect(mainDiv).toHaveClass('content-generator--is-streaming');
  });

  test('does not apply streaming class when error occurs', async () => {
    const user = userEvent.setup();
    const { container } = render(<ContentGeneratorTextArea draftId="12345-bad" />);
    await clickLightbulb(user);

    const generateOutline = screen.getByText('Replace with generated outline');
    await user.click(generateOutline);

    const mainDiv = container.querySelector('.content-generator');
    expect(mainDiv).not.toHaveClass('conductor-content-generator--is-streaming');
  });

  test('converts Markdown to HTML when streaming', async () => {
    const user = userEvent.setup();
    render(<ContentGeneratorTextArea draftId="12345" />);
    await clickLightbulb(user);
    await user.click(screen.getByText('Replace with generated draft'));
    const generateContentButton = screen.getByRole('button', { name: 'Generate content' });
    await user.click(generateContentButton);
    const editableArea = screen.getByRole('textbox');

    await waitFor(() => {
      expect(editableArea.querySelector('h1')).toBeInTheDocument();
      expect(editableArea.querySelector('h2')).toBeInTheDocument();
      expect(editableArea.querySelector('h3')).toBeInTheDocument();
      expect(editableArea.querySelector('p')).toBeInTheDocument();
    });
  });

  describe('Copy Text Button', () => {
    test('copies text to clipboard when clicked', async () => {
      const user = userEvent.setup();
      render(<ContentGeneratorTextArea draftId="12345" initialText="<p>Hello, world!</p>" />);

      await screen.findByRole('textbox');

      const copyButton = screen.getByRole('button', { name: /Copy Text/i });
      await user.click(copyButton);
      const copiedText = await window.navigator.clipboard.readText();
      expect(copiedText).toEqual('Hello, world!');
    });

    test('copies formatted HTML content correctly', async () => {
      const user = userEvent.setup();
      render(<ContentGeneratorTextArea draftId="12345" initialText="<p>Hello, world!</p>" />);

      await screen.findByRole('textbox');

      const copyButton = screen.getByRole('button', { name: /Copy Text/i });
      await user.click(copyButton);
      const clipboardItems = await navigator.clipboard.read();
      const clipboardBlobs = [];

      for (const clipboardItem of clipboardItems) {
        for (const type of clipboardItem.types) {
          const blob = await clipboardItem.getType(type);
          clipboardBlobs.push(blob);
        }
      }

      expect(clipboardBlobs).toHaveLength(2);
      expect(clipboardBlobs[0]!.type).toEqual('text/html');

      const htmlText = await clipboardBlobs[0]!.text();
      expect(htmlText).toEqual('<p>Hello, world!</p>');
    });

    test('strips HTML for plain text copy', async () => {
      const user = userEvent.setup();
      render(<ContentGeneratorTextArea draftId="12345" initialText="<p>Hello, world!</p>" />);

      await screen.findByRole('textbox');

      const copyButton = screen.getByRole('button', { name: /Copy Text/i });
      await user.click(copyButton);
      const clipboardItems = await navigator.clipboard.read();
      const clipboardBlobs = [];

      for (const clipboardItem of clipboardItems) {
        for (const type of clipboardItem.types) {
          const blob = await clipboardItem.getType(type);
          clipboardBlobs.push(blob);
        }
      }

      expect(clipboardBlobs).toHaveLength(2);
      expect(clipboardBlobs[1]!.type).toEqual('text/plain');
      expect(clipboardBlobs[1]!.type).toEqual('text/plain');

      const htmlText = await clipboardBlobs[1]!.text();
      expect(htmlText).toEqual('Hello, world!');
    });
  });
});
