import { expect, test } from 'vitest';
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react';
import { userEvent } from '@testing-library/user-event';
import { http, HttpResponse } from 'msw';
import App from './App.js';
import React from 'react';
import { server } from './mocks/server.js';

describe('App', () => {
  beforeEach(() => {
    window.history.pushState({}, '', '/canvas/editor/canvas_page/1');
  });

  afterEach(() => {
    // Use replaceState (not pushState) so the patched pushState doesn't fire
    // locationchange and trigger a setUrl update on the still-mounted component.
    window.history.replaceState({}, '', '/');
  });

  describe('Draft usage alert', () => {
    test('displays alert and hides tabs when no drafts remain', async () => {
      server.use(
        http.get('/conductor/api/settings', () =>
          HttpResponse.json({ accountId: '12345', maxDrafts: 5, usedDrafts: 5 }),
        ),
      );

      render(<App />);

      // Verify the alert is displayed
      expect(await screen.findByRole('alert')).toBeInTheDocument();
      expect(screen.getByText(/No drafts remaining\./i)).toBeInTheDocument();

      // Verify tabs are not displayed
      expect(screen.queryByRole('tab', { name: /keywords/i })).not.toBeInTheDocument();
      expect(screen.queryByRole('tab', { name: /competitors/i })).not.toBeInTheDocument();
      expect(screen.queryByRole('tab', { name: /content outline/i })).not.toBeInTheDocument();

      // Verify the no-drafts message is displayed
      expect(screen.getByText(/You have reached your draft limit/i)).toBeInTheDocument();
    });

    test('displays tabs when no drafts remain, but editing existing draft', async () => {
      server.use(
        http.get('/conductor/api/settings', () =>
          HttpResponse.json({ accountId: '12345', maxDrafts: 5, usedDrafts: 5 }),
        ),
        http.get('/conductor/api/draft/:entityType/:entityId', () =>
          HttpResponse.json({ draft_id: '12345' }),
        ),
      );

      render(<App />);

      // Verify the alert is displayed
      expect(await screen.findByRole('alert')).toBeInTheDocument();
      expect(screen.getByText(/No drafts remaining\./i)).toBeInTheDocument();

      // Verify tabs ARE displayed because we have an existing draft ID
      expect(screen.getByRole('tab', { name: /keywords/i })).toBeInTheDocument();
      expect(screen.getByRole('tab', { name: /competitors/i })).toBeInTheDocument();
      expect(screen.getByRole('tab', { name: /content outline/i })).toBeInTheDocument();

      // Verify the no-drafts message is NOT displayed
      expect(screen.queryByText(/You have reached your draft limit/i)).not.toBeInTheDocument();
    });

    test('displays error message when draft usage information is not available', async () => {
      server.use(
        http.get('/conductor/api/settings', () => new HttpResponse(null, { status: 500 })),
      );

      render(<App />);

      // Verify the error message is displayed
      expect(await screen.findByText(/Unable to fetch settings/i)).toBeInTheDocument();

      // Verify tabs are not displayed
      expect(screen.queryByRole('tab', { name: /keywords/i })).not.toBeInTheDocument();
      expect(screen.queryByRole('tab', { name: /competitors/i })).not.toBeInTheDocument();
      expect(screen.queryByRole('tab', { name: /content outline/i })).not.toBeInTheDocument();
    });

    test('shows the alert when more than two-thirds of drafts are used', async () => {
      server.use(
        http.get('/conductor/api/settings', () =>
          HttpResponse.json({ accountId: '12345', maxDrafts: 9, usedDrafts: 7 }),
        ),
      );

      render(<App />);

      expect(await screen.findByRole('alert')).toBeInTheDocument();
      expect(
        screen.getByText(/You have used 7 out of 9 of your writing drafts/i),
      ).toBeInTheDocument();
    });

    test("doesn't show the alert when two-thirds or less of drafts are used", async () => {
      // Default handler returns usedDrafts: 2, maxDrafts: 5 (40% — below the 60% threshold).
      render(<App />);

      await screen.findByRole('tab', { name: /keywords/i });
      expect(screen.queryByRole('alert')).not.toBeInTheDocument();
    });

    test('shows contact support message when draft limit reached', async () => {
      server.use(
        http.get('/conductor/api/settings', () =>
          HttpResponse.json({ accountId: '12345', maxDrafts: 10, usedDrafts: 10 }),
        ),
      );

      render(<App />);

      expect(await screen.findByRole('alert')).toBeInTheDocument();
      expect(
        screen.getByText(
          /You have reached your draft limit. Please contact support to increase your limit/i,
        ),
      ).toBeInTheDocument();

      // Verify tabs are not displayed
      expect(screen.queryByRole('tab', { name: /keywords/i })).not.toBeInTheDocument();
      expect(screen.queryByRole('tab', { name: /competitors/i })).not.toBeInTheDocument();
      expect(screen.queryByRole('tab', { name: /content outline/i })).not.toBeInTheDocument();
    });
  });

  test('4 tabs are present and first tab content is visible', async () => {
    render(<App />);

    // Verify all 4 tabs are present
    const keywordsTab = await screen.findByRole('tab', { name: /keywords/i });
    const competitorsTab = screen.getByRole('tab', { name: /competitors/i });
    // const guidanceTab = screen.getByRole('tab', { name: /guidance/i });
    const contentOutlineTab = screen.getByRole('tab', { name: /content outline/i });

    expect(keywordsTab).toBeInTheDocument();
    expect(competitorsTab).toBeInTheDocument();
    // expect(guidanceTab).toBeInTheDocument();
    expect(contentOutlineTab).toBeInTheDocument();

    // Verify the first tab (Keywords) is selected/active by default
    expect(keywordsTab).toHaveAttribute('aria-selected', 'true');
  });

  describe('Keywords', () => {
    test('Keywords are saved to state', async () => {
      const user = userEvent.setup();

      render(<App />);

      const input = await screen.findByRole('textbox');

      await user.type(input, 'jawn{Enter}');
      await user.type(input, 'tastykake{Enter}');
      expect(screen.getByText(/^jawn$/)).toBeInTheDocument();
      expect(screen.getByText(/^tastykake$/)).toBeInTheDocument();

      const competitorsTab = screen.getByRole('tab', { name: /competitors/i });
      await user.click(competitorsTab);

      expect(screen.queryByText(/^jawn$/)).not.toBeInTheDocument();
      expect(screen.queryByText(/^tastykake$/)).not.toBeInTheDocument();

      const keywordsTab = screen.getByRole('tab', { name: /keywords/i });
      await user.click(keywordsTab);

      expect(screen.getByText(/^jawn$/)).toBeInTheDocument();
      expect(screen.getByText(/^tastykake$/)).toBeInTheDocument();
    });

    test('Keywords persist when switching tabs', async () => {
      const user = userEvent.setup();

      render(<App />);

      // Add a keyword in the Keywords tab and 'Evaluate'
      await user.type(await screen.findByRole('textbox'), 'phanatic{Enter}');
      const evaluateButton = screen.getByRole('button', { name: /evaluate/i });
      expect(evaluateButton).not.toBeDisabled();
      await user.click(evaluateButton);
      const loadingSpinner = screen.getByRole('status');
      expect(loadingSpinner).toBeInTheDocument();
      await waitFor(
        () => {
          const loadingSpinner = screen.queryByRole('status');
          expect(loadingSpinner).not.toBeInTheDocument();
        },
        {
          timeout: 3000,
        },
      );
      await waitFor(() => {
        expect(screen.getByRole('table')).toBeInTheDocument();
      });

      // Switch to the Competitors tab
      await user.click(screen.getByRole('tab', { name: /competitors/i }));
      expect(screen.queryByRole('table')).not.toBeInTheDocument();

      // Switch back to the Keywords tab
      await user.click(screen.getByRole('tab', { name: /keywords/i }));
      expect(screen.getByText('phanatic')).toBeInTheDocument();

      // Wait for results table to be restored
      await waitFor(() => {
        expect(screen.getByRole('table')).toBeInTheDocument();
      });
    });
  });

  test('Rank source is preserved', async () => {
    const user = userEvent.setup();
    const { container } = render(<App />);

    await screen.findByRole('tab', { name: /keywords/i });

    // eslint-disable-next-line testing-library/no-container, testing-library/no-node-access -- react-select internal DOM
    let singleValueElement = container.querySelector('.rank-source-selector__single-value');
    expect(singleValueElement).toHaveTextContent('Google (US / English)');

    // eslint-disable-next-line testing-library/no-container, testing-library/no-node-access -- react-select internal DOM
    fireEvent.mouseDown(container.querySelector('.rank-source-selector__dropdown-indicator')!, {
      button: 0,
    });

    await user.keyboard('United Kingdom');
    await user.keyboard('{Enter}');

    // eslint-disable-next-line testing-library/no-container, testing-library/no-node-access -- react-select internal DOM
    singleValueElement = container.querySelector('.rank-source-selector__single-value');
    expect(singleValueElement).toHaveTextContent('Google (United Kingdom / English)');

    const competitorsTab = screen.getByRole('tab', { name: /competitors/i });
    await user.click(competitorsTab);
    const keywordsTab = screen.getByRole('tab', { name: /keywords/i });
    await user.click(keywordsTab);

    // eslint-disable-next-line testing-library/no-container, testing-library/no-node-access -- react-select internal DOM
    singleValueElement = container.querySelector('.rank-source-selector__single-value');
    expect(singleValueElement).toHaveTextContent('Google (United Kingdom / English)');
  });

  test('Competitor URLs fetched when clicking the Generate Content Guidance button', async () => {
    const user = userEvent.setup();

    render(<App />);

    const input = await screen.findByRole('textbox');

    await user.type(input, 'jawn{Enter}');
    await user.type(input, 'tastykake{Enter}');
    expect(screen.getByText(/^jawn$/)).toBeInTheDocument();
    expect(screen.getByText(/^tastykake$/)).toBeInTheDocument();

    const generateButton = screen.getByRole('button', { name: /generate content guidance/i });
    await user.click(generateButton);
    await waitFor(() => {
      expect(screen.queryByText('Loading')).not.toBeInTheDocument();
    });

    const listItems = screen.getAllByRole('listitem');
    expect(listItems).toHaveLength(30);
  });

  test('Competitor URLs fetched when changing tabs', async () => {
    const user = userEvent.setup();

    render(<App />);

    const input = await screen.findByRole('textbox');

    await user.type(input, 'jawn{Enter}');
    await user.type(input, 'tastykake{Enter}');

    const competitorsTab = screen.getByRole('tab', { name: /competitors/i });
    await user.click(competitorsTab);
    await waitFor(() => {
      expect(screen.queryByText('Loading')).not.toBeInTheDocument();
    });

    const listItems = screen.getAllByRole('listitem');
    expect(listItems).toHaveLength(30);

    const label = screen.getByLabelText('https://en.wikipedia.org/wiki/Jawn');
    expect(label).toBeInTheDocument();
  });

  test('Competitor URLs updated when keywords are updated', async () => {
    const user = userEvent.setup();

    const { container } = render(<App />);

    let input = await screen.findByRole('textbox');

    await user.type(input, 'jawn{Enter}');
    await user.type(input, 'tastykake{Enter}');

    const competitorsTab = screen.getByRole('tab', { name: /competitors/i });
    await user.click(competitorsTab);
    await waitFor(() => {
      expect(screen.queryByText('Loading')).not.toBeInTheDocument();
    });

    // eslint-disable-next-line testing-library/no-container, testing-library/no-node-access -- checking DOM order
    let firstLabel = container.querySelector('label:first-of-type');
    expect(firstLabel).toHaveTextContent('https://en.wikipedia.org/wiki/Jawn');

    const keywordsTab = screen.getByRole('tab', { name: /keywords/i });
    await user.click(keywordsTab);
    const removeJawnButton = screen.getByLabelText('Remove jawn');
    await user.click(removeJawnButton);
    const removeTastykakeButton = screen.getByLabelText('Remove tastykake');
    await user.click(removeTastykakeButton);
    expect(screen.queryByText('jawn')).not.toBeInTheDocument();
    expect(screen.queryByText('tastykake')).not.toBeInTheDocument();
    input = screen.getByRole('textbox');
    await user.type(input, 'gritty{Enter}');
    await user.type(input, 'phanatic{Enter}');
    await user.type(input, 'swoop{Enter}');

    const generateButton = screen.getByRole('button', { name: /generate content guidance/i });
    await user.click(generateButton);
    await waitFor(() => {
      expect(screen.queryByText('Loading')).not.toBeInTheDocument();
    });
    // eslint-disable-next-line testing-library/no-container, testing-library/no-node-access -- checking DOM order
    firstLabel = container.querySelector('label:first-of-type');
    expect(firstLabel).toHaveTextContent('https://www.instagram.com/grittynhl/');
  });

  test('Excluded competitor URLs are preserved', async () => {
    const user = userEvent.setup();

    render(<App />);

    const input = await screen.findByRole('textbox');

    await user.type(input, 'jawn{Enter}');
    await user.type(input, 'tastykake{Enter}');

    const generateButton = screen.getByRole('button', { name: /generate content guidance/i });
    await user.click(generateButton);
    await waitFor(() => {
      expect(screen.queryByText('Loading')).not.toBeInTheDocument();
    });

    const listItems = screen.getAllByRole('listitem');
    expect(listItems).toHaveLength(30);

    const checkboxes = screen.getAllByRole('checkbox') as HTMLInputElement[];
    await user.click(checkboxes[0]!);
    await user.click(checkboxes[1]!);
    await user.click(checkboxes[2]!);
    const checkedCheckboxes = checkboxes.filter((checkbox) => checkbox.checked);
    expect(checkedCheckboxes).toHaveLength(27);

    const keywordsTab = screen.getByRole('tab', { name: /keywords/i });
    await user.click(keywordsTab);
    // const guidanceTab = screen.getByRole('tab', { name: /guidance/i });
    // await user.click(guidanceTab);
    const competitorsTab = screen.getByRole('tab', { name: /competitors/i });
    await user.click(competitorsTab);

    const checkboxesAgain = screen.getAllByRole('checkbox') as HTMLInputElement[];
    const checkedCheckboxesAgain = checkboxesAgain.filter((checkbox) => checkbox.checked);
    expect(checkedCheckboxesAgain).toHaveLength(27);
  });

  test('Competitor URLs are preserved', async () => {
    server.use(
      http.get('/conductor/api/draft/:entityType/:entityId', () =>
        HttpResponse.json({ draft_id: 'bead88f1-bd37-48a7-9861-46f04058141d' }),
      ),
    );

    const user = userEvent.setup();

    render(<App />);

    // Wait for the app to fully initialise (settings + draft fetch)
    const generateButton = await screen.findByRole('button', {
      name: /generate content guidance/i,
    });
    await waitFor(() => {
      expect(screen.queryByText('Loading')).not.toBeInTheDocument();
    });

    await user.click(generateButton);
    await waitFor(() => {
      expect(screen.queryByText('Loading')).not.toBeInTheDocument();
    });

    const checkboxes = screen.getAllByRole('checkbox') as HTMLInputElement[];
    expect(checkboxes).toHaveLength(45);
    await user.click(checkboxes[0]!);

    const keywordsTab = screen.getByRole('tab', { name: /keywords/i });
    await user.click(keywordsTab);
    await user.click(generateButton);
    const checkedCheckboxes = checkboxes.filter((checkbox) => checkbox.checked);
    expect(checkedCheckboxes).toHaveLength(44);
  });

  test('Competitor URLs are reset when keywords are changed', async () => {
    const user = userEvent.setup();

    render(<App />);

    let input = await screen.findByRole('textbox');

    await user.type(input, 'jawn{Enter}');
    await user.type(input, 'tastykake{Enter}');

    let generateButton = screen.getByRole('button', { name: /generate content guidance/i });
    await user.click(generateButton);
    await waitFor(() => {
      expect(screen.queryByText('Loading')).not.toBeInTheDocument();
    });

    let checkboxes = screen.getAllByRole('checkbox') as HTMLInputElement[];
    expect(checkboxes).toHaveLength(30);
    await user.click(checkboxes[0]!);
    let checkedCheckboxes = checkboxes.filter((checkbox) => checkbox.checked);
    expect(checkedCheckboxes).toHaveLength(29);

    const keywordsTab = screen.getByRole('tab', { name: /keywords/i });
    await user.click(keywordsTab);
    await user.click(generateButton);
    expect(checkedCheckboxes).toHaveLength(29);

    await user.click(keywordsTab);
    input = screen.getByRole('textbox');
    await user.type(input, 'gritty{Enter}');
    generateButton = screen.getByRole('button', { name: /generate content guidance/i });
    await user.click(generateButton);
    await waitFor(() => {
      expect(screen.queryByText('Loading')).not.toBeInTheDocument();
    });
    checkboxes = screen.getAllByRole('checkbox') as HTMLInputElement[];
    checkedCheckboxes = checkboxes.filter((checkbox) => checkbox.checked);
    expect(checkedCheckboxes).toHaveLength(30);
  });

  test('settings and draft are refetched when the URL changes', async () => {
    render(<App />);

    // Initial load: default handler returns usedDrafts: 2, no draft.
    await screen.findByRole('tab', { name: /keywords/i });
    expect(screen.queryByRole('alert')).not.toBeInTheDocument();

    // Simulate navigation: override both endpoints for the new page.
    server.use(
      http.get('/conductor/api/settings', () =>
        HttpResponse.json({ accountId: '12345', maxDrafts: 5, usedDrafts: 5 }),
      ),
      http.get('/conductor/api/draft/:entityType/:entityId', () =>
        HttpResponse.json({ draft_id: 'new-draft-id' }),
      ),
    );
    await act(async () => {
      window.history.pushState({}, '', '/canvas/editor/canvas_page/2');
      window.dispatchEvent(new PopStateEvent('popstate'));
    });

    // New settings (usedDrafts: 5) trigger the alert; the draft is linked so
    // tabs remain visible.
    await screen.findByRole('alert');
    expect(screen.getByRole('tab', { name: /keywords/i })).toBeInTheDocument();
  });
});
