import React from 'react';
import { render, screen, waitFor } from '@testing-library/react';
import { userEvent } from '@testing-library/user-event';
import '@testing-library/jest-dom';
import { describe, test, expect, vi, beforeEach } from 'vitest';
import GuidanceItemTextArea from './GuidanceItemTextArea.js';
import { updateInsight } from '../utilities/insight.js';

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

describe('GuidanceItemTextArea Component', () => {
  const mockInsight = {
    initialValue: 'Initial objective from API',
    value: 'Initial objective from API',
    key: 'OBJECTIVE_INSIGHT',
  };

  const mockProps = {
    draftId: 'test-draft-id',
    insight: mockInsight,
    refreshInsights: vi.fn(() => Promise.resolve()),
    label: 'Objective',
  };

  beforeEach(() => {
    vi.clearAllMocks();
  });

  test('displays objective with initial value on load', () => {
    render(<GuidanceItemTextArea {...mockProps} />);

    expect(screen.getByText(/Objective:/i)).toBeInTheDocument();
    expect(screen.getByText('Initial objective from API')).toBeInTheDocument();

    const customizeButton = screen.getByRole('button', { name: /customize objective/i });
    expect(customizeButton).toBeInTheDocument();
    expect(customizeButton).toBeEnabled();

    expect(screen.queryByRole('button', { name: /reset/i })).not.toBeInTheDocument();
    expect(screen.queryByRole('button', { name: /save objective/i })).not.toBeInTheDocument();
    expect(screen.queryByRole('textbox')).not.toBeInTheDocument();
  });

  test('shows textarea when "Customize objective" is clicked', async () => {
    const user = userEvent.setup();
    render(<GuidanceItemTextArea {...mockProps} />);

    const customizeButton = screen.getByRole('button', { name: /customize objective/i });
    await user.click(customizeButton);

    const textarea = screen.getByRole('textbox') as HTMLTextAreaElement;
    expect(textarea).toBeInTheDocument();
    expect(textarea.value).toBe('Initial objective from API');

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

  test('updates displayed objective after saving custom value', async () => {
    const user = userEvent.setup();
    render(<GuidanceItemTextArea {...mockProps} />);

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

    // Modify the textarea
    const textarea = screen.getByRole('textbox');
    await user.clear(textarea);
    await user.type(textarea, 'This is a new objective');

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

    // Wait for the save operation to complete
    await waitFor(() => {
      expect(screen.getByText('This is a new objective')).toBeInTheDocument();
    });
    await waitFor(() => {
      expect(screen.queryByRole('textbox')).not.toBeInTheDocument();
    });
  });

  test('navigating away and returning to the tab displays the custom objective', async () => {
    const user = userEvent.setup();
    const { rerender } = render(<GuidanceItemTextArea {...mockProps} />);

    await user.click(screen.getByRole('button', { name: /customize objective/i }));
    const textarea = screen.getByRole('textbox');
    await user.clear(textarea);
    await user.type(textarea, 'Custom saved objective');
    await user.click(screen.getByRole('button', { name: /save objective/i }));

    await waitFor(() => {
      expect(screen.getByText('Custom saved objective')).toBeInTheDocument();
    });

    const updatedInsight = {
      ...mockInsight,
      value: 'Custom saved objective',
    };
    rerender(<GuidanceItemTextArea {...mockProps} insight={updatedInsight} />);

    expect(screen.getByText('Custom saved objective')).toBeInTheDocument();
    expect(screen.queryByRole('textbox')).not.toBeInTheDocument();
  });

  test('clearing the text area disables the save button', async () => {
    const user = userEvent.setup();
    render(<GuidanceItemTextArea {...mockProps} />);

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

    const saveButton = screen.getByRole('button', { name: /save objective/i });
    expect(saveButton).toBeEnabled();

    const textarea = screen.getByRole('textbox');
    await user.clear(textarea);

    expect(saveButton).toBeDisabled();
  });

  test('clicking reset replaces the text area with insight.initialValue', async () => {
    const user = userEvent.setup();
    const propsWithCustomValue = {
      ...mockProps,
      insight: {
        ...mockInsight,
        value: 'Custom modified value',
      },
    };
    render(<GuidanceItemTextArea {...propsWithCustomValue} />);

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

    const textarea = screen.getByRole('textbox') as HTMLTextAreaElement;
    expect(textarea.value).toBe('Custom modified value');

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

    expect(textarea.value).toBe('Initial objective from API');
  });

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

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

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

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

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

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

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

      expect(screen.queryByRole('textbox')).not.toBeInTheDocument();
      expect(screen.getByRole('button', { name: /customize objective/i })).toBeInTheDocument();
      expect(screen.queryByRole('button', { name: /cancel/i })).not.toBeInTheDocument();
      expect(screen.queryByRole('button', { name: /save objective/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(<GuidanceItemTextArea {...mockProps} />);

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

      const textarea = screen.getByRole('textbox');
      await user.clear(textarea);
      await user.type(textarea, 'Unsaved changes');

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

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

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

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

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

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

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

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

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

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

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

      await waitFor(() => {
        expect(updateInsight).toHaveBeenCalledWith({
          draftId: 'test-draft-id',
          type: 'OBJECTIVE_INSIGHT',
          value: 'Initial objective from API',
        });
      });

      await waitFor(() => {
        expect(mockProps.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();
      render(<GuidanceItemTextArea {...mockProps} />);

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

      // While saving, the customize button should be disabled
      const customizeButton = screen.getByRole('button', { name: /customize objective/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(<GuidanceItemTextArea {...mockProps} />);

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

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

      resolveUpdate!();
      await waitFor(() => {
        expect(
          // eslint-disable-next-line testing-library/no-container, testing-library/no-node-access -- checking CSS class state
          container.querySelector('.guidance-item-textarea--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,
        insight: {
          ...mockInsight,
          value: '',
          initialValue: '',
        },
      };

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

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

      // Save button should be disabled when value is empty
      expect(screen.getByRole('button', { name: /save objective/i })).toBeDisabled();

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