import { describe, it, expect } from 'vitest';
import { calculateLayout, Event } from '../layoutAlgorithm';

describe('calculateLayout', () => {
  describe('basic cases', () => {
    it('should return empty array for empty input', () => {
      const result = calculateLayout([]);
      expect(result).toEqual([]);
    });

    it('should assign laneIndex 0 to a single event', () => {
      const events: Event[] = [
        {
          id: 1,
          resourceId: 1,
          title: 'Event 1',
          serviceName: 'Service A',
          start: new Date('2025-01-01T10:00:00'),
          end: new Date('2025-01-01T11:00:00'),
        },
      ];

      const result = calculateLayout(events);

      expect(result).toHaveLength(1);
      expect(result[0].laneIndex).toBe(0);
    });

    it('should preserve all event properties', () => {
      const events: Event[] = [
        {
          id: 1,
          resourceId: 2,
          title: 'Haircut',
          serviceName: 'Basic Haircut',
          start: new Date('2025-01-01T10:00:00'),
          end: new Date('2025-01-01T11:00:00'),
          status: 'CONFIRMED',
          isPaid: true,
        },
      ];

      const result = calculateLayout(events);

      expect(result[0]).toMatchObject({
        id: 1,
        resourceId: 2,
        title: 'Haircut',
        serviceName: 'Basic Haircut',
        status: 'CONFIRMED',
        isPaid: true,
      });
      expect(result[0].start).toEqual(events[0].start);
      expect(result[0].end).toEqual(events[0].end);
    });
  });

  describe('non-overlapping events', () => {
    it('should assign laneIndex 0 to all non-overlapping sequential events', () => {
      const events: Event[] = [
        {
          id: 1,
          resourceId: 1,
          title: 'Event 1',
          start: new Date('2025-01-01T09:00:00'),
          end: new Date('2025-01-01T10:00:00'),
        },
        {
          id: 2,
          resourceId: 1,
          title: 'Event 2',
          start: new Date('2025-01-01T10:00:00'),
          end: new Date('2025-01-01T11:00:00'),
        },
        {
          id: 3,
          resourceId: 1,
          title: 'Event 3',
          start: new Date('2025-01-01T11:00:00'),
          end: new Date('2025-01-01T12:00:00'),
        },
      ];

      const result = calculateLayout(events);

      expect(result).toHaveLength(3);
      expect(result[0].laneIndex).toBe(0);
      expect(result[1].laneIndex).toBe(0);
      expect(result[2].laneIndex).toBe(0);
    });

    it('should assign laneIndex 0 to non-overlapping events with gaps', () => {
      const events: Event[] = [
        {
          id: 1,
          resourceId: 1,
          title: 'Event 1',
          start: new Date('2025-01-01T09:00:00'),
          end: new Date('2025-01-01T10:00:00'),
        },
        {
          id: 2,
          resourceId: 1,
          title: 'Event 2',
          start: new Date('2025-01-01T11:00:00'),
          end: new Date('2025-01-01T12:00:00'),
        },
        {
          id: 3,
          resourceId: 1,
          title: 'Event 3',
          start: new Date('2025-01-01T14:00:00'),
          end: new Date('2025-01-01T15:00:00'),
        },
      ];

      const result = calculateLayout(events);

      expect(result.every(event => event.laneIndex === 0)).toBe(true);
    });
  });

  describe('edge case: events ending when another starts', () => {
    it('should assign same lane when event ends exactly when another starts', () => {
      const events: Event[] = [
        {
          id: 1,
          resourceId: 1,
          title: 'Event 1',
          start: new Date('2025-01-01T10:00:00'),
          end: new Date('2025-01-01T11:00:00'),
        },
        {
          id: 2,
          resourceId: 1,
          title: 'Event 2',
          start: new Date('2025-01-01T11:00:00'),
          end: new Date('2025-01-01T12:00:00'),
        },
      ];

      const result = calculateLayout(events);

      expect(result[0].laneIndex).toBe(0);
      expect(result[1].laneIndex).toBe(0); // Can reuse lane since end == start
    });

    it('should handle millisecond precision for exact boundaries', () => {
      const events: Event[] = [
        {
          id: 1,
          resourceId: 1,
          title: 'Event 1',
          start: new Date('2025-01-01T10:00:00.000Z'),
          end: new Date('2025-01-01T11:00:00.000Z'),
        },
        {
          id: 2,
          resourceId: 1,
          title: 'Event 2',
          start: new Date('2025-01-01T11:00:00.000Z'),
          end: new Date('2025-01-01T12:00:00.000Z'),
        },
      ];

      const result = calculateLayout(events);

      expect(result[0].laneIndex).toBe(0);
      expect(result[1].laneIndex).toBe(0);
    });

    it('should assign different lane when event starts one millisecond before another ends', () => {
      const events: Event[] = [
        {
          id: 1,
          resourceId: 1,
          title: 'Event 1',
          start: new Date('2025-01-01T10:00:00.000Z'),
          end: new Date('2025-01-01T11:00:00.000Z'),
        },
        {
          id: 2,
          resourceId: 1,
          title: 'Event 2',
          start: new Date('2025-01-01T10:59:59.999Z'),
          end: new Date('2025-01-01T12:00:00.000Z'),
        },
      ];

      const result = calculateLayout(events);

      expect(result[0].laneIndex).toBe(0);
      expect(result[1].laneIndex).toBe(1); // Overlaps by 1ms
    });
  });

  describe('two overlapping events', () => {
    it('should assign different lanes to overlapping events', () => {
      const events: Event[] = [
        {
          id: 1,
          resourceId: 1,
          title: 'Event 1',
          start: new Date('2025-01-01T10:00:00'),
          end: new Date('2025-01-01T11:00:00'),
        },
        {
          id: 2,
          resourceId: 1,
          title: 'Event 2',
          start: new Date('2025-01-01T10:30:00'),
          end: new Date('2025-01-01T11:30:00'),
        },
      ];

      const result = calculateLayout(events);

      expect(result[0].laneIndex).toBe(0);
      expect(result[1].laneIndex).toBe(1);
    });

    it('should handle events where one completely contains another', () => {
      const events: Event[] = [
        {
          id: 1,
          resourceId: 1,
          title: 'Long Event',
          start: new Date('2025-01-01T09:00:00'),
          end: new Date('2025-01-01T13:00:00'),
        },
        {
          id: 2,
          resourceId: 1,
          title: 'Short Event',
          start: new Date('2025-01-01T10:00:00'),
          end: new Date('2025-01-01T11:00:00'),
        },
      ];

      const result = calculateLayout(events);

      expect(result[0].laneIndex).toBe(0);
      expect(result[1].laneIndex).toBe(1);
    });

    it('should handle events starting at the same time', () => {
      const events: Event[] = [
        {
          id: 1,
          resourceId: 1,
          title: 'Event 1',
          start: new Date('2025-01-01T10:00:00'),
          end: new Date('2025-01-01T11:00:00'),
        },
        {
          id: 2,
          resourceId: 1,
          title: 'Event 2',
          start: new Date('2025-01-01T10:00:00'),
          end: new Date('2025-01-01T12:00:00'),
        },
      ];

      const result = calculateLayout(events);

      expect(result[0].laneIndex).toBe(0);
      expect(result[1].laneIndex).toBe(1);
    });
  });

  describe('multiple overlapping events', () => {
    it('should handle three overlapping events requiring three lanes', () => {
      const events: Event[] = [
        {
          id: 1,
          resourceId: 1,
          title: 'Event 1',
          start: new Date('2025-01-01T10:00:00'),
          end: new Date('2025-01-01T12:00:00'),
        },
        {
          id: 2,
          resourceId: 1,
          title: 'Event 2',
          start: new Date('2025-01-01T10:30:00'),
          end: new Date('2025-01-01T12:30:00'),
        },
        {
          id: 3,
          resourceId: 1,
          title: 'Event 3',
          start: new Date('2025-01-01T11:00:00'),
          end: new Date('2025-01-01T13:00:00'),
        },
      ];

      const result = calculateLayout(events);

      expect(result[0].laneIndex).toBe(0);
      expect(result[1].laneIndex).toBe(1);
      expect(result[2].laneIndex).toBe(2);
    });

    it('should efficiently reuse lanes when earlier events end', () => {
      const events: Event[] = [
        {
          id: 1,
          resourceId: 1,
          title: 'Event 1',
          start: new Date('2025-01-01T09:00:00'),
          end: new Date('2025-01-01T10:00:00'),
        },
        {
          id: 2,
          resourceId: 1,
          title: 'Event 2',
          start: new Date('2025-01-01T09:30:00'),
          end: new Date('2025-01-01T11:00:00'),
        },
        {
          id: 3,
          resourceId: 1,
          title: 'Event 3',
          start: new Date('2025-01-01T10:00:00'),
          end: new Date('2025-01-01T12:00:00'),
        },
      ];

      const result = calculateLayout(events);

      expect(result[0].laneIndex).toBe(0);
      expect(result[1].laneIndex).toBe(1);
      expect(result[2].laneIndex).toBe(0); // Can reuse lane 0 since event 1 ended
    });

    it('should handle complex pattern with five overlapping events', () => {
      const events: Event[] = [
        {
          id: 1,
          resourceId: 1,
          title: 'Event 1',
          start: new Date('2025-01-01T09:00:00'),
          end: new Date('2025-01-01T15:00:00'),
        },
        {
          id: 2,
          resourceId: 1,
          title: 'Event 2',
          start: new Date('2025-01-01T10:00:00'),
          end: new Date('2025-01-01T11:00:00'),
        },
        {
          id: 3,
          resourceId: 1,
          title: 'Event 3',
          start: new Date('2025-01-01T10:30:00'),
          end: new Date('2025-01-01T12:00:00'),
        },
        {
          id: 4,
          resourceId: 1,
          title: 'Event 4',
          start: new Date('2025-01-01T11:00:00'),
          end: new Date('2025-01-01T13:00:00'),
        },
        {
          id: 5,
          resourceId: 1,
          title: 'Event 5',
          start: new Date('2025-01-01T11:30:00'),
          end: new Date('2025-01-01T14:00:00'),
        },
      ];

      const result = calculateLayout(events);

      // Event 1 (9-15): lane 0
      // Event 2 (10-11): lane 1
      // Event 3 (10:30-12): lane 2 (overlaps with 1 and 2)
      // Event 4 (11-13): lane 1 (can reuse, event 2 ended)
      // Event 5 (11:30-14): lane 3 (overlaps with 1, 3, and 4)

      expect(result[0].laneIndex).toBe(0);
      expect(result[1].laneIndex).toBe(1);
      expect(result[2].laneIndex).toBe(2);
      expect(result[3].laneIndex).toBe(1); // Reuses lane 1
      expect(result[4].laneIndex).toBe(3);
    });
  });

  describe('event ordering', () => {
    it('should produce same result regardless of input order', () => {
      const events: Event[] = [
        {
          id: 3,
          resourceId: 1,
          title: 'Event 3',
          start: new Date('2025-01-01T11:00:00'),
          end: new Date('2025-01-01T12:00:00'),
        },
        {
          id: 1,
          resourceId: 1,
          title: 'Event 1',
          start: new Date('2025-01-01T10:00:00'),
          end: new Date('2025-01-01T11:30:00'),
        },
        {
          id: 2,
          resourceId: 1,
          title: 'Event 2',
          start: new Date('2025-01-01T10:30:00'),
          end: new Date('2025-01-01T11:00:00'),
        },
      ];

      const result = calculateLayout(events);

      // Should be sorted by start time internally
      expect(result[0].id).toBe(1); // Starts at 10:00
      expect(result[1].id).toBe(2); // Starts at 10:30
      expect(result[2].id).toBe(3); // Starts at 11:00

      expect(result[0].laneIndex).toBe(0);
      expect(result[1].laneIndex).toBe(1);
      expect(result[2].laneIndex).toBe(1); // Can reuse lane 1
    });

    it('should handle reverse chronological order', () => {
      const events: Event[] = [
        {
          id: 3,
          resourceId: 1,
          title: 'Event 3',
          start: new Date('2025-01-01T14:00:00'),
          end: new Date('2025-01-01T15:00:00'),
        },
        {
          id: 2,
          resourceId: 1,
          title: 'Event 2',
          start: new Date('2025-01-01T12:00:00'),
          end: new Date('2025-01-01T13:00:00'),
        },
        {
          id: 1,
          resourceId: 1,
          title: 'Event 1',
          start: new Date('2025-01-01T10:00:00'),
          end: new Date('2025-01-01T11:00:00'),
        },
      ];

      const result = calculateLayout(events);

      expect(result[0].id).toBe(1);
      expect(result[1].id).toBe(2);
      expect(result[2].id).toBe(3);

      expect(result.every(event => event.laneIndex === 0)).toBe(true);
    });

    it('should not modify the original events array', () => {
      const events: Event[] = [
        {
          id: 2,
          resourceId: 1,
          title: 'Event 2',
          start: new Date('2025-01-01T11:00:00'),
          end: new Date('2025-01-01T12:00:00'),
        },
        {
          id: 1,
          resourceId: 1,
          title: 'Event 1',
          start: new Date('2025-01-01T10:00:00'),
          end: new Date('2025-01-01T11:00:00'),
        },
      ];

      const originalOrder = events.map(e => e.id);
      calculateLayout(events);
      const afterOrder = events.map(e => e.id);

      expect(afterOrder).toEqual(originalOrder);
    });
  });

  describe('different statuses and properties', () => {
    it('should handle events with all different statuses', () => {
      const events: Event[] = [
        {
          id: 1,
          resourceId: 1,
          title: 'Pending',
          start: new Date('2025-01-01T10:00:00'),
          end: new Date('2025-01-01T11:00:00'),
          status: 'PENDING',
        },
        {
          id: 2,
          resourceId: 1,
          title: 'Confirmed',
          start: new Date('2025-01-01T10:30:00'),
          end: new Date('2025-01-01T11:30:00'),
          status: 'CONFIRMED',
        },
        {
          id: 3,
          resourceId: 1,
          title: 'Completed',
          start: new Date('2025-01-01T11:00:00'),
          end: new Date('2025-01-01T12:00:00'),
          status: 'COMPLETED',
        },
        {
          id: 4,
          resourceId: 1,
          title: 'Cancelled',
          start: new Date('2025-01-01T11:30:00'),
          end: new Date('2025-01-01T12:30:00'),
          status: 'CANCELLED',
        },
        {
          id: 5,
          resourceId: 1,
          title: 'No Show',
          start: new Date('2025-01-01T12:00:00'),
          end: new Date('2025-01-01T13:00:00'),
          status: 'NO_SHOW',
        },
      ];

      const result = calculateLayout(events);

      expect(result).toHaveLength(5);
      // All statuses should be preserved
      expect(result[0].status).toBe('PENDING');
      expect(result[1].status).toBe('CONFIRMED');
      expect(result[2].status).toBe('COMPLETED');
      expect(result[3].status).toBe('CANCELLED');
      expect(result[4].status).toBe('NO_SHOW');
    });

    it('should handle events with mixed isPaid values', () => {
      const events: Event[] = [
        {
          id: 1,
          resourceId: 1,
          title: 'Paid',
          start: new Date('2025-01-01T10:00:00'),
          end: new Date('2025-01-01T11:00:00'),
          isPaid: true,
        },
        {
          id: 2,
          resourceId: 1,
          title: 'Unpaid',
          start: new Date('2025-01-01T10:30:00'),
          end: new Date('2025-01-01T11:30:00'),
          isPaid: false,
        },
        {
          id: 3,
          resourceId: 1,
          title: 'No payment info',
          start: new Date('2025-01-01T11:00:00'),
          end: new Date('2025-01-01T12:00:00'),
        },
      ];

      const result = calculateLayout(events);

      expect(result[0].isPaid).toBe(true);
      expect(result[1].isPaid).toBe(false);
      expect(result[2].isPaid).toBeUndefined();
    });

    it('should handle events with different resourceIds', () => {
      // Note: The algorithm doesn't filter by resourceId, it assigns lanes globally
      const events: Event[] = [
        {
          id: 1,
          resourceId: 1,
          title: 'Resource 1',
          start: new Date('2025-01-01T10:00:00'),
          end: new Date('2025-01-01T11:00:00'),
        },
        {
          id: 2,
          resourceId: 2,
          title: 'Resource 2',
          start: new Date('2025-01-01T10:30:00'),
          end: new Date('2025-01-01T11:30:00'),
        },
      ];

      const result = calculateLayout(events);

      // Even though different resources, they still get different lanes
      expect(result[0].laneIndex).toBe(0);
      expect(result[1].laneIndex).toBe(1);
    });
  });

  describe('real-world scenarios', () => {
    it('should handle a busy day with overlapping appointments', () => {
      const events: Event[] = [
        {
          id: 1,
          resourceId: 1,
          title: 'Client A - Haircut',
          serviceName: 'Haircut',
          start: new Date('2025-01-15T09:00:00'),
          end: new Date('2025-01-15T09:30:00'),
          status: 'CONFIRMED',
          isPaid: true,
        },
        {
          id: 2,
          resourceId: 1,
          title: 'Client B - Color',
          serviceName: 'Hair Color',
          start: new Date('2025-01-15T09:15:00'),
          end: new Date('2025-01-15T11:00:00'),
          status: 'CONFIRMED',
          isPaid: false,
        },
        {
          id: 3,
          resourceId: 1,
          title: 'Client C - Trim',
          serviceName: 'Trim',
          start: new Date('2025-01-15T09:30:00'),
          end: new Date('2025-01-15T10:00:00'),
          status: 'PENDING',
          isPaid: false,
        },
        {
          id: 4,
          resourceId: 1,
          title: 'Client D - Consultation',
          serviceName: 'Consultation',
          start: new Date('2025-01-15T11:00:00'),
          end: new Date('2025-01-15T11:30:00'),
          status: 'CONFIRMED',
          isPaid: true,
        },
      ];

      const result = calculateLayout(events);

      expect(result).toHaveLength(4);
      expect(result[0].laneIndex).toBe(0); // 9:00-9:30
      expect(result[1].laneIndex).toBe(1); // 9:15-11:00 overlaps with first
      expect(result[2].laneIndex).toBe(0); // 9:30-10:00 can reuse lane 0
      expect(result[3].laneIndex).toBe(0); // 11:00-11:30 can reuse lane 0
    });

    it('should handle lunch break pattern', () => {
      const events: Event[] = [
        {
          id: 1,
          resourceId: 1,
          title: 'Morning Appointment',
          start: new Date('2025-01-15T11:00:00'),
          end: new Date('2025-01-15T12:00:00'),
        },
        {
          id: 2,
          resourceId: 1,
          title: 'Afternoon Appointment',
          start: new Date('2025-01-15T13:00:00'),
          end: new Date('2025-01-15T14:00:00'),
        },
        {
          id: 3,
          resourceId: 1,
          title: 'Late Appointment',
          start: new Date('2025-01-15T14:00:00'),
          end: new Date('2025-01-15T15:00:00'),
        },
      ];

      const result = calculateLayout(events);

      expect(result.every(event => event.laneIndex === 0)).toBe(true);
    });

    it('should handle back-to-back events spanning multiple days', () => {
      const events: Event[] = [
        {
          id: 1,
          resourceId: 1,
          title: 'Day 1 Morning',
          start: new Date('2025-01-15T09:00:00'),
          end: new Date('2025-01-15T10:00:00'),
        },
        {
          id: 2,
          resourceId: 1,
          title: 'Day 1 Afternoon',
          start: new Date('2025-01-15T14:00:00'),
          end: new Date('2025-01-15T15:00:00'),
        },
        {
          id: 3,
          resourceId: 1,
          title: 'Day 2 Morning',
          start: new Date('2025-01-16T09:00:00'),
          end: new Date('2025-01-16T10:00:00'),
        },
      ];

      const result = calculateLayout(events);

      expect(result.every(event => event.laneIndex === 0)).toBe(true);
    });
  });
});
