import React from 'react';
import { render, fireEvent } from '@testing-library/react';
import EdgeOrderBadge from '../EdgeOrderBadge';

describe('EdgeOrderBadge', () => {
  const defaultProps = {
    edgeId: 'edge-1',
    edgeOrderInfo: {
      pathX: 200,
      pathY: 150,
      order: 1,
      totalEdges: 3,
      sourceNodeId: 'node-1',
    },
    isLocked: false,
    onReorderEdge: jest.fn(),
  };

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

  const getBadgeElement = () => {
    return document.querySelector('.edge-order-number') as HTMLElement;
  };

  describe('rendering', () => {
    it('should render the order number', () => {
      render(<EdgeOrderBadge {...defaultProps} />);
      const badge = getBadgeElement();
      expect(badge).toBeTruthy();
      expect(badge.textContent).toBe('1');
    });

    it('should not render when pathX is undefined', () => {
      const { container } = render(
        <EdgeOrderBadge
          {...defaultProps}
          edgeOrderInfo={{ ...defaultProps.edgeOrderInfo, pathX: undefined }}
        />
      );
      expect(container.firstChild).toBeNull();
    });

    it('should not render when totalEdges is 1', () => {
      const { container } = render(
        <EdgeOrderBadge
          {...defaultProps}
          edgeOrderInfo={{ ...defaultProps.edgeOrderInfo, totalEdges: 1 }}
        />
      );
      expect(container.firstChild).toBeNull();
    });

    it('should be draggable when not locked', () => {
      render(<EdgeOrderBadge {...defaultProps} />);
      const badge = getBadgeElement();
      expect(badge.getAttribute('draggable')).toBe('true');
    });

    it('should not be draggable when locked', () => {
      render(<EdgeOrderBadge {...defaultProps} isLocked={true} />);
      const badge = getBadgeElement();
      expect(badge.getAttribute('draggable')).toBe('false');
    });
  });

  describe('drag and drop', () => {
    it('should stop propagation on mouseDown', () => {
      render(<EdgeOrderBadge {...defaultProps} />);
      const badge = getBadgeElement();
      const event = new MouseEvent('mousedown', { bubbles: true });
      const stopPropagation = jest.spyOn(event, 'stopPropagation');
      badge.dispatchEvent(event);
      expect(stopPropagation).toHaveBeenCalled();
    });

    it('should not handle dragStart when locked', () => {
      render(<EdgeOrderBadge {...defaultProps} isLocked={true} />);
      const badge = getBadgeElement();
      // Should not throw
      fireEvent.dragStart(badge);
    });

    it('should add visual feedback on dragOver', () => {
      render(<EdgeOrderBadge {...defaultProps} />);
      const badge = getBadgeElement();
      // Need to provide dataTransfer since the handler accesses it
      const dataTransfer = { dropEffect: '' };
      fireEvent.dragOver(badge, { dataTransfer } as any);
      // The drag-over class is added via event.currentTarget
      expect(badge.classList.contains('drag-over')).toBe(true);
    });

    it('should remove visual feedback on dragLeave', () => {
      render(<EdgeOrderBadge {...defaultProps} />);
      const badge = getBadgeElement();
      fireEvent.dragLeave(badge);
    });

    it('should not handle drop when locked', () => {
      render(<EdgeOrderBadge {...defaultProps} isLocked={true} />);
      const badge = getBadgeElement();
      fireEvent.drop(badge);
      expect(defaultProps.onReorderEdge).not.toHaveBeenCalled();
    });

    it('should silently handle empty drop data', () => {
      render(<EdgeOrderBadge {...defaultProps} />);
      const badge = getBadgeElement();

      // Simulate a drop with empty data - should not throw
      fireEvent.drop(badge, {
        dataTransfer: { getData: () => '' },
      });
      expect(defaultProps.onReorderEdge).not.toHaveBeenCalled();
    });

    it('should silently handle invalid JSON in drop data', () => {
      const debugSpy = jest.spyOn(console, 'debug').mockImplementation(() => {});
      render(<EdgeOrderBadge {...defaultProps} />);
      const badge = getBadgeElement();

      fireEvent.drop(badge, {
        dataTransfer: { getData: () => 'not-json' },
      });
      expect(defaultProps.onReorderEdge).not.toHaveBeenCalled();
      debugSpy.mockRestore();
    });
  });

  describe('positioning', () => {
    it('should position badge at pathX, pathY - 20', () => {
      render(<EdgeOrderBadge {...defaultProps} />);
      const badge = getBadgeElement();
      expect(badge.style.transform).toContain('200px');
      expect(badge.style.transform).toContain('130px'); // 150 - 20
    });

    it('should handle undefined pathY gracefully', () => {
      render(
        <EdgeOrderBadge
          {...defaultProps}
          edgeOrderInfo={{ ...defaultProps.edgeOrderInfo, pathY: undefined }}
        />
      );
      const badge = getBadgeElement();
      expect(badge.style.transform).toContain('-20px'); // 0 - 20
    });

    it('should have high z-index for visibility', () => {
      render(<EdgeOrderBadge {...defaultProps} />);
      const badge = getBadgeElement();
      // z-index is now applied via CSS class .edge-order-number
      expect(badge.classList.contains('edge-order-number')).toBe(true);
    });
  });

  describe('successful reorder', () => {
    it('should call onReorderEdge on valid drop with matching source node', () => {
      render(<EdgeOrderBadge {...defaultProps} />);
      const badge = getBadgeElement();
      const dropData = JSON.stringify({
        edgeId: 'other-edge',
        sourceNodeId: 'node-1',
        currentOrder: 2,
        totalEdges: 3,
      });
      fireEvent.drop(badge, {
        dataTransfer: { getData: () => dropData },
      });
      expect(defaultProps.onReorderEdge).toHaveBeenCalledWith('node-1', 2, 1);
    });

    it('should not call onReorderEdge when same order is dropped', () => {
      render(<EdgeOrderBadge {...defaultProps} />);
      const badge = getBadgeElement();
      const dropData = JSON.stringify({
        edgeId: 'other-edge',
        sourceNodeId: 'node-1',
        currentOrder: 1,
        totalEdges: 3,
      });
      fireEvent.drop(badge, {
        dataTransfer: { getData: () => dropData },
      });
      expect(defaultProps.onReorderEdge).not.toHaveBeenCalled();
    });

    it('should not call onReorderEdge when different source node', () => {
      render(<EdgeOrderBadge {...defaultProps} />);
      const badge = getBadgeElement();
      const dropData = JSON.stringify({
        edgeId: 'other-edge',
        sourceNodeId: 'different-node',
        currentOrder: 2,
        totalEdges: 3,
      });
      fireEvent.drop(badge, {
        dataTransfer: { getData: () => dropData },
      });
      expect(defaultProps.onReorderEdge).not.toHaveBeenCalled();
    });
  });

  describe('sourceNodeId fallback', () => {
    it('should use edgeId split as sourceNodeId fallback', () => {
      const props = {
        ...defaultProps,
        edgeId: 'nodeA_to_nodeB',
        edgeOrderInfo: {
          ...defaultProps.edgeOrderInfo,
          sourceNodeId: undefined,
        },
      };
      render(<EdgeOrderBadge {...props} />);
      const badge = getBadgeElement();
      const dropData = JSON.stringify({
        edgeId: 'other-edge',
        sourceNodeId: 'nodeA',
        currentOrder: 2,
        totalEdges: 3,
      });
      fireEvent.drop(badge, {
        dataTransfer: { getData: () => dropData },
      });
      expect(defaultProps.onReorderEdge).toHaveBeenCalledWith('nodeA', 2, 1);
    });
  });

  describe('dragStart visual feedback', () => {
    it('should set data transfer and visual feedback on dragStart', () => {
      render(<EdgeOrderBadge {...defaultProps} />);
      const badge = getBadgeElement();
      const mockDataTransfer = {
        setData: jest.fn(),
        effectAllowed: '',
        setDragImage: jest.fn(),
      };
      fireEvent.dragStart(badge, { dataTransfer: mockDataTransfer });
      expect(mockDataTransfer.setData).toHaveBeenCalledWith('edgeOrderReorder', expect.any(String));
      expect(mockDataTransfer.effectAllowed).toBe('move');
    });
  });

  describe('dragEnd cleanup', () => {
    it('should restore opacity and cursor on dragEnd', () => {
      render(<EdgeOrderBadge {...defaultProps} />);
      const badge = getBadgeElement();
      fireEvent.dragEnd(badge);
      // dragEnd restores styles
    });
  });

  describe('dragOver when locked', () => {
    it('should not set dropEffect when locked', () => {
      render(<EdgeOrderBadge {...defaultProps} isLocked={true} />);
      const badge = getBadgeElement();
      const dataTransfer = { dropEffect: '' };
      fireEvent.dragOver(badge, { dataTransfer } as any);
      // When locked, handler returns early
    });

    it('should not process drop when locked', () => {
      render(<EdgeOrderBadge {...defaultProps} isLocked={true} />);
      const badge = getBadgeElement();
      const dropData = JSON.stringify({
        edgeId: 'other-edge',
        sourceNodeId: 'node-1',
        currentOrder: 2,
        totalEdges: 3,
      });
      fireEvent.drop(badge, {
        dataTransfer: { getData: () => dropData },
      });
      expect(defaultProps.onReorderEdge).not.toHaveBeenCalled();
    });
  });

  describe('drop without onReorderEdge callback', () => {
    it('should handle drop gracefully when onReorderEdge is undefined', () => {
      const props = { ...defaultProps, onReorderEdge: undefined };
      render(<EdgeOrderBadge {...props} />);
      const badge = getBadgeElement();
      const dropData = JSON.stringify({
        edgeId: 'other-edge',
        sourceNodeId: 'node-1',
        currentOrder: 2,
        totalEdges: 3,
      });
      // Should not throw
      fireEvent.drop(badge, {
        dataTransfer: { getData: () => dropData },
      });
    });
  });
});
