/**
 * Tests for ErrorBoundary component
 */

import React from 'react';
import { render, fireEvent } from '@testing-library/react-native';
import { Text, View } from 'react-native';
import { ErrorBoundary, withErrorBoundary } from '../ErrorBoundary';

// Component that throws an error
function ThrowingComponent({ shouldThrow = true }: { shouldThrow?: boolean }) {
  if (shouldThrow) {
    throw new Error('Test error');
  }
  return <Text>Content rendered</Text>;
}

// Suppress console errors during tests
const originalConsoleError = console.error;
beforeAll(() => {
  console.error = jest.fn();
});
afterAll(() => {
  console.error = originalConsoleError;
});

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

  it('should render children when no error occurs', () => {
    const { getByText } = render(
      <ErrorBoundary>
        <Text>Hello World</Text>
      </ErrorBoundary>
    );

    expect(getByText('Hello World')).toBeTruthy();
  });

  it('should render fallback UI when error occurs', () => {
    const { getByText } = render(
      <ErrorBoundary>
        <ThrowingComponent />
      </ErrorBoundary>
    );

    expect(getByText('Oops! Something went wrong')).toBeTruthy();
    expect(getByText(/The app encountered an unexpected error/)).toBeTruthy();
  });

  it('should render custom fallback when provided', () => {
    const { getByText } = render(
      <ErrorBoundary fallback={<Text>Custom Error UI</Text>}>
        <ThrowingComponent />
      </ErrorBoundary>
    );

    expect(getByText('Custom Error UI')).toBeTruthy();
  });

  it('should call onError callback when error occurs', () => {
    const onError = jest.fn();

    render(
      <ErrorBoundary onError={onError}>
        <ThrowingComponent />
      </ErrorBoundary>
    );

    expect(onError).toHaveBeenCalledTimes(1);
    expect(onError).toHaveBeenCalledWith(
      expect.any(Error),
      expect.objectContaining({
        componentStack: expect.any(String),
      })
    );
  });

  it('should provide retry functionality', () => {
    const onRetry = jest.fn();

    const { getByText } = render(
      <ErrorBoundary onRetry={onRetry}>
        <ThrowingComponent />
      </ErrorBoundary>
    );

    const tryAgainButton = getByText('Try Again');
    fireEvent.press(tryAgainButton);

    expect(onRetry).toHaveBeenCalledTimes(1);
  });

  it('should reset error state when retry is pressed', () => {
    let shouldThrow = true;

    const TestComponent = () => {
      if (shouldThrow) {
        throw new Error('Test');
      }
      return <Text>Recovered</Text>;
    };

    const { getByText, queryByText, rerender } = render(
      <ErrorBoundary onRetry={() => { shouldThrow = false; }}>
        <TestComponent />
      </ErrorBoundary>
    );

    // Error is shown
    expect(getByText('Oops! Something went wrong')).toBeTruthy();

    // Press retry
    fireEvent.press(getByText('Try Again'));

    // Re-render to see if it recovers
    rerender(
      <ErrorBoundary>
        <TestComponent />
      </ErrorBoundary>
    );

    expect(getByText('Recovered')).toBeTruthy();
    expect(queryByText('Oops! Something went wrong')).toBeNull();
  });

  it('should reset error state when resetKey changes', () => {
    const { rerender, getByText, queryByText } = render(
      <ErrorBoundary resetKey="key1">
        <ThrowingComponent shouldThrow={true} />
      </ErrorBoundary>
    );

    expect(getByText('Oops! Something went wrong')).toBeTruthy();

    // Change resetKey and provide non-throwing child
    rerender(
      <ErrorBoundary resetKey="key2">
        <ThrowingComponent shouldThrow={false} />
      </ErrorBoundary>
    );

    expect(getByText('Content rendered')).toBeTruthy();
    expect(queryByText('Oops! Something went wrong')).toBeNull();
  });

  it('should show error details in development mode', () => {
    const { getByText, queryByText } = render(
      <ErrorBoundary showDetails={true}>
        <ThrowingComponent />
      </ErrorBoundary>
    );

    // Details toggle should be present
    expect(getByText(/Error Details/)).toBeTruthy();

    // Click to expand
    fireEvent.press(getByText(/Error Details/));

    // Error message should be visible
    expect(getByText('Test error')).toBeTruthy();
  });

  it('should hide error details when showDetails is false', () => {
    const { queryByText } = render(
      <ErrorBoundary showDetails={false}>
        <ThrowingComponent />
      </ErrorBoundary>
    );

    expect(queryByText(/Error Details/)).toBeNull();
  });
});

describe('withErrorBoundary HOC', () => {
  it('should wrap component with error boundary', () => {
    const MyComponent = () => <Text>My Component</Text>;
    const WrappedComponent = withErrorBoundary(MyComponent);

    const { getByText } = render(<WrappedComponent />);

    expect(getByText('My Component')).toBeTruthy();
  });

  it('should catch errors from wrapped component', () => {
    const onError = jest.fn();
    const WrappedComponent = withErrorBoundary(ThrowingComponent, { onError });

    const { getByText } = render(<WrappedComponent />);

    expect(getByText('Oops! Something went wrong')).toBeTruthy();
    expect(onError).toHaveBeenCalled();
  });

  it('should pass props to wrapped component', () => {
    interface Props {
      message: string;
    }

    const MyComponent = ({ message }: Props) => <Text>{message}</Text>;
    const WrappedComponent = withErrorBoundary(MyComponent);

    const { getByText } = render(<WrappedComponent message="Hello Props" />);

    expect(getByText('Hello Props')).toBeTruthy();
  });

  it('should set correct displayName', () => {
    const MyComponent = () => <Text>Test</Text>;
    MyComponent.displayName = 'MyComponent';

    const WrappedComponent = withErrorBoundary(MyComponent);

    expect(WrappedComponent.displayName).toBe('withErrorBoundary(MyComponent)');
  });
});
