const ejs = require('ejs');
const fs = require('fs').promises;
const { logger } = require('nodejs-log-agent');
const { generatePDF } = require('./pdfGenerate');
const { getBrowser } = require('./puppeteerManager');

// Mock all external dependencies
jest.mock('ejs', () => ({
  renderFile: jest.fn(),
}));

jest.mock('fs', () => ({
  promises: {
    mkdir: jest.fn(),
  },
}));

jest.mock('./puppeteerManager', () => ({
  getBrowser: jest.fn(),
}));

jest.mock('nodejs-log-agent', () => ({
  logger: {
    error: jest.fn(),
    warn: jest.fn(),
    info: jest.fn(),
  },
}));

describe('generatePDF', () => {
  const mockPage = {
    setContent: jest.fn(),
    pdf: jest.fn(),
    close: jest.fn(),
  };

  const mockBrowser = {
    newPage: jest.fn(() => Promise.resolve(mockPage)),
  };

  // Reset mocks before each test to ensure a clean state
  beforeEach(() => {
    jest.clearAllMocks();
    getBrowser.mockResolvedValue(mockBrowser);
  });

  const mockInvoiceData = {
    inv_id: 'TEST-001',
    inv_status: 'Paid',
  };

  it('should generate a PDF successfully', async () => {
    const mockHtml = '<html><body>Test PDF</body></html>';

    // Arrange: Set up the mocks for a successful scenario
    ejs.renderFile.mockResolvedValue(mockHtml);
    fs.promises.mkdir.mockResolvedValue(undefined); // mkdir succeeds

    // Act: Call the function
    const result = await generatePDF(mockInvoiceData);

    // Assert: Verify the outcomes
    expect(ejs.renderFile).toHaveBeenCalledWith(expect.stringContaining('invoice.ejs'), mockInvoiceData);
    expect(fs.promises.mkdir).toHaveBeenCalledWith(expect.any(String), { recursive: true });
    expect(getBrowser).toHaveBeenCalled();
    expect(mockBrowser.newPage).toHaveBeenCalled();
    expect(mockPage.setContent).toHaveBeenCalledWith(mockHtml, { waitUntil: 'networkidle0' });
    expect(mockPage.pdf).toHaveBeenCalled();
    expect(mockPage.close).toHaveBeenCalled();

    expect(result).toHaveProperty('status', true);
    expect(result).toHaveProperty('message', 'PDF generated successfully');
    expect(result.pdfName).toContain(mockInvoiceData.inv_id);
    expect(result.pdfPath).toContain(mockInvoiceData.inv_id);
  });

  it('should throw a structured error if puppeteer fails to create the file', async () => {
    const mockError = new Error('PDF creation failed');

    // Arrange: Set up mocks for a failure in the pdf generation
    ejs.renderFile.mockResolvedValue('<html></html>');
    fs.promises.mkdir.mockResolvedValue(undefined);
    mockPage.pdf.mockRejectedValue(mockError);

    // Act & Assert: Expect the promise to throw the structured error
    await expect(generatePDF(mockInvoiceData)).rejects.toMatchObject({
      status: false,
      message: `Failed to generate PDF: ${mockError.message}`,
      error: mockError,
    });

    // Ensure page is still closed in finally block
    expect(mockPage.close).toHaveBeenCalled();
  });

  it('should throw an error if EJS template rendering fails', async () => {
    const mockError = new Error('EJS rendering failed');

    // Arrange: Mock ejs.renderFile to throw an error
    ejs.renderFile.mockRejectedValue(mockError);

    // Act & Assert: Expect the function to throw an error
    await expect(generatePDF(mockInvoiceData)).rejects.toMatchObject({ message: expect.stringContaining(mockError.message) });
    expect(logger.error).toHaveBeenCalledWith("[Puppeteer] Failed to generate PDF", expect.any(Object));
  });

  it('should throw an error if directory creation fails', async () => {
    const mockError = new Error('Could not create directory');

    // Arrange: Mock fs.promises.mkdir to throw an error
    ejs.renderFile.mockResolvedValue('<html></html>');
    fs.promises.mkdir.mockRejectedValue(mockError);

    // Act & Assert: Expect the function to throw an error
    await expect(generatePDF(mockInvoiceData)).rejects.toMatchObject({ message: expect.stringContaining(mockError.message) });
    expect(logger.error).toHaveBeenCalledWith("[Puppeteer] Failed to generate PDF", expect.any(Object));
  });
});
