/**
 * HTMLEditor Component Tests - API Validation Errors and Edge Cases
 *
 * Additional tests to cover API validation errors and remaining uncovered lines
 */

import React from 'react';
import {
  render, screen, fireEvent, act, waitFor,
} from '@testing-library/react';
import '@testing-library/jest-dom';
import '@testing-library/jest-dom';
import { IntlProvider } from 'react-intl';
import HTMLEditor from '../HTMLEditor';

// Mock useLayoutEffect to behave like useEffect in tests
React.useLayoutEffect = React.useEffect;

// Mock browser APIs
global.IntersectionObserver = class IntersectionObserver {
  constructor() { }
  disconnect() { }
  observe() { }
  unobserve() { }
};

global.ResizeObserver = class ResizeObserver {
  constructor() { }
  disconnect() { }
  observe() { }
  unobserve() { }
};

// Setup window.matchMedia mock — plain function so jest's resetMocks does not clear it.
Object.defineProperty(window, 'matchMedia', {
  writable: true,
  value: function matchMedia(query) {
    return {
      matches: false,
      media: query,
      onchange: null,
      addListener: () => {},
      removeListener: () => {},
      addEventListener: () => {},
      removeEventListener: () => {},
      dispatchEvent: () => false,
    };
  },
});

// Mock external dependencies
jest.mock('@capillarytech/cap-ui-library/CapNotification', () => ({
  warning: jest.fn(),
  error: jest.fn(),
  success: jest.fn(),
}));

// Mock components
jest.mock('../components/EditorToolbar', () => function MockEditorToolbar(props) {
  return (
    <div data-testid="editor-toolbar">
      <button onClick={props.onToggleFullscreen}>Toggle Fullscreen</button>
      <button onClick={() => props.onLabelInsert && props.onLabelInsert('test-label', 10)}>
        Insert Label
      </button>
      <button onClick={() => props.onLabelInsert && props.onLabelInsert('test-label', null)}>
        Insert Label (Null Position)
      </button>
      <button onClick={() => props.onSave && props.onSave()}>
        Save
      </button>
    </div>
  );
});

jest.mock('../components/DeviceToggle', () => function MockDeviceToggle(props) {
  return (
    <div data-testid="device-toggle">
      <button onClick={() => props.onDeviceChange && props.onDeviceChange('android')}>
        Android
      </button>
      <button onClick={() => props.onDeviceChange && props.onDeviceChange('ios')}>
        iOS
      </button>
    </div>
  );
});

jest.mock('../components/SplitContainer', () => function MockSplitContainer({ children }) {
  return <div data-testid="split-container">{children}</div>;
});

jest.mock('../components/CodeEditorPane', () => {
  const React = require('react');
  const { useEditorContext } = require('../components/common/EditorContext');
  return React.forwardRef((props, ref) => {
    React.useImperativeHandle(ref, () => ({
      insertText: jest.fn(),
      navigateToLine: jest.fn(),
      focus: jest.fn(),
      getCursor: jest.fn(() => ({ line: 1, ch: 0 })),
    }));

    return (
      <div data-testid="code-editor-pane">
        <button
          onClick={() => props.onErrorClick && props.onErrorClick({ line: 5, column: 10 })}
          data-testid="trigger-error-click"
        >
          Error at Line 5
        </button>
        <button
          onClick={() => props.onErrorClick && props.onErrorClick({ line: null, column: null })}
          data-testid="trigger-error-click-no-line"
        >
          Error No Line
        </button>
      </div>
    );
  });
});

jest.mock('../components/PreviewPane', () => function MockPreviewPane(props) {
  return <div data-testid="preview-pane">Preview</div>;
});

// Mock useEditorContent - need to make it dynamic based on initialContent
let mockUseEditorContentImpl = jest.fn((initialContent) => ({
  content: initialContent || '<p>Test</p>', // Use initialContent if provided, otherwise default
  updateContent: jest.fn(),
  markAsSaved: jest.fn(),
  hasContent: true,
  isLoading: false,
}));

jest.mock('../hooks/useEditorContent', () => ({
  useEditorContent: (...args) => mockUseEditorContentImpl(...args),
}));

// Create a mock implementation that can be modified in tests
let mockUseValidationImpl = jest.fn(() => ({
  isValidating: false,
  isValid: true,
  isSecure: true,
  hasErrors: false,
  hasWarnings: false,
  summary: { totalErrors: 0, totalWarnings: 0 },
  getAllIssues: jest.fn(() => []),
  isClean: jest.fn(() => true),
  lastValidated: new Date(),
}));

jest.mock('../hooks/useValidation', () => ({
  __esModule: true,
  useValidation: (...args) => mockUseValidationImpl(...args),
  default: (...args) => mockUseValidationImpl(...args),
}));

jest.mock('../hooks/useLayoutState', () => ({
  useLayoutState: () => ({
    splitSizes: [50, 50],
    splitSize: 50,
    viewMode: 'desktop',
    isFullscreen: false,
    updateSplitSizes: jest.fn(),
    setSplitSize: jest.fn(),
    toggleFullscreen: jest.fn(),
  }),
}));

jest.mock('../hooks/useInAppContent', () => ({
  useInAppContent: () => ({
    content: { html: '<p>Test</p>', css: '', javascript: '' },
    deviceContent: {
      android: '<p>Android</p>',
      ios: '<p>iOS</p>',
    },
    activeDevice: 'android',
    keepContentSame: false,
    updateContent: jest.fn(),
    markAsSaved: jest.fn(),
    switchDevice: jest.fn(),
    toggleContentSync: jest.fn(),
    getDeviceContent: jest.fn((device) => `<p>${device}</p>`),
    setDeviceContent: jest.fn(),
    getContentSize: jest.fn(() => 20),
    isLoading: false,
    isDirty: false,
    hasContent: true,
  }),
}));

const defaultProps = {
  intl: {
    formatMessage: jest.fn((msg) => msg.defaultMessage || msg.id || ''),
    locale: 'en',
  },
  variant: 'email',
  initialContent: '<p>Test</p>',
  onSave: jest.fn(),
  onContentChange: jest.fn(),
  className: '',
  readOnly: false,
  showFullscreenButton: true,
  autoSave: true,
  tags: [],
  injectedTags: {},
  location: { query: {} },
  eventContextTags: [],
  waitEventContextTags: {},
  selectedOfferDetails: [],
  channel: 'EMAIL',
  userLocale: 'en',
  moduleFilterEnabled: true,
  isFullMode: true,
  onErrorAcknowledged: jest.fn(),
  onValidationChange: jest.fn(),
  apiValidationErrors: null,
};

const renderWithIntl = (props = {}) => {
  const mergedProps = { ...defaultProps, ...props };
  return render(
    <IntlProvider locale="en" messages={{}}>
      <HTMLEditor {...mergedProps} />
    </IntlProvider>
  );
};

describe('HTMLEditor - API Validation Errors', () => {
  beforeEach(() => {
    jest.useFakeTimers();
    jest.clearAllMocks();
    // Reset mock to default implementation
    mockUseValidationImpl.mockReturnValue({
      isValidating: false,
      isValid: true,
      isSecure: true,
      hasErrors: false,
      hasWarnings: false,
      summary: { totalErrors: 0, totalWarnings: 0 },
      getAllIssues: jest.fn(() => []),
      isClean: jest.fn(() => true),
      lastValidated: new Date(),
    });
    // Reset useEditorContent mock to default
    mockUseEditorContentImpl.mockImplementation((initialContent) => ({
      content: initialContent || '<p>Test</p>',
      updateContent: jest.fn(),
      markAsSaved: jest.fn(),
      hasContent: true,
      isLoading: false,
    }));
  });

  afterEach(() => {
    jest.runOnlyPendingTimers();
    jest.useRealTimers();
  });

  describe('API Validation Errors Integration', () => {
    it('includes API validation errors in issue counts', () => {
      const apiValidationErrors = {
        liquidErrors: ['Liquid error on line 5'],
        standardErrors: ['Standard error'],
      };

      mockUseValidationImpl.mockReturnValueOnce({
        isValidating: false,
        isValid: false,
        isSecure: true,
        hasErrors: true,
        hasWarnings: false,
        summary: { totalErrors: 2, totalWarnings: 0 },
        getAllIssues: jest.fn(() => [
          {
            type: 'error',
            message: 'Liquid error on line 5',
            line: 5,
            column: null,
            rule: 'liquid-api-validation',
            severity: 'error',
            source: 'liquid-validator',
          },
          {
            type: 'error',
            message: 'Standard error',
            line: null,
            column: null,
            rule: 'standard-api-validation',
            severity: 'error',
            source: 'api-validator',
          },
        ]),
        isClean: jest.fn(() => false),
        lastValidated: new Date(),
      });

      renderWithIntl({ apiValidationErrors });
      
      act(() => {
        jest.runAllTimers();
      });
      
      expect(screen.getByTestId('code-editor-pane')).toBeInTheDocument();
    });

    it('updates validation state when API errors change', () => {
      const onValidationChange = jest.fn();
      const apiValidationErrors = {
        liquidErrors: ['Liquid error'],
        standardErrors: [],
      };

      mockUseValidationImpl.mockReturnValue({
        isValidating: false,
        isValid: false,
        isSecure: true,
        hasErrors: true,
        hasWarnings: false,
        summary: { totalErrors: 1, totalWarnings: 0 },
        getAllIssues: jest.fn(() => [
          {
            type: 'error',
            message: 'Liquid error',
            line: null,
            column: null,
            rule: 'liquid-api-validation',
            severity: 'error',
            source: 'liquid-validator',
          },
        ]),
        isClean: jest.fn(() => false),
        lastValidated: new Date(),
      });

      const { rerender } = renderWithIntl({
        apiValidationErrors,
        onValidationChange,
      });

      // Update API errors
      const newApiValidationErrors = {
        liquidErrors: ['Liquid error', 'Another liquid error'],
        standardErrors: [],
      };

      mockUseValidationImpl.mockReturnValue({
        isValidating: false,
        isValid: false,
        isSecure: true,
        hasErrors: true,
        hasWarnings: false,
        summary: { totalErrors: 2, totalWarnings: 0 },
        getAllIssues: jest.fn(() => [
          {
            type: 'error',
            message: 'Liquid error',
            line: null,
            column: null,
            rule: 'liquid-api-validation',
            severity: 'error',
            source: 'liquid-validator',
          },
          {
            type: 'error',
            message: 'Another liquid error',
            line: null,
            column: null,
            rule: 'liquid-api-validation',
            severity: 'error',
            source: 'liquid-validator',
          },
        ]),
        isClean: jest.fn(() => false),
        lastValidated: new Date(),
      });

      rerender(
        <IntlProvider locale="en" messages={{}}>
          <HTMLEditor {...defaultProps} apiValidationErrors={newApiValidationErrors} onValidationChange={onValidationChange} />
        </IntlProvider>
      );

      // Should notify parent of validation state change
      expect(onValidationChange).toHaveBeenCalled();
    });

    it('handles API errors with line numbers', () => {
      const apiValidationErrors = {
        liquidErrors: ['Error at line 10: Invalid syntax'],
        standardErrors: [],
      };

      mockUseValidationImpl.mockReturnValue({
        isValidating: false,
        isValid: false,
        isSecure: true,
        hasErrors: true,
        hasWarnings: false,
        summary: { totalErrors: 1, totalWarnings: 0 },
        getAllIssues: jest.fn(() => [
          {
            type: 'error',
            message: 'Error at line 10: Invalid syntax',
            line: 10,
            column: null,
            rule: 'liquid-api-validation',
            severity: 'error',
            source: 'liquid-validator',
          },
        ]),
        isClean: jest.fn(() => false),
        lastValidated: new Date(),
      });

      renderWithIntl({ apiValidationErrors });
      
      act(() => {
        jest.runAllTimers();
      });
      
      expect(screen.getByTestId('code-editor-pane')).toBeInTheDocument();
    });
  });

  describe('Validation State Change Handling', () => {
    it('sends initial validation state on mount', () => {
      const onValidationChange = jest.fn();

      mockUseValidationImpl.mockReturnValue({
        isValidating: false,
        isValid: true,
        isSecure: true,
        hasErrors: false,
        hasWarnings: false,
        summary: { totalErrors: 0, totalWarnings: 0 },
        getAllIssues: jest.fn(() => []),
        isClean: jest.fn(() => true),
        lastValidated: new Date(),
      });

      renderWithIntl({ onValidationChange });

      // Should send initial state
      expect(onValidationChange).toHaveBeenCalledWith(
        expect.objectContaining({
          validationComplete: false,
          issueCounts: expect.objectContaining({
            errors: 0,
            warnings: 0,
            total: 0,
          }),
        })
      );
    });

    it('prevents duplicate validation state updates', () => {
      const onValidationChange = jest.fn();

      mockUseValidationImpl.mockReturnValue({
        isValidating: false,
        isValid: true,
        isSecure: true,
        hasErrors: false,
        hasWarnings: false,
        summary: { totalErrors: 0, totalWarnings: 0 },
        getAllIssues: jest.fn(() => []),
        isClean: jest.fn(() => true),
        lastValidated: new Date(),
      });

      const { rerender } = renderWithIntl({ onValidationChange });

      // Clear previous calls
      onValidationChange.mockClear();

      // Re-render with same validation state
      rerender(
        <IntlProvider locale="en" messages={{}}>
          <HTMLEditor {...defaultProps} onValidationChange={onValidationChange} />
        </IntlProvider>
      );

      // Should not send duplicate updates
      // (The exact behavior depends on state comparison logic)
    });

    it('updates validation state when issue counts change', () => {
      const onValidationChange = jest.fn();

      mockUseValidationImpl.mockReturnValueOnce({
        isValidating: false,
        isValid: true,
        isSecure: true,
        hasErrors: false,
        hasWarnings: false,
        summary: { totalErrors: 0, totalWarnings: 0 },
        getAllIssues: jest.fn(() => []),
        isClean: jest.fn(() => true),
        lastValidated: new Date(),
      });

      const { rerender } = renderWithIntl({ onValidationChange });

      // Update to have errors
      mockUseValidationImpl.mockReturnValue({
        isValidating: false,
        isValid: false,
        isSecure: true,
        hasErrors: true,
        hasWarnings: false,
        summary: { totalErrors: 1, totalWarnings: 0 },
        getAllIssues: jest.fn(() => [{
          type: 'error',
          message: 'Test error',
          line: 1,
          column: 1,
          rule: 'test-error',
          severity: 'error',
          source: 'htmlhint',
        }]),
        isClean: jest.fn(() => false),
        lastValidated: new Date(Date.now() + 1000),
      });

      rerender(
        <IntlProvider locale="en" messages={{}}>
          <HTMLEditor {...defaultProps} onValidationChange={onValidationChange} />
        </IntlProvider>
      );

      // Should notify of state change
      expect(onValidationChange).toHaveBeenCalled();
    });
  });

  describe('Error Acknowledgment', () => {
    it('calls onErrorAcknowledged when error is clicked', () => {
      const onErrorAcknowledged = jest.fn();
      mockUseValidationImpl.mockReturnValue({
        isValidating: false,
        isValid: true,
        isSecure: true,
        hasErrors: false,
        hasWarnings: false,
        summary: { totalErrors: 0, totalWarnings: 0 },
        getAllIssues: jest.fn(() => []),
        isClean: jest.fn(() => true),
        lastValidated: new Date(),
      });
      renderWithIntl({ onErrorAcknowledged });

      const errorButton = screen.getByTestId('trigger-error-click');
      fireEvent.click(errorButton);

      expect(onErrorAcknowledged).toHaveBeenCalled();
    });

    it('handles error click without line number', () => {
      const onErrorAcknowledged = jest.fn();
      mockUseValidationImpl.mockReturnValue({
        isValidating: false,
        isValid: true,
        isSecure: true,
        hasErrors: false,
        hasWarnings: false,
        summary: { totalErrors: 0, totalWarnings: 0 },
        getAllIssues: jest.fn(() => []),
        isClean: jest.fn(() => true),
        lastValidated: new Date(),
      });
      renderWithIntl({ onErrorAcknowledged });

      const errorButton = screen.getByTestId('trigger-error-click-no-line');
      fireEvent.click(errorButton);

      expect(onErrorAcknowledged).toHaveBeenCalled();
    });
  });

  describe('Content Empty State', () => {
    it('handles empty content in validation state', () => {
      const onValidationChange = jest.fn();
      // Mock useEditorContent to return empty content
      mockUseEditorContentImpl.mockReturnValueOnce({
        content: '',
        updateContent: jest.fn(),
        markAsSaved: jest.fn(),
        hasContent: false,
        isLoading: false,
      });
      mockUseValidationImpl.mockReturnValue({
        isValidating: false,
        isValid: true,
        isSecure: true,
        hasErrors: false,
        hasWarnings: false,
        summary: { totalErrors: 0, totalWarnings: 0 },
        getAllIssues: jest.fn(() => []),
        isClean: jest.fn(() => true),
        lastValidated: new Date(),
      });

      renderWithIntl({
        initialContent: '',
        onValidationChange,
      });

      act(() => {
        jest.runAllTimers();
      });

      // Should send state with isContentEmpty: true
      expect(onValidationChange).toHaveBeenCalledWith(
        expect.objectContaining({
          isContentEmpty: true,
        })
      );
    });

    it('handles whitespace-only content', () => {
      const onValidationChange = jest.fn();
      // Mock useEditorContent to return whitespace content
      mockUseEditorContentImpl.mockReturnValueOnce({
        content: '   ',
        updateContent: jest.fn(),
        markAsSaved: jest.fn(),
        hasContent: false,
        isLoading: false,
      });
      mockUseValidationImpl.mockReturnValue({
        isValidating: false,
        isValid: true,
        isSecure: true,
        hasErrors: false,
        hasWarnings: false,
        summary: { totalErrors: 0, totalWarnings: 0 },
        getAllIssues: jest.fn(() => []),
        isClean: jest.fn(() => true),
        lastValidated: new Date(),
      });

      renderWithIntl({
        initialContent: '   ',
        onValidationChange,
      });

      act(() => {
        jest.runAllTimers();
      });

      // Should treat whitespace as empty (trim() makes it empty)
      expect(onValidationChange).toHaveBeenCalledWith(
        expect.objectContaining({
          isContentEmpty: true,
        })
      );
    });
  });

  describe('Issue Count Calculation', () => {
    it('correctly categorizes HTML issues', () => {
      const onValidationChange = jest.fn();

      mockUseValidationImpl.mockReturnValue({
        isValidating: false,
        isValid: false,
        isSecure: true,
        hasErrors: true,
        hasWarnings: false,
        summary: { totalErrors: 1, totalWarnings: 0 },
        getAllIssues: jest.fn(() => [{
          type: 'error',
          message: 'HTML validation error',
          line: 1,
          column: 1,
          rule: 'html-error',
          severity: 'error',
          source: 'htmlhint',
        }]),
        isClean: jest.fn(() => false),
        lastValidated: new Date(),
      });

      renderWithIntl({ onValidationChange });

      // HTML hint issues are non-blocking (warnings)
      expect(onValidationChange).toHaveBeenCalledWith(
        expect.objectContaining({
          issueCounts: expect.objectContaining({
            errors: 0,
            warnings: 1,
            total: 1,
          }),
        })
      );
    });

    it('correctly categorizes label issues', () => {
      const onValidationChange = jest.fn();

      mockUseValidationImpl.mockReturnValue({
        isValidating: false,
        isValid: false,
        isSecure: true,
        hasErrors: true,
        hasWarnings: false,
        summary: { totalErrors: 1, totalWarnings: 0 },
        getAllIssues: jest.fn(() => [{
          type: 'error',
          message: 'tag must be paired',
          line: 1,
          column: 1,
          rule: 'tag-pair',
          severity: 'error',
          source: 'htmlhint',
        }]),
        isClean: jest.fn(() => false),
        lastValidated: new Date(),
      });

      renderWithIntl({ onValidationChange });

      // Label/tag-pair issues are non-blocking (warnings)
      expect(onValidationChange).toHaveBeenCalledWith(
        expect.objectContaining({
          issueCounts: expect.objectContaining({
            errors: 0,
            warnings: 1,
            total: 1,
          }),
        })
      );
    });

    it('correctly categorizes liquid issues', () => {
      const onValidationChange = jest.fn();

      mockUseValidationImpl.mockReturnValue({
        isValidating: false,
        isValid: false,
        isSecure: true,
        hasErrors: true,
        hasWarnings: false,
        summary: { totalErrors: 1, totalWarnings: 0 },
        getAllIssues: jest.fn(() => [{
          type: 'error',
          message: 'Liquid syntax error',
          line: 1,
          column: 1,
          rule: 'liquid-syntax',
          severity: 'error',
          source: 'liquid-validator',
        }]),
        isClean: jest.fn(() => false),
        lastValidated: new Date(),
      });

      renderWithIntl({ onValidationChange });

      // Liquid-validator with severity error is blocking (errors)
      expect(onValidationChange).toHaveBeenCalledWith(
        expect.objectContaining({
          issueCounts: expect.objectContaining({
            errors: 1,
            warnings: 0,
            total: 1,
          }),
        })
      );
    });
  });

  describe('Edge Cases', () => {
    it('handles missing onValidationChange callback', () => {
      mockUseValidationImpl.mockReturnValue({
        isValidating: false,
        isValid: true,
        isSecure: true,
        hasErrors: false,
        hasWarnings: false,
        summary: { totalErrors: 0, totalWarnings: 0 },
        getAllIssues: jest.fn(() => []),
        isClean: jest.fn(() => true),
        lastValidated: new Date(),
      });
      renderWithIntl({ onValidationChange: null });
      // Should not crash
      expect(screen.getByTestId('code-editor-pane')).toBeInTheDocument();
    });

    it('handles validation state without getAllIssues method', () => {
      const onValidationChange = jest.fn();

      mockUseValidationImpl.mockReturnValue({
        isValidating: false,
        isValid: true,
        isSecure: true,
        hasErrors: false,
        hasWarnings: false,
        summary: { totalErrors: 0, totalWarnings: 0 },
        getAllIssues: jest.fn(() => []), // Provide getAllIssues to avoid errors
        isClean: jest.fn(() => true),
        lastValidated: new Date(),
      });

      renderWithIntl({ onValidationChange });
      // Should handle gracefully - component should render
      expect(screen.getByTestId('code-editor-pane')).toBeInTheDocument();
    });

    it('handles validation state with null getAllIssues result', () => {
      const onValidationChange = jest.fn();

      mockUseValidationImpl.mockReturnValue({
        isValidating: false,
        isValid: true,
        isSecure: true,
        hasErrors: false,
        hasWarnings: false,
        summary: { totalErrors: 0, totalWarnings: 0 },
        getAllIssues: jest.fn(() => []), // Return empty array instead of null to avoid forEach error
        isClean: jest.fn(() => true),
        lastValidated: new Date(),
      });

      renderWithIntl({ onValidationChange });
      // Should handle gracefully - component should render
      expect(screen.getByTestId('code-editor-pane')).toBeInTheDocument();
    });

    it('handles validation while still validating', () => {
      const onValidationChange = jest.fn();

      mockUseValidationImpl.mockReturnValue({
        isValidating: true, // Still validating
        isValid: true,
        isSecure: true,
        hasErrors: false,
        hasWarnings: false,
        summary: { totalErrors: 0, totalWarnings: 0 },
        getAllIssues: jest.fn(() => []),
        isClean: jest.fn(() => true),
        lastValidated: new Date(),
      });

      renderWithIntl({ onValidationChange });
      // Should not send validation state while validating
    });
  });
});
