import { ContractBookingService } from './contract-booking.service';
import { Booking } from '../bookings/entities/booking.entity';

/**
 * The GL contract-drawdown path must run wagon consolidation before invoicing.
 * A partial-wagon drawdown (e.g. 21× 20FT → one leftover container) parks in
 * PENDING_CONSOLIDATION and is NOT finalized (no invoice / milestones) until it
 * pairs with a wagon partner. These tests exercise the two new hooks directly.
 */
describe('ContractBookingService — drawdown consolidation gate', () => {
  function makeService(overrides: {
    consolidationService?: Partial<Record<string, jest.Mock>>;
    bookingsRepository?: Partial<Record<string, jest.Mock>>;
    invoiceService?: Partial<Record<string, jest.Mock>>;
    milestoneService?: Partial<Record<string, jest.Mock>>;
    contractsRepository?: Partial<Record<string, jest.Mock>>;
  }) {
    const consolidationService = {
      slotsFromBooking: jest.fn().mockResolvedValue([]),
      describePaired: jest.fn().mockReturnValue('paired'),
      describePending: jest.fn().mockReturnValue('pending'),
      needsConsolidationFromBooking: jest.fn().mockResolvedValue(false),
      ...overrides.consolidationService,
    };
    const bookingsRepository = {
      findConsolidationPartner: jest.fn().mockResolvedValue(null),
      pairConsolidation: jest.fn().mockResolvedValue(undefined),
      parkForConsolidation: jest.fn().mockResolvedValue(undefined),
      findByIdWithFiles: jest.fn(),
      ...overrides.bookingsRepository,
    };
    const invoiceService = {
      ensureInvoiceForBooking: jest.fn().mockResolvedValue({ id: 'inv-1' }),
      ...overrides.invoiceService,
    };
    const milestoneService = {
      seedPostBookingMilestones: jest.fn().mockResolvedValue(undefined),
      seedPreBookingMilestonesOnBooking: jest.fn().mockResolvedValue(undefined),
      ensureBookingMilestones: jest.fn().mockResolvedValue(undefined),
      ...overrides.milestoneService,
    };
    const contractsRepository = {
      findByIdWithRelations: jest.fn(),
      currentCycle: jest.fn().mockResolvedValue(null),
      linkBooking: jest.fn().mockResolvedValue(undefined),
      update: jest.fn().mockResolvedValue(undefined),
      ...overrides.contractsRepository,
    };

    const service = new ContractBookingService(
      contractsRepository as never,
      bookingsRepository as never,
      {} as never, // bookingPricingService
      consolidationService as never,
      {} as never, // containerTypesService
      {} as never, // ruleEngineService
      milestoneService as never,
      {} as never, // workflowService
      invoiceService as never,
      {
        createdToStaff: jest.fn(),
        createdByGlForCustomer: jest.fn(),
      } as never, // bookingNotifier
      {} as never, // dataSource
      {} as never, // trainSchedulingService
      {} as never, // bookingBatchService
      {} as never, // bookingTransitionService
    );
    return {
      service,
      consolidationService,
      bookingsRepository,
      invoiceService,
      milestoneService,
      contractsRepository,
    };
  }

  const booking = { id: 'b-1', reference: 'BK-1' } as Booking;

  it('parks (not pairs) when no complementary partner exists', async () => {
    const { service, bookingsRepository } = makeService({
      consolidationService: {
        slotsFromBooking: jest
          .fn()
          .mockResolvedValue([{ containerTypeId: 'ct', slotsNeeded: 1 }]),
      },
      bookingsRepository: {
        findConsolidationPartner: jest.fn().mockResolvedValue(null),
      },
    });

    const result = await (service as never as {
      consolidateDrawdown: (b: Booking, s: string) => Promise<{ paired: boolean }>;
    }).consolidateDrawdown(booking, 'OPERATION_REQUEST_PENDING');

    expect(result.paired).toBe(false);
    expect(bookingsRepository.parkForConsolidation).toHaveBeenCalledWith(
      'b-1',
      'OPERATION_REQUEST_PENDING',
    );
    expect(bookingsRepository.pairConsolidation).not.toHaveBeenCalled();
  });

  it('pairs when a complementary partner exists', async () => {
    const { service, bookingsRepository } = makeService({
      consolidationService: {
        slotsFromBooking: jest
          .fn()
          .mockResolvedValue([{ containerTypeId: 'ct', slotsNeeded: 1 }]),
      },
      bookingsRepository: {
        findConsolidationPartner: jest
          .fn()
          .mockResolvedValue({ id: 'p-1', reference: 'BK-2' }),
      },
    });

    const result = await (service as never as {
      consolidateDrawdown: (b: Booking, s: string) => Promise<{ paired: boolean }>;
    }).consolidateDrawdown(booking, 'AWAITING_DOCUMENTS');

    expect(result.paired).toBe(true);
    expect(bookingsRepository.pairConsolidation).toHaveBeenCalledWith('b-1', 'p-1');
    expect(bookingsRepository.parkForConsolidation).not.toHaveBeenCalled();
  });

  it('onConsolidationPaired finalizes a resumed contract booking (invoice + milestones)', async () => {
    const paired = {
      id: 'b-1',
      reference: 'BK-1',
      contractId: 'c-1',
      status: 'OPERATION_REQUEST_PENDING',
    } as Booking;
    const contract = {
      id: 'c-1',
      contractKind: 'GENERAL',
      customsClearingEnabled: true,
      tradeDirection: 'EXPORT',
    };
    const { service, invoiceService, milestoneService } = makeService({
      bookingsRepository: {
        findByIdWithFiles: jest.fn().mockResolvedValue(paired),
      },
      contractsRepository: {
        findByIdWithRelations: jest.fn().mockResolvedValue(contract),
      },
    });

    await service.onConsolidationPaired({ bookingIds: ['b-1'] });

    expect(invoiceService.ensureInvoiceForBooking).toHaveBeenCalledTimes(1);
    // GENERAL customs → per-booking milestones, via the idempotent ensure so a
    // pairing replay (or an initiated instance's pre-seeded timeline) never
    // duplicates rows.
    expect(milestoneService.ensureBookingMilestones).toHaveBeenCalledWith(
      'b-1',
      'EXPORT',
    );
  });

  it('onConsolidationPaired ignores a booking still PENDING_CONSOLIDATION', async () => {
    const stillPending = {
      id: 'b-1',
      contractId: 'c-1',
      status: 'PENDING_CONSOLIDATION',
    } as Booking;
    const { service, invoiceService } = makeService({
      bookingsRepository: {
        findByIdWithFiles: jest.fn().mockResolvedValue(stillPending),
      },
    });

    await service.onConsolidationPaired({ bookingIds: ['b-1'] });

    expect(invoiceService.ensureInvoiceForBooking).not.toHaveBeenCalled();
  });

  it('onConsolidationPaired ignores a non-contract (direct) booking', async () => {
    const direct = { id: 'd-1', status: 'SUBMITTED', contractId: null } as Booking;
    const { service, invoiceService, contractsRepository } = makeService({
      bookingsRepository: {
        findByIdWithFiles: jest.fn().mockResolvedValue(direct),
      },
    });

    await service.onConsolidationPaired({ bookingIds: ['d-1'] });

    expect(contractsRepository.findByIdWithRelations).not.toHaveBeenCalled();
    expect(invoiceService.ensureInvoiceForBooking).not.toHaveBeenCalled();
  });
});
