/**
 * Tests for CustomValuesEditor component.
 * Covers: loading state, values-missing warning, JSON view, tag table, discard/update buttons.
 * @jest-environment jsdom
 */

import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import { IntlProvider } from 'react-intl';
import PropTypes from 'prop-types';

jest.mock('@capillarytech/cap-ui-library/CapRow', () => ({ children, className }) => (
  <div className={className}>{children}</div>
));
jest.mock('@capillarytech/cap-ui-library/CapSpin', () => ({ size }) => (
  <div data-testid="cap-spin" data-size={size}>Loading...</div>
));
jest.mock('@capillarytech/cap-ui-library/CapSwitch', () => ({ checked, onChange }) => (
  <button
    type="button"
    data-testid="cap-switch"
    aria-checked={String(checked)}
    onClick={() => onChange(!checked)}
  >
    Toggle
  </button>
));
jest.mock('@capillarytech/cap-ui-library/CapButton', () => ({ children, onClick, disabled, loading, type: btnType, icon }) => (
  <button
    type="button"
    data-testid="cap-button"
    data-btntype={btnType}
    data-icon={icon}
    onClick={onClick}
    disabled={!!(disabled || loading)}
  >
    {children}
  </button>
));
jest.mock('@capillarytech/cap-ui-library/CapInput', () => ({ value, onChange, placeholder, isRequired, className }) => (
  <input
    data-testid="cap-input"
    className={className}
    value={value || ''}
    onChange={onChange}
    placeholder={placeholder}
    required={!!isRequired}
  />
));
jest.mock('@capillarytech/cap-ui-library/CapLabel', () => ({ children, type, className }) => (
  <span data-testid="cap-label" data-type={type} className={className}>{children}</span>
));

import CustomValuesEditor from '../CustomValuesEditor';

const defaultProps = {
  isExtractingTags: false,
  isUpdatePreviewDisabled: false,
  showJSON: false,
  setShowJSON: jest.fn(),
  customValues: { '{{customer.name}}': 'Alice', '{{customer.email}}': '' },
  handleJSONTextChange: jest.fn(),
  extractedTags: [
    { fullPath: '{{customer.name}}' },
    { fullPath: '{{customer.email}}' },
  ],
  requiredTags: [{ fullPath: '{{customer.name}}' }],
  optionalTags: [{ fullPath: '{{customer.email}}' }],
  handleCustomValueChange: jest.fn(),
  handleDiscardCustomValues: jest.fn(),
  handleUpdatePreview: jest.fn(),
  isUpdatingPreview: false,
  formatMessage: jest.fn((msg) => msg.defaultMessage || msg.id),
};

// IntlProvider wrapper for incoming tests
const scope = 'app.v2Components.TestAndPreviewSlidebox';
const mockMessages = {
  [`${scope}.extractingTags`]: 'Extracting tags...',
  [`${scope}.valuesMissing`]: 'Values missing for some of the personalization tags',
  [`${scope}.showJSON`]: 'Show JSON',
  [`${scope}.personalizationTags`]: 'Personalization Tags',
  [`${scope}.customValues`]: 'Custom Values',
  [`${scope}.enterValue`]: 'Enter value',
  [`${scope}.discardCustomValues`]: 'Discard custom values',
  [`${scope}.updatePreview`]: 'Update Preview',
};

const TestWrapper = ({ children }) => (
  <IntlProvider locale="en" messages={mockMessages}>
    {children}
  </IntlProvider>
);

TestWrapper.propTypes = {
  children: PropTypes.node.isRequired,
};

describe('CustomValuesEditor', () => {
  beforeEach(() => jest.clearAllMocks());

  describe('loading state (isExtractingTags)', () => {
    it('renders a spinner and extracting-tags message when isExtractingTags is true', () => {
      render(
        <TestWrapper>
          <CustomValuesEditor {...defaultProps} isExtractingTags />
        </TestWrapper>
      );
      expect(screen.getByTestId('cap-spin')).toBeTruthy();
      expect(screen.getByText('Extracting tags...')).toBeTruthy();
    });

    it('does not render the editor toggle when extracting tags', () => {
      render(
        <TestWrapper>
          <CustomValuesEditor {...defaultProps} isExtractingTags />
        </TestWrapper>
      );
      expect(screen.queryByTestId('cap-switch')).toBeNull();
      expect(screen.queryAllByTestId('cap-button')).toHaveLength(0);
    });

    it('renders the editor (not spinner) when isExtractingTags is false', () => {
      render(
        <TestWrapper>
          <CustomValuesEditor {...defaultProps} isExtractingTags={false} />
        </TestWrapper>
      );
      expect(screen.queryByTestId('cap-spin')).toBeNull();
      expect(screen.getByTestId('cap-switch')).toBeTruthy();
    });
  });

  describe('values missing warning', () => {
    it('shows values-missing label when isUpdatePreviewDisabled is true', () => {
      render(
        <TestWrapper>
          <CustomValuesEditor {...defaultProps} isUpdatePreviewDisabled />
        </TestWrapper>
      );
      expect(screen.getByText('Values missing for some of the personalization tags')).toBeTruthy();
    });

    it('does not show values-missing label when isUpdatePreviewDisabled is false', () => {
      render(
        <TestWrapper>
          <CustomValuesEditor {...defaultProps} isUpdatePreviewDisabled={false} />
        </TestWrapper>
      );
      expect(
        screen.queryByText('Values missing for some of the personalization tags')
      ).toBeNull();
    });
  });

  describe('JSON view (showJSON = true)', () => {
    it('renders a textarea with the formatted JSON', () => {
      const customValues = { name: 'Alice', age: 30 };
      render(
        <TestWrapper>
          <CustomValuesEditor {...defaultProps} showJSON customValues={customValues} />
        </TestWrapper>
      );
      const textarea = document.querySelector('textarea.json-textarea');
      expect(textarea).toBeTruthy();
      expect(textarea.value).toBe(JSON.stringify(customValues, null, 2));
    });

    it('renders line numbers matching the number of JSON lines', () => {
      const customValues = { a: 1, b: 2 };
      render(
        <TestWrapper>
          <CustomValuesEditor {...defaultProps} showJSON customValues={customValues} />
        </TestWrapper>
      );
      const lineCount = JSON.stringify(customValues, null, 2).split('\n').length;
      const lineNumbers = document.querySelectorAll('.line-number');
      expect(lineNumbers).toHaveLength(lineCount);
    });

    it('calls handleJSONTextChange when the textarea value changes', () => {
      render(
        <TestWrapper>
          <CustomValuesEditor {...defaultProps} showJSON />
        </TestWrapper>
      );
      const textarea = document.querySelector('textarea.json-textarea');
      fireEvent.change(textarea, { target: { value: '{"key": "val"}' } });
      expect(defaultProps.handleJSONTextChange).toHaveBeenCalledWith('{"key": "val"}');
    });

    it('does not render the tag table in JSON view', () => {
      render(
        <TestWrapper>
          <CustomValuesEditor {...defaultProps} showJSON />
        </TestWrapper>
      );
      expect(screen.queryByText('Personalization Tags')).toBeNull();
    });
  });

  describe('tag table view (showJSON = false)', () => {
    it('renders Personalization Tags and Custom Values headers when extractedTags is non-empty', () => {
      render(
        <TestWrapper>
          <CustomValuesEditor {...defaultProps} showJSON={false} />
        </TestWrapper>
      );
      expect(screen.getByText('Personalization Tags')).toBeTruthy();
      expect(screen.getByText('Custom Values')).toBeTruthy();
    });

    it('renders required tags with a required indicator (*)', () => {
      render(
        <TestWrapper>
          <CustomValuesEditor {...defaultProps} showJSON={false} />
        </TestWrapper>
      );
      expect(screen.getByText('{{customer.name}}')).toBeTruthy();
      expect(screen.getByText('*')).toBeTruthy();
    });

    it('renders optional tags without a required indicator', () => {
      render(
        <TestWrapper>
          <CustomValuesEditor {...defaultProps} showJSON={false} />
        </TestWrapper>
      );
      expect(screen.getByText('{{customer.email}}')).toBeTruthy();
    });

    it('does not render the table when extractedTags is empty', () => {
      render(
        <TestWrapper>
          <CustomValuesEditor
            {...defaultProps}
            showJSON={false}
            extractedTags={[]}
            requiredTags={[]}
            optionalTags={[]}
          />
        </TestWrapper>
      );
      expect(screen.queryByText('Personalization Tags')).toBeNull();
    });

    it('calls handleCustomValueChange with the tag path and new value for required tags', () => {
      render(
        <TestWrapper>
          <CustomValuesEditor {...defaultProps} showJSON={false} />
        </TestWrapper>
      );
      const inputs = screen.getAllByTestId('cap-input');
      fireEvent.change(inputs[0], { target: { value: 'Bob' } });
      expect(defaultProps.handleCustomValueChange).toHaveBeenCalledWith('{{customer.name}}', 'Bob');
    });

    it('calls handleCustomValueChange with the tag path and new value for optional tags', () => {
      render(
        <TestWrapper>
          <CustomValuesEditor {...defaultProps} showJSON={false} />
        </TestWrapper>
      );
      const inputs = screen.getAllByTestId('cap-input');
      fireEvent.change(inputs[1], { target: { value: 'bob@example.com' } });
      expect(defaultProps.handleCustomValueChange).toHaveBeenCalledWith(
        '{{customer.email}}',
        'bob@example.com'
      );
    });
  });

  describe('JSON / tag toggle', () => {
    it('calls setShowJSON(true) when toggle is clicked from tag view', () => {
      render(
        <TestWrapper>
          <CustomValuesEditor {...defaultProps} showJSON={false} />
        </TestWrapper>
      );
      fireEvent.click(screen.getByTestId('cap-switch'));
      expect(defaultProps.setShowJSON).toHaveBeenCalledWith(true);
    });

    it('calls setShowJSON(false) when toggle is clicked from JSON view', () => {
      render(
        <TestWrapper>
          <CustomValuesEditor {...defaultProps} showJSON />
        </TestWrapper>
      );
      fireEvent.click(screen.getByTestId('cap-switch'));
      expect(defaultProps.setShowJSON).toHaveBeenCalledWith(false);
    });
  });

  describe('action buttons', () => {
    it('calls handleDiscardCustomValues when the discard button is clicked', () => {
      render(
        <TestWrapper>
          <CustomValuesEditor {...defaultProps} />
        </TestWrapper>
      );
      const buttons = screen.getAllByTestId('cap-button');
      fireEvent.click(buttons[0]); // discard is first
      expect(defaultProps.handleDiscardCustomValues).toHaveBeenCalled();
    });

    it('calls handleUpdatePreview when the update-preview button is clicked', () => {
      render(
        <TestWrapper>
          <CustomValuesEditor {...defaultProps} />
        </TestWrapper>
      );
      const buttons = screen.getAllByTestId('cap-button');
      fireEvent.click(buttons[1]); // update is second
      expect(defaultProps.handleUpdatePreview).toHaveBeenCalled();
    });

    it('disables the update-preview button when isUpdatePreviewDisabled is true', () => {
      render(
        <TestWrapper>
          <CustomValuesEditor {...defaultProps} isUpdatePreviewDisabled />
        </TestWrapper>
      );
      const buttons = screen.getAllByTestId('cap-button');
      expect(buttons[1].disabled).toBe(true);
    });

    it('disables the update-preview button when isUpdatingPreview is true', () => {
      render(
        <TestWrapper>
          <CustomValuesEditor {...defaultProps} isUpdatingPreview />
        </TestWrapper>
      );
      const buttons = screen.getAllByTestId('cap-button');
      expect(buttons[1].disabled).toBe(true);
    });

    it('enables the update-preview button when neither flag is set', () => {
      render(
        <TestWrapper>
          <CustomValuesEditor
            {...defaultProps}
            isUpdatePreviewDisabled={false}
            isUpdatingPreview={false}
          />
        </TestWrapper>
      );
      const buttons = screen.getAllByTestId('cap-button');
      expect(buttons[1].disabled).toBe(false);
    });
  });
});
