import {
  CsvBooking,
  CsvBookingStatus,
  DocumentType,
  CsvBookingDocument,
} from './csv-booking.entity';
import { PortCode } from '../value-objects/port-code.vo';

describe('CsvBooking Entity', () => {
  // Test data factory
  const createValidBooking = (
    _overrides?: Partial<ConstructorParameters<typeof CsvBooking>[0]>
  ): CsvBooking => {
    const documents: CsvBookingDocument[] = [
      {
        id: 'doc-1',
        type: DocumentType.BILL_OF_LADING,
        fileName: 'bill-of-lading.pdf',
        filePath: '/uploads/bill-of-lading.pdf',
        mimeType: 'application/pdf',
        size: 1024,
        uploadedAt: new Date(),
      },
      {
        id: 'doc-2',
        type: DocumentType.PACKING_LIST,
        fileName: 'packing-list.pdf',
        filePath: '/uploads/packing-list.pdf',
        mimeType: 'application/pdf',
        size: 2048,
        uploadedAt: new Date(),
      },
      {
        id: 'doc-3',
        type: DocumentType.COMMERCIAL_INVOICE,
        fileName: 'invoice.pdf',
        filePath: '/uploads/invoice.pdf',
        mimeType: 'application/pdf',
        size: 3072,
        uploadedAt: new Date(),
      },
    ];

    return new CsvBooking(
      'booking-123',
      'user-456',
      'org-789',
      'SSC Consolidation',
      'bookings@sscconsolidation.com',
      PortCode.create('NLRTM'),
      PortCode.create('USNYC'),
      10.5,
      1500,
      3,
      1200.0,
      1100.0,
      'USD',
      15,
      'LCL',
      CsvBookingStatus.PENDING,
      documents,
      'token-abc123',
      new Date(),
      undefined,
      'Test booking',
      undefined
    );
  };

  describe('Constructor and Validation', () => {
    it('should create a valid booking', () => {
      const booking = createValidBooking();

      expect(booking.id).toBe('booking-123');
      expect(booking.userId).toBe('user-456');
      expect(booking.organizationId).toBe('org-789');
      expect(booking.carrierName).toBe('SSC Consolidation');
      expect(booking.carrierEmail).toBe('bookings@sscconsolidation.com');
      expect(booking.status).toBe(CsvBookingStatus.PENDING);
      expect(booking.documents).toHaveLength(3);
    });

    it('should throw error if ID is empty', () => {
      expect(() => {
        const docs: CsvBookingDocument[] = [
          {
            id: 'doc-1',
            type: DocumentType.BILL_OF_LADING,
            fileName: 'test.pdf',
            filePath: '/test.pdf',
            mimeType: 'application/pdf',
            size: 1024,
            uploadedAt: new Date(),
          },
        ];
        new CsvBooking(
          '',
          'user-456',
          'org-789',
          'SSC Consolidation',
          'bookings@sscconsolidation.com',
          PortCode.create('NLRTM'),
          PortCode.create('USNYC'),
          10.5,
          1500,
          3,
          1200.0,
          1100.0,
          'USD',
          15,
          'LCL',
          CsvBookingStatus.PENDING,
          docs,
          'token-abc123',
          new Date()
        );
      }).toThrow('Booking ID is required');
    });

    it('should throw error if volume is negative', () => {
      expect(() => {
        const docs: CsvBookingDocument[] = [
          {
            id: 'doc-1',
            type: DocumentType.BILL_OF_LADING,
            fileName: 'test.pdf',
            filePath: '/test.pdf',
            mimeType: 'application/pdf',
            size: 1024,
            uploadedAt: new Date(),
          },
        ];
        new CsvBooking(
          'booking-123',
          'user-456',
          'org-789',
          'SSC Consolidation',
          'bookings@sscconsolidation.com',
          PortCode.create('NLRTM'),
          PortCode.create('USNYC'),
          -10.5, // Negative volume
          1500,
          3,
          1200.0,
          1100.0,
          'USD',
          15,
          'LCL',
          CsvBookingStatus.PENDING,
          docs,
          'token-abc123',
          new Date()
        );
      }).toThrow('Volume must be positive');
    });

    it('should throw error if email format is invalid', () => {
      expect(() => {
        const docs: CsvBookingDocument[] = [
          {
            id: 'doc-1',
            type: DocumentType.BILL_OF_LADING,
            fileName: 'test.pdf',
            filePath: '/test.pdf',
            mimeType: 'application/pdf',
            size: 1024,
            uploadedAt: new Date(),
          },
        ];
        new CsvBooking(
          'booking-123',
          'user-456',
          'org-789',
          'SSC Consolidation',
          'invalid-email', // Invalid email
          PortCode.create('NLRTM'),
          PortCode.create('USNYC'),
          10.5,
          1500,
          3,
          1200.0,
          1100.0,
          'USD',
          15,
          'LCL',
          CsvBookingStatus.PENDING,
          docs,
          'token-abc123',
          new Date()
        );
      }).toThrow('Invalid carrier email format');
    });

    it('should throw error if no documents provided', () => {
      expect(() => {
        new CsvBooking(
          'booking-123',
          'user-456',
          'org-789',
          'SSC Consolidation',
          'bookings@sscconsolidation.com',
          PortCode.create('NLRTM'),
          PortCode.create('USNYC'),
          10.5,
          1500,
          3,
          1200.0,
          1100.0,
          'USD',
          15,
          'LCL',
          CsvBookingStatus.PENDING,
          [], // Empty documents
          'token-abc123',
          new Date()
        );
      }).toThrow('At least one document is required for booking');
    });
  });

  describe('Accept Method', () => {
    it('should accept a pending booking', () => {
      const booking = createValidBooking();

      expect(booking.status).toBe(CsvBookingStatus.PENDING);

      booking.accept();

      expect(booking.status).toBe(CsvBookingStatus.ACCEPTED);
      expect(booking.respondedAt).toBeDefined();
    });

    it('should throw error when accepting non-pending booking', () => {
      const booking = createValidBooking();
      booking.accept(); // First acceptance

      expect(() => {
        booking.accept(); // Try to accept again
      }).toThrow('Cannot accept booking with status ACCEPTED');
    });

    it('should throw error when accepting expired booking', () => {
      const booking = createValidBooking();
      // Set requestedAt to 8 days ago (expired)
      (booking as any).requestedAt = new Date(Date.now() - 8 * 24 * 60 * 60 * 1000);

      expect(booking.isExpired()).toBe(true);

      expect(() => {
        booking.accept();
      }).toThrow('Cannot accept expired booking');
    });
  });

  describe('Reject Method', () => {
    it('should reject a pending booking', () => {
      const booking = createValidBooking();

      expect(booking.status).toBe(CsvBookingStatus.PENDING);

      booking.reject('Capacity full');

      expect(booking.status).toBe(CsvBookingStatus.REJECTED);
      expect(booking.respondedAt).toBeDefined();
      expect(booking.rejectionReason).toBe('Capacity full');
    });

    it('should reject without reason', () => {
      const booking = createValidBooking();

      booking.reject();

      expect(booking.status).toBe(CsvBookingStatus.REJECTED);
      expect(booking.rejectionReason).toBeUndefined();
    });

    it('should throw error when rejecting non-pending booking', () => {
      const booking = createValidBooking();
      booking.reject(); // First rejection

      expect(() => {
        booking.reject(); // Try to reject again
      }).toThrow('Cannot reject booking with status REJECTED');
    });
  });

  describe('Cancel Method', () => {
    it('should cancel a pending booking', () => {
      const booking = createValidBooking();

      booking.cancel();

      expect(booking.status).toBe(CsvBookingStatus.CANCELLED);
      expect(booking.respondedAt).toBeDefined();
    });

    it('should throw error when cancelling accepted booking', () => {
      const booking = createValidBooking();
      booking.accept();

      expect(() => {
        booking.cancel();
      }).toThrow('Cannot cancel accepted booking');
    });

    it('should throw error when cancelling rejected booking', () => {
      const booking = createValidBooking();
      booking.reject();

      expect(() => {
        booking.cancel();
      }).toThrow('Cannot cancel rejected booking');
    });
  });

  describe('Expiration Logic', () => {
    it('should not be expired for recent bookings', () => {
      const booking = createValidBooking();

      expect(booking.isExpired()).toBe(false);
    });

    it('should be expired after 7 days', () => {
      const booking = createValidBooking();
      // Set requestedAt to 8 days ago
      (booking as any).requestedAt = new Date(Date.now() - 8 * 24 * 60 * 60 * 1000);

      expect(booking.isExpired()).toBe(true);
    });

    it('should not be expired if already accepted', () => {
      const booking = createValidBooking();
      booking.accept();

      // Set requestedAt to 8 days ago
      (booking as any).requestedAt = new Date(Date.now() - 8 * 24 * 60 * 60 * 1000);

      expect(booking.isExpired()).toBe(false);
    });

    it('should calculate days until expiration correctly', () => {
      const booking = createValidBooking();

      const days = booking.getDaysUntilExpiration();

      expect(days).toBeGreaterThan(6);
      expect(days).toBeLessThanOrEqual(7);
    });

    it('should return 0 days for accepted bookings', () => {
      const booking = createValidBooking();
      booking.accept();

      expect(booking.getDaysUntilExpiration()).toBe(0);
    });
  });

  describe('Status Check Methods', () => {
    it('should correctly identify pending booking', () => {
      const booking = createValidBooking();

      expect(booking.isPending()).toBe(true);
      expect(booking.isAccepted()).toBe(false);
      expect(booking.isRejected()).toBe(false);
      expect(booking.isCancelled()).toBe(false);
    });

    it('should correctly identify accepted booking', () => {
      const booking = createValidBooking();
      booking.accept();

      expect(booking.isPending()).toBe(false);
      expect(booking.isAccepted()).toBe(true);
      expect(booking.isRejected()).toBe(false);
      expect(booking.isCancelled()).toBe(false);
    });

    it('should correctly identify rejected booking', () => {
      const booking = createValidBooking();
      booking.reject();

      expect(booking.isPending()).toBe(false);
      expect(booking.isAccepted()).toBe(false);
      expect(booking.isRejected()).toBe(true);
      expect(booking.isCancelled()).toBe(false);
    });
  });

  describe('Document Methods', () => {
    it('should check if document type exists', () => {
      const booking = createValidBooking();

      expect(booking.hasDocumentType(DocumentType.BILL_OF_LADING)).toBe(true);
      expect(booking.hasDocumentType(DocumentType.CERTIFICATE_OF_ORIGIN)).toBe(false);
    });

    it('should get documents by type', () => {
      const booking = createValidBooking();

      const billOfLading = booking.getDocumentsByType(DocumentType.BILL_OF_LADING);
      expect(billOfLading).toHaveLength(1);
      expect(billOfLading[0].fileName).toBe('bill-of-lading.pdf');
    });

    it('should check if all required documents are present', () => {
      const booking = createValidBooking();

      expect(booking.hasAllRequiredDocuments()).toBe(true);
    });

    it('should return false if required documents are missing', () => {
      const docs: CsvBookingDocument[] = [
        {
          id: 'doc-1',
          type: DocumentType.BILL_OF_LADING,
          fileName: 'bill-of-lading.pdf',
          filePath: '/uploads/bill-of-lading.pdf',
          mimeType: 'application/pdf',
          size: 1024,
          uploadedAt: new Date(),
        },
      ];

      const booking = new CsvBooking(
        'booking-123',
        'user-456',
        'org-789',
        'SSC Consolidation',
        'bookings@sscconsolidation.com',
        PortCode.create('NLRTM'),
        PortCode.create('USNYC'),
        10.5,
        1500,
        3,
        1200.0,
        1100.0,
        'USD',
        15,
        'LCL',
        CsvBookingStatus.PENDING,
        docs,
        'token-abc123',
        new Date()
      );

      expect(booking.hasAllRequiredDocuments()).toBe(false);
    });
  });

  describe('Helper Methods', () => {
    it('should return route description', () => {
      const booking = createValidBooking();

      expect(booking.getRouteDescription()).toBe('NLRTM → USNYC');
    });

    it('should return booking summary', () => {
      const booking = createValidBooking();

      expect(booking.getSummary()).toContain('CSV Booking booking-123');
      expect(booking.getSummary()).toContain('SSC Consolidation');
      expect(booking.getSummary()).toContain('NLRTM → USNYC');
      expect(booking.getSummary()).toContain('PENDING');
    });

    it('should return price in specified currency', () => {
      const booking = createValidBooking();

      expect(booking.getPriceInCurrency('USD')).toBe(1200.0);
      expect(booking.getPriceInCurrency('EUR')).toBe(1100.0);
    });

    it('should calculate response time in hours', () => {
      const booking = createValidBooking();

      // No response yet
      expect(booking.getResponseTimeHours()).toBeNull();

      // Accept booking
      booking.accept();

      const responseTime = booking.getResponseTimeHours();
      expect(responseTime).toBeGreaterThanOrEqual(0);
      expect(responseTime).toBeLessThan(1); // Should be less than 1 hour for this test
    });
  });
});
