import { pdfGenerationService } from '../pdfGenerationService';
import puppeteer from 'puppeteer';
import fs from 'fs';
import path from 'path';

// Mock dependencies
jest.mock('puppeteer', () => ({
  launch: jest.fn(),
}));
jest.mock('fs');
jest.mock('path');

const mockPuppeteer = puppeteer as jest.Mocked<typeof puppeteer>;
const mockFs = fs as jest.Mocked<typeof fs>;
const mockPath = path as jest.Mocked<typeof path>;

describe('PDFGenerationService', () => {
  const mockMarkdown = `# CIM Review Summary

## (A) Deal Overview
- **Target Company Name:** ABC Company
- **Industry/Sector:** Technology
- **Geography:** San Francisco, CA

## (B) Business Description
- **Core Operations Summary:** Technology company with AI focus
- **Key Products/Services:** AI software solutions

## (C) Market & Industry Analysis
- **Market Size:** $10 billion
- **Growth Rate:** 15% annually

## Key Investment Considerations
- Strong technology platform
- Growing market opportunity
- Experienced management team`;

  const mockPage = {
    setContent: jest.fn(),
    pdf: jest.fn(),
    goto: jest.fn(),
    evaluate: jest.fn(),
    close: jest.fn(),
  };

  const mockBrowser = {
    newPage: jest.fn().mockResolvedValue(mockPage),
    close: jest.fn(),
  };

  beforeEach(() => {
    jest.clearAllMocks();
    
    // Mock puppeteer
    mockPuppeteer.launch.mockResolvedValue(mockBrowser as any);
    
    // Mock fs
    mockFs.existsSync.mockReturnValue(true);
    mockFs.mkdirSync.mockImplementation(() => undefined);
    mockFs.writeFileSync.mockImplementation(() => {});
    mockFs.readFileSync.mockReturnValue(Buffer.from('%PDF-1.4 test content'));
    mockFs.statSync.mockReturnValue({ size: 1000 } as any);
    
    // Mock path
    mockPath.join.mockImplementation((...args) => args.join('/'));
    mockPath.dirname.mockReturnValue('/test/uploads/summaries');
  });

  describe('generatePDFFromMarkdown', () => {
    it('should generate PDF from markdown successfully', async () => {
      mockPage.pdf.mockResolvedValue(Buffer.from('mock pdf content'));

      const result = await pdfGenerationService.generatePDFFromMarkdown(
        mockMarkdown,
        '/test/output.pdf'
      );

      expect(result).toBe(true);
      expect(mockPuppeteer.launch).toHaveBeenCalled();
      expect(mockPage.setContent).toHaveBeenCalled();
      expect(mockPage.pdf).toHaveBeenCalled();
      expect(mockPage.close).toHaveBeenCalled();
    });

    it('should create output directory if it does not exist', async () => {
      mockFs.existsSync.mockReturnValue(false);
      mockPage.pdf.mockResolvedValue(Buffer.from('mock pdf content'));

      await pdfGenerationService.generatePDFFromMarkdown(
        mockMarkdown,
        '/test/output.pdf'
      );

      expect(mockFs.mkdirSync).toHaveBeenCalledWith('/test', { recursive: true });
    });

    it('should handle PDF generation failure', async () => {
      mockPage.pdf.mockRejectedValue(new Error('PDF generation failed'));

      const result = await pdfGenerationService.generatePDFFromMarkdown(
        mockMarkdown,
        '/test/output.pdf'
      );

      expect(result).toBe(false);
      expect(mockPage.close).toHaveBeenCalled();
    });

    it('should use custom options', async () => {
      mockPage.pdf.mockResolvedValue(Buffer.from('mock pdf content'));

      const customOptions = {
        format: 'Letter' as const,
        margin: {
          top: '0.5in',
          right: '0.5in',
          bottom: '0.5in',
          left: '0.5in',
        },
        displayHeaderFooter: false,
      };

      await pdfGenerationService.generatePDFFromMarkdown(
        mockMarkdown,
        '/test/output.pdf',
        customOptions
      );

      expect(mockPage.pdf).toHaveBeenCalledWith(
        expect.objectContaining({
          format: 'Letter',
          margin: customOptions.margin,
          displayHeaderFooter: false,
          path: '/test/output.pdf',
        })
      );
    });
  });

  describe('generatePDFBuffer', () => {
    it('should generate PDF buffer successfully', async () => {
      const mockBuffer = Buffer.from('mock pdf content');
      mockPage.pdf.mockResolvedValue(mockBuffer);

      const result = await pdfGenerationService.generatePDFBuffer(mockMarkdown);

      expect(result).toEqual(mockBuffer);
      expect(mockPage.setContent).toHaveBeenCalled();
      expect(mockPage.pdf).toHaveBeenCalled();
      expect(mockPage.close).toHaveBeenCalled();
    });

    it('should handle PDF buffer generation failure', async () => {
      mockPage.pdf.mockRejectedValue(new Error('PDF generation failed'));

      const result = await pdfGenerationService.generatePDFBuffer(mockMarkdown);

      expect(result).toBeNull();
      expect(mockPage.close).toHaveBeenCalled();
    });

    it('should convert markdown to HTML correctly', async () => {
      const mockBuffer = Buffer.from('mock pdf content');
      mockPage.pdf.mockResolvedValue(mockBuffer);

      await pdfGenerationService.generatePDFBuffer(mockMarkdown);

      const setContentCall = mockPage.setContent.mock.calls[0][0];
      expect(setContentCall).toContain('<!DOCTYPE html>');
      expect(setContentCall).toContain('<h1>CIM Review Summary</h1>');
      expect(setContentCall).toContain('<h2>(A) Deal Overview</h2>');
      expect(setContentCall).toContain('<strong>Target Company Name:</strong>');
    });
  });

  describe('generatePDFFromHTML', () => {
    it('should generate PDF from HTML file successfully', async () => {
      mockPage.pdf.mockResolvedValue(Buffer.from('mock pdf content'));

      const result = await pdfGenerationService.generatePDFFromHTML(
        '/test/input.html',
        '/test/output.pdf'
      );

      expect(result).toBe(true);
      expect(mockPage.goto).toHaveBeenCalledWith('file:///test/input.html', {
        waitUntil: 'networkidle0',
      });
      expect(mockPage.pdf).toHaveBeenCalled();
    });

    it('should handle HTML file not found', async () => {
      mockPage.goto.mockRejectedValue(new Error('File not found'));

      const result = await pdfGenerationService.generatePDFFromHTML(
        '/test/input.html',
        '/test/output.pdf'
      );

      expect(result).toBe(false);
      expect(mockPage.close).toHaveBeenCalled();
    });
  });

  describe('generatePDFFromURL', () => {
    it('should generate PDF from URL successfully', async () => {
      mockPage.pdf.mockResolvedValue(Buffer.from('mock pdf content'));

      const result = await pdfGenerationService.generatePDFFromURL(
        'https://example.com',
        '/test/output.pdf'
      );

      expect(result).toBe(true);
      expect(mockPage.goto).toHaveBeenCalledWith('https://example.com', {
        waitUntil: 'networkidle0',
        timeout: 30000,
      });
      expect(mockPage.pdf).toHaveBeenCalled();
    });

    it('should handle URL timeout', async () => {
      mockPage.goto.mockRejectedValue(new Error('Timeout'));

      const result = await pdfGenerationService.generatePDFFromURL(
        'https://example.com',
        '/test/output.pdf'
      );

      expect(result).toBe(false);
      expect(mockPage.close).toHaveBeenCalled();
    });
  });

  describe('validatePDF', () => {
    it('should validate valid PDF file', async () => {
      const result = await pdfGenerationService.validatePDF('/test/valid.pdf');

      expect(result).toBe(true);
      expect(mockFs.readFileSync).toHaveBeenCalledWith('/test/valid.pdf');
      expect(mockFs.statSync).toHaveBeenCalledWith('/test/valid.pdf');
    });

    it('should reject invalid PDF header', async () => {
      mockFs.readFileSync.mockReturnValue(Buffer.from('INVALID PDF CONTENT'));

      const result = await pdfGenerationService.validatePDF('/test/invalid.pdf');

      expect(result).toBe(false);
    });

    it('should reject file that is too small', async () => {
      mockFs.statSync.mockReturnValue({ size: 50 } as any);

      const result = await pdfGenerationService.validatePDF('/test/small.pdf');

      expect(result).toBe(false);
    });

    it('should handle file read errors', async () => {
      mockFs.readFileSync.mockImplementation(() => {
        throw new Error('File read error');
      });

      const result = await pdfGenerationService.validatePDF('/test/error.pdf');

      expect(result).toBe(false);
    });
  });

  describe('getPDFMetadata', () => {
    it('should get PDF metadata successfully', async () => {
      const mockMetadata = {
        title: 'Test Document',
        url: 'file:///test/document.pdf',
        pageCount: 1,
      };

      mockPage.evaluate.mockResolvedValue(mockMetadata);

      const result = await pdfGenerationService.getPDFMetadata('/test/document.pdf');

      expect(result).toEqual(mockMetadata);
      expect(mockPage.goto).toHaveBeenCalledWith('file:///test/document.pdf', {
        waitUntil: 'networkidle0',
      });
    });

    it('should handle metadata retrieval failure', async () => {
      mockPage.goto.mockRejectedValue(new Error('Navigation failed'));

      const result = await pdfGenerationService.getPDFMetadata('/test/document.pdf');

      expect(result).toBeNull();
      expect(mockPage.close).toHaveBeenCalled();
    });
  });

  describe('markdown to HTML conversion', () => {
    it('should convert headers correctly', () => {
      const markdown = '# H1\n## H2\n### H3';
      const html = (pdfGenerationService as any).markdownToHTML(markdown);
      
      expect(html).toContain('<h1>H1</h1>');
      expect(html).toContain('<h2>H2</h2>');
      expect(html).toContain('<h3>H3</h3>');
    });

    it('should convert bold and italic text', () => {
      const markdown = '**bold** and *italic* text';
      const html = (pdfGenerationService as any).markdownToHTML(markdown);
      
      expect(html).toContain('<strong>bold</strong>');
      expect(html).toContain('<em>italic</em>');
    });

    it('should convert lists correctly', () => {
      const markdown = '- Item 1\n- Item 2\n- Item 3';
      const html = (pdfGenerationService as any).markdownToHTML(markdown);
      
      expect(html).toContain('<ul>');
      expect(html).toContain('<li>Item 1</li>');
      expect(html).toContain('<li>Item 2</li>');
      expect(html).toContain('<li>Item 3</li>');
      expect(html).toContain('</ul>');
    });

    it('should include proper CSS styling', () => {
      const html = (pdfGenerationService as any).markdownToHTML(mockMarkdown);
      
      expect(html).toContain('<style>');
      expect(html).toContain('font-family');
      expect(html).toContain('color: #333');
      expect(html).toContain('border-bottom');
    });

    it('should include header and footer', () => {
      const html = (pdfGenerationService as any).markdownToHTML(mockMarkdown);
      
      expect(html).toContain('<div class="header">');
      expect(html).toContain('<h1>CIM Review Summary</h1>');
      expect(html).toContain('<div class="footer">');
      expect(html).toContain('BPCP CIM Document Processor');
    });
  });

  describe('browser management', () => {
    it('should reuse browser instance', async () => {
      mockPage.pdf.mockResolvedValue(Buffer.from('mock pdf content'));

      // First call
      await pdfGenerationService.generatePDFBuffer(mockMarkdown);
      
      // Second call should reuse the same browser
      await pdfGenerationService.generatePDFBuffer(mockMarkdown);

      expect(mockPuppeteer.launch).toHaveBeenCalledTimes(1);
    });

    it('should close browser on cleanup', async () => {
      await pdfGenerationService.close();

      expect(mockBrowser.close).toHaveBeenCalled();
    });

    it('should handle browser launch failure', async () => {
      mockPuppeteer.launch.mockRejectedValue(new Error('Browser launch failed'));

      const result = await pdfGenerationService.generatePDFBuffer(mockMarkdown);

      expect(result).toBeNull();
    });
  });

  describe('error handling', () => {
    it('should handle page creation failure', async () => {
      mockBrowser.newPage.mockRejectedValue(new Error('Page creation failed'));

      const result = await pdfGenerationService.generatePDFBuffer(mockMarkdown);

      expect(result).toBeNull();
    });

    it('should handle content setting failure', async () => {
      mockPage.setContent.mockRejectedValue(new Error('Content setting failed'));

      const result = await pdfGenerationService.generatePDFBuffer(mockMarkdown);

      expect(result).toBeNull();
      expect(mockPage.close).toHaveBeenCalled();
    });

    it('should handle file system errors', async () => {
      mockFs.mkdirSync.mockImplementation(() => {
        throw new Error('Directory creation failed');
      });
      mockPage.pdf.mockResolvedValue(Buffer.from('mock pdf content'));

      const result = await pdfGenerationService.generatePDFFromMarkdown(
        mockMarkdown,
        '/test/output.pdf'
      );

      expect(result).toBe(false);
    });
  });
}); 