import { Test, type TestingModule } from '@nestjs/testing';
import { BadRequestException, NotFoundException } from '@nestjs/common';
import { BookingPanService } from './booking-pan.service';
import { BookingService } from '../booking.service';
import { BookingRepository } from '../repositories/booking.repository';
import { PrivateDocumentStorage } from '../../../common/storage/private-document.storage';

describe('BookingPanService', () => {
  let service: BookingPanService;
  let bookings: { findById: jest.Mock };
  let repo: { findById: jest.Mock; update: jest.Mock };
  let files: {
    save: jest.Mock;
    delete: jest.Mock;
    exists: jest.Mock;
    read: jest.Mock;
  };

  const BOOKING = '11111111-1111-4111-8111-111111111111';
  const pdf = Buffer.from('%PDF-1.4 pan');

  beforeEach(async () => {
    bookings = {
      findById: jest.fn().mockResolvedValue({ id: BOOKING }),
    };
    repo = {
      findById: jest.fn().mockResolvedValue({ id: BOOKING, pan_card_filename: null }),
      update: jest.fn().mockResolvedValue(undefined),
    };
    files = {
      save: jest.fn().mockResolvedValue('1712-abcd.pdf'),
      delete: jest.fn().mockResolvedValue(undefined),
      exists: jest.fn().mockResolvedValue(true),
      read: jest.fn().mockResolvedValue(pdf),
    };

    const module: TestingModule = await Test.createTestingModule({
      providers: [
        BookingPanService,
        { provide: BookingService, useValue: bookings },
        { provide: BookingRepository, useValue: repo },
        { provide: PrivateDocumentStorage, useValue: files },
      ],
    }).compile();

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

  it('rejects upload when the booking is not visible in this tenant', async () => {
    bookings.findById.mockRejectedValue(new NotFoundException('Booking not found'));
    await expect(
      service.upload(BOOKING, { buffer: pdf, size: pdf.length } as Express.Multer.File),
    ).rejects.toThrow(NotFoundException);
    expect(files.save).not.toHaveBeenCalled();
  });

  it('stores a generated filename and never trusts the client name', async () => {
    const result = await service.upload(BOOKING, {
      originalname: '../../evil.pdf',
      buffer: pdf,
      size: pdf.length,
    } as Express.Multer.File);
    expect(result).toEqual({ has_pan_card: true });
    expect(files.save).toHaveBeenCalledWith('.pdf', pdf);
    expect(repo.update).toHaveBeenCalledWith(
      BOOKING,
      expect.objectContaining({ pan_card_filename: '1712-abcd.pdf' }),
    );
  });

  it('rejects non-image/PDF bodies', async () => {
    await expect(
      service.upload(BOOKING, {
        buffer: Buffer.from('not-a-pan'),
        size: 9,
      } as Express.Multer.File),
    ).rejects.toThrow(BadRequestException);
    expect(files.save).not.toHaveBeenCalled();
  });

  it('streams only after a tenant-scoped booking lookup', async () => {
    repo.findById.mockResolvedValue({ id: BOOKING, pan_card_filename: '1712-abcd.pdf' });
    const file = await service.download(BOOKING);
    expect(bookings.findById).toHaveBeenCalledWith(BOOKING);
    expect(files.read).toHaveBeenCalledWith('1712-abcd.pdf');
    expect(file).toBeDefined();
  });
});
