import { Test, type TestingModule } from '@nestjs/testing';
import { BookingStatus } from '../../../entities/booking.entity';
import { BookingRepository } from '../repositories/booking.repository';
import {
  BookingStatusSyncService,
  resolveDateDrivenBookingStatus,
} from './booking-status-sync.service';

describe('resolveDateDrivenBookingStatus', () => {
  const today = '2026-09-10';

  it('ignores cancelled regardless of dates', () => {
    expect(
      resolveDateDrivenBookingStatus(BookingStatus.CANCELLED, '2026-09-01', '2026-09-20', today),
    ).toBeNull();
    expect(
      resolveDateDrivenBookingStatus(BookingStatus.CANCELLED, '2026-09-11', '2026-09-20', today),
    ).toBeNull();
    expect(
      resolveDateDrivenBookingStatus(BookingStatus.CANCELLED, '2026-09-01', '2026-09-02', today),
    ).toBeNull();
  });

  it('ignores completed regardless of dates', () => {
    expect(
      resolveDateDrivenBookingStatus(BookingStatus.COMPLETED, '2026-09-09', '2026-09-11', today),
    ).toBeNull();
    expect(
      resolveDateDrivenBookingStatus(BookingStatus.COMPLETED, '2026-09-11', '2026-09-20', today),
    ).toBeNull();
    expect(
      resolveDateDrivenBookingStatus(BookingStatus.COMPLETED, '2026-09-01', '2026-09-02', today),
    ).toBeNull();
  });

  it('ignores on_hold regardless of dates', () => {
    expect(
      resolveDateDrivenBookingStatus(BookingStatus.ON_HOLD, '2026-09-01', '2026-09-20', today),
    ).toBeNull();
  });

  it('sets ongoing when today is within travel dates', () => {
    expect(
      resolveDateDrivenBookingStatus(BookingStatus.UPCOMING, '2026-09-10', '2026-09-15', today),
    ).toBe(BookingStatus.ONGOING);
  });

  it('sets ongoing on the last travel day', () => {
    expect(
      resolveDateDrivenBookingStatus(BookingStatus.UPCOMING, '2026-09-08', '2026-09-10', today),
    ).toBe(BookingStatus.ONGOING);
  });

  it('sets completed when today is after travel dates', () => {
    expect(
      resolveDateDrivenBookingStatus(BookingStatus.ONGOING, '2026-09-01', '2026-09-09', today),
    ).toBe(BookingStatus.COMPLETED);
  });

  it('leaves upcoming unchanged when travel dates are still in the future', () => {
    expect(
      resolveDateDrivenBookingStatus(BookingStatus.UPCOMING, '2026-09-11', '2026-09-20', today),
    ).toBeNull();
  });

  it('reverts premature ongoing to upcoming when travel dates are still in the future', () => {
    expect(
      resolveDateDrivenBookingStatus(BookingStatus.ONGOING, '2026-09-11', '2026-09-20', today),
    ).toBe(BookingStatus.UPCOMING);
  });

  it('does not rewrite a status that already matches', () => {
    expect(
      resolveDateDrivenBookingStatus(BookingStatus.ONGOING, '2026-09-08', '2026-09-12', today),
    ).toBeNull();
  });
});

describe('BookingStatusSyncService', () => {
  let service: BookingStatusSyncService;

  const mockRepo = {
    listActiveTenantIdsForStatusSync: jest.fn(),
    findEligibleForDateSync: jest.fn(),
    bulkUpdateStatus: jest.fn(),
  };

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [BookingStatusSyncService, { provide: BookingRepository, useValue: mockRepo }],
    }).compile();

    service = module.get(BookingStatusSyncService);
  });

  afterEach(() => jest.clearAllMocks());

  it('passes each tenant id into repository writes and never mixes tenants', async () => {
    mockRepo.listActiveTenantIdsForStatusSync.mockResolvedValue(['tenant-a', 'tenant-b']);
    mockRepo.findEligibleForDateSync.mockImplementation(async (tenantId: string) => {
      if (tenantId === 'tenant-a') {
        return [
          { id: 'b-a', status: 'upcoming', start_date: '2026-09-10', end_date: '2026-09-12' },
        ];
      }
      return [{ id: 'b-b', status: 'upcoming', start_date: '2026-09-01', end_date: '2026-09-02' }];
    });
    mockRepo.bulkUpdateStatus.mockResolvedValue(undefined);

    const result = await service.syncAllTenants('2026-09-10');

    expect(mockRepo.findEligibleForDateSync).toHaveBeenCalledWith('tenant-a');
    expect(mockRepo.findEligibleForDateSync).toHaveBeenCalledWith('tenant-b');
    expect(mockRepo.bulkUpdateStatus).toHaveBeenCalledWith(
      'tenant-a',
      ['b-a'],
      BookingStatus.ONGOING,
    );
    expect(mockRepo.bulkUpdateStatus).toHaveBeenCalledWith(
      'tenant-b',
      ['b-b'],
      BookingStatus.COMPLETED,
    );
    expect(result).toEqual({
      today: '2026-09-10',
      tenantsProcessed: 2,
      upcomingUpdated: 0,
      ongoingUpdated: 1,
      completedUpdated: 1,
    });
  });

  it('corrects a premature ongoing booking back to upcoming for that tenant only', async () => {
    mockRepo.listActiveTenantIdsForStatusSync.mockResolvedValue(['tenant-a', 'tenant-b']);
    mockRepo.findEligibleForDateSync.mockImplementation(async (tenantId: string) => {
      if (tenantId === 'tenant-a') {
        return [
          { id: 'b-early', status: 'ongoing', start_date: '2026-09-20', end_date: '2026-09-25' },
        ];
      }
      return [{ id: 'b-ok', status: 'ongoing', start_date: '2026-09-08', end_date: '2026-09-12' }];
    });
    mockRepo.bulkUpdateStatus.mockResolvedValue(undefined);

    const result = await service.syncAllTenants('2026-09-10');

    expect(mockRepo.bulkUpdateStatus).toHaveBeenCalledWith(
      'tenant-a',
      ['b-early'],
      BookingStatus.UPCOMING,
    );
    expect(mockRepo.bulkUpdateStatus).toHaveBeenCalledWith('tenant-b', [], BookingStatus.UPCOMING);
    expect(result.upcomingUpdated).toBe(1);
    expect(result.ongoingUpdated).toBe(0);
  });

  it('rejects an unscoped update by always requiring tenant_id on writes', async () => {
    mockRepo.listActiveTenantIdsForStatusSync.mockResolvedValue(['tenant-a']);
    mockRepo.findEligibleForDateSync.mockResolvedValue([
      { id: 'b1', status: 'upcoming', start_date: '2026-09-01', end_date: '2026-09-02' },
    ]);
    mockRepo.bulkUpdateStatus.mockResolvedValue(undefined);

    await service.syncAllTenants('2026-09-10');

    for (const call of mockRepo.bulkUpdateStatus.mock.calls) {
      expect(call[0]).toBe('tenant-a');
      expect(call[0]).not.toBeUndefined();
    }
  });

  it('skips cancelled, completed, and on_hold rows even if the repository returns them', async () => {
    mockRepo.listActiveTenantIdsForStatusSync.mockResolvedValue(['tenant-a']);
    mockRepo.findEligibleForDateSync.mockResolvedValue([
      { id: 'c1', status: 'cancelled', start_date: '2026-09-01', end_date: '2026-09-20' },
      { id: 'd1', status: 'completed', start_date: '2026-09-09', end_date: '2026-09-11' },
      { id: 'h1', status: 'on_hold', start_date: '2026-09-01', end_date: '2026-09-20' },
    ]);
    mockRepo.bulkUpdateStatus.mockResolvedValue(undefined);

    await service.syncAllTenants('2026-09-10');

    expect(mockRepo.bulkUpdateStatus).toHaveBeenCalledWith('tenant-a', [], BookingStatus.UPCOMING);
    expect(mockRepo.bulkUpdateStatus).toHaveBeenCalledWith('tenant-a', [], BookingStatus.ONGOING);
    expect(mockRepo.bulkUpdateStatus).toHaveBeenCalledWith('tenant-a', [], BookingStatus.COMPLETED);
  });
});
