/**
 * Unit tests for Portal component
 *
 * Tests the Portal component which uses ReactDOM.createPortal to render
 * children outside the parent DOM hierarchy. This is useful for modals,
 * tooltips, and other UI elements that need to escape parent stacking contexts.
 */

import { describe, it, expect, afterEach } from 'vitest';
import { render, screen, cleanup } from '@testing-library/react';
import Portal from '../Portal';

describe('Portal', () => {
  afterEach(() => {
    // Clean up any rendered components
    cleanup();
  });

  describe('Basic Rendering', () => {
    it('should render children', () => {
      render(
        <Portal>
          <div data-testid="portal-content">Portal Content</div>
        </Portal>
      );

      expect(screen.getByTestId('portal-content')).toBeInTheDocument();
      expect(screen.getByText('Portal Content')).toBeInTheDocument();
    });

    it('should render text content', () => {
      render(<Portal>Simple text content</Portal>);

      expect(screen.getByText('Simple text content')).toBeInTheDocument();
    });

    it('should render complex JSX children', () => {
      render(
        <Portal>
          <div>
            <h1>Title</h1>
            <p>Description</p>
            <button>Click me</button>
          </div>
        </Portal>
      );

      expect(screen.getByRole('heading', { name: 'Title' })).toBeInTheDocument();
      expect(screen.getByText('Description')).toBeInTheDocument();
      expect(screen.getByRole('button', { name: 'Click me' })).toBeInTheDocument();
    });
  });

  describe('Portal Behavior', () => {
    it('should render content to document.body', () => {
      const { container } = render(
        <div id="root">
          <Portal>
            <div data-testid="portal-content">Portal Content</div>
          </Portal>
        </div>
      );

      const portalContent = screen.getByTestId('portal-content');

      // Portal content should NOT be inside the container
      expect(container.contains(portalContent)).toBe(false);

      // Portal content SHOULD be inside document.body
      expect(document.body.contains(portalContent)).toBe(true);
    });

    it('should escape parent DOM hierarchy', () => {
      const { container } = render(
        <div id="parent" style={{ position: 'relative', zIndex: 1 }}>
          <div id="child">
            <Portal>
              <div data-testid="portal-content">Escaped Content</div>
            </Portal>
          </div>
        </div>
      );

      const portalContent = screen.getByTestId('portal-content');
      const parent = container.querySelector('#parent');

      // Portal content should not be inside parent
      expect(parent?.contains(portalContent)).toBe(false);

      // Portal content should be direct child of body
      expect(portalContent.parentElement).toBe(document.body);
    });
  });

  describe('Multiple Children', () => {
    it('should render multiple children', () => {
      render(
        <Portal>
          <div data-testid="child-1">First child</div>
          <div data-testid="child-2">Second child</div>
          <div data-testid="child-3">Third child</div>
        </Portal>
      );

      expect(screen.getByTestId('child-1')).toBeInTheDocument();
      expect(screen.getByTestId('child-2')).toBeInTheDocument();
      expect(screen.getByTestId('child-3')).toBeInTheDocument();
    });

    it('should render an array of children', () => {
      const items = ['Item 1', 'Item 2', 'Item 3'];

      render(
        <Portal>
          {items.map((item, index) => (
            <div key={index} data-testid={`item-${index}`}>
              {item}
            </div>
          ))}
        </Portal>
      );

      items.forEach((item, index) => {
        expect(screen.getByTestId(`item-${index}`)).toBeInTheDocument();
        expect(screen.getByText(item)).toBeInTheDocument();
      });
    });

    it('should render nested components', () => {
      const NestedComponent = () => (
        <div data-testid="nested">
          <span>Nested Component</span>
        </div>
      );

      render(
        <Portal>
          <NestedComponent />
          <div>Other content</div>
        </Portal>
      );

      expect(screen.getByTestId('nested')).toBeInTheDocument();
      expect(screen.getByText('Nested Component')).toBeInTheDocument();
      expect(screen.getByText('Other content')).toBeInTheDocument();
    });
  });

  describe('Mounting Behavior', () => {
    it('should not render before component is mounted', () => {
      // This test verifies the internal mounting state
      const { rerender } = render(
        <Portal>
          <div data-testid="portal-content">Content</div>
        </Portal>
      );

      // After initial render, content should be present
      expect(screen.getByTestId('portal-content')).toBeInTheDocument();

      // Re-render should still show content
      rerender(
        <Portal>
          <div data-testid="portal-content">Updated Content</div>
        </Portal>
      );

      expect(screen.getByText('Updated Content')).toBeInTheDocument();
    });
  });

  describe('Multiple Portals', () => {
    it('should support multiple portal instances', () => {
      render(
        <div>
          <Portal>
            <div data-testid="portal-1">Portal 1</div>
          </Portal>
          <Portal>
            <div data-testid="portal-2">Portal 2</div>
          </Portal>
          <Portal>
            <div data-testid="portal-3">Portal 3</div>
          </Portal>
        </div>
      );

      expect(screen.getByTestId('portal-1')).toBeInTheDocument();
      expect(screen.getByTestId('portal-2')).toBeInTheDocument();
      expect(screen.getByTestId('portal-3')).toBeInTheDocument();

      // All portals should be in document.body
      expect(document.body.contains(screen.getByTestId('portal-1'))).toBe(true);
      expect(document.body.contains(screen.getByTestId('portal-2'))).toBe(true);
      expect(document.body.contains(screen.getByTestId('portal-3'))).toBe(true);
    });

    it('should keep portals separate from each other', () => {
      render(
        <div>
          <Portal>
            <div data-testid="portal-1">
              <span data-testid="content-1">Content 1</span>
            </div>
          </Portal>
          <Portal>
            <div data-testid="portal-2">
              <span data-testid="content-2">Content 2</span>
            </div>
          </Portal>
        </div>
      );

      const portal1 = screen.getByTestId('portal-1');
      const portal2 = screen.getByTestId('portal-2');
      const content1 = screen.getByTestId('content-1');
      const content2 = screen.getByTestId('content-2');

      // Each portal should contain only its own content
      expect(portal1.contains(content1)).toBe(true);
      expect(portal1.contains(content2)).toBe(false);
      expect(portal2.contains(content2)).toBe(true);
      expect(portal2.contains(content1)).toBe(false);
    });
  });

  describe('Cleanup', () => {
    it('should remove content from body when unmounted', () => {
      const { unmount } = render(
        <Portal>
          <div data-testid="portal-content">Temporary Content</div>
        </Portal>
      );

      // Content should exist initially
      expect(screen.getByTestId('portal-content')).toBeInTheDocument();

      // Unmount the component
      unmount();

      // Content should be removed from DOM
      expect(screen.queryByTestId('portal-content')).not.toBeInTheDocument();
    });

    it('should clean up multiple portals on unmount', () => {
      const { unmount } = render(
        <div>
          <Portal>
            <div data-testid="portal-1">Portal 1</div>
          </Portal>
          <Portal>
            <div data-testid="portal-2">Portal 2</div>
          </Portal>
        </div>
      );

      expect(screen.getByTestId('portal-1')).toBeInTheDocument();
      expect(screen.getByTestId('portal-2')).toBeInTheDocument();

      unmount();

      expect(screen.queryByTestId('portal-1')).not.toBeInTheDocument();
      expect(screen.queryByTestId('portal-2')).not.toBeInTheDocument();
    });
  });

  describe('Re-rendering', () => {
    it('should update content on re-render', () => {
      const { rerender } = render(
        <Portal>
          <div data-testid="portal-content">Initial Content</div>
        </Portal>
      );

      expect(screen.getByText('Initial Content')).toBeInTheDocument();

      rerender(
        <Portal>
          <div data-testid="portal-content">Updated Content</div>
        </Portal>
      );

      expect(screen.getByText('Updated Content')).toBeInTheDocument();
      expect(screen.queryByText('Initial Content')).not.toBeInTheDocument();
    });

    it('should handle prop changes', () => {
      const TestComponent = ({ message }: { message: string }) => (
        <Portal>
          <div data-testid="message">{message}</div>
        </Portal>
      );

      const { rerender } = render(<TestComponent message="First message" />);

      expect(screen.getByText('First message')).toBeInTheDocument();

      rerender(<TestComponent message="Second message" />);

      expect(screen.getByText('Second message')).toBeInTheDocument();
      expect(screen.queryByText('First message')).not.toBeInTheDocument();
    });
  });

  describe('Edge Cases', () => {
    it('should handle empty children', () => {
      render(<Portal>{null}</Portal>);

      // Should not throw error
      expect(document.body).toBeInTheDocument();
    });

    it('should handle undefined children', () => {
      render(<Portal>{undefined}</Portal>);

      // Should not throw error
      expect(document.body).toBeInTheDocument();
    });

    it('should handle boolean children', () => {
      render(
        <Portal>
          {false && <div>Should not render</div>}
          {true && <div data-testid="should-render">Should render</div>}
        </Portal>
      );

      expect(screen.queryByText('Should not render')).not.toBeInTheDocument();
      expect(screen.getByTestId('should-render')).toBeInTheDocument();
    });

    it('should handle conditional rendering', () => {
      const { rerender } = render(
        <Portal>
          {false && <div data-testid="conditional">Conditional Content</div>}
        </Portal>
      );

      expect(screen.queryByTestId('conditional')).not.toBeInTheDocument();

      rerender(
        <Portal>
          {true && <div data-testid="conditional">Conditional Content</div>}
        </Portal>
      );

      expect(screen.getByTestId('conditional')).toBeInTheDocument();
    });
  });

  describe('Integration with Parent Components', () => {
    it('should work inside modals', () => {
      const Modal = ({ children }: { children: React.ReactNode }) => (
        <div className="modal" data-testid="modal">
          <Portal>{children}</Portal>
        </div>
      );

      const { container } = render(
        <Modal>
          <div data-testid="modal-content">Modal Content</div>
        </Modal>
      );

      const modalContent = screen.getByTestId('modal-content');
      const modal = container.querySelector('[data-testid="modal"]');

      // Modal content should not be inside modal container
      expect(modal?.contains(modalContent)).toBe(false);

      // Modal content should be in document.body
      expect(document.body.contains(modalContent)).toBe(true);
    });

    it('should preserve event handlers', () => {
      let clicked = false;
      const handleClick = () => {
        clicked = true;
      };

      render(
        <Portal>
          <button data-testid="button" onClick={handleClick}>
            Click me
          </button>
        </Portal>
      );

      const button = screen.getByTestId('button');
      button.click();

      expect(clicked).toBe(true);
    });

    it('should preserve CSS classes and styles', () => {
      render(
        <Portal>
          <div
            data-testid="styled-content"
            className="custom-class"
            style={{ color: 'red', fontSize: '16px' }}
          >
            Styled Content
          </div>
        </Portal>
      );

      const styledContent = screen.getByTestId('styled-content');

      expect(styledContent).toHaveClass('custom-class');
      // Check styles individually - color may be normalized to rgb()
      expect(styledContent.style.color).toBeTruthy();
      expect(styledContent.style.fontSize).toBe('16px');
    });
  });

  describe('Accessibility', () => {
    it('should maintain ARIA attributes', () => {
      render(
        <Portal>
          <div
            data-testid="aria-content"
            role="dialog"
            aria-label="Test Dialog"
            aria-describedby="description"
          >
            <div id="description">Dialog description</div>
          </div>
        </Portal>
      );

      const content = screen.getByTestId('aria-content');

      expect(content).toHaveAttribute('role', 'dialog');
      expect(content).toHaveAttribute('aria-label', 'Test Dialog');
      expect(content).toHaveAttribute('aria-describedby', 'description');
    });

    it('should support semantic HTML inside portal', () => {
      render(
        <Portal>
          <dialog open data-testid="dialog">
            <h2>Dialog Title</h2>
            <p>Dialog content</p>
          </dialog>
        </Portal>
      );

      expect(screen.getByTestId('dialog')).toBeInTheDocument();
      expect(screen.getByRole('heading', { name: 'Dialog Title' })).toBeInTheDocument();
    });
  });
});
