/**
 * Unit Tests — Format Converter (src/assembly/format-converter.ts)
 *
 * Tests HTML and DOCX generation from markdown input.
 * Covers all three style profiles (traditional, elegant, accessible)
 * and edge cases in conversion.
 */

import { describe, it, expect } from 'vitest';
import {
  convertToHtml,
  convertToDocx,
  convertToPdf,
  extractSoulBranding,
  type DocumentStyle,
  type SoulBranding,
} from '../../src/assembly/format-converter.js';

// ── Sample test documents ──────────────────────────────────────────────

const MINIMAL_MD = `# Test Document

## Section 1

This is a test document with basic content.

## Section 2

More content here with **bold** and *italic* text.
`;

const COMPLEX_MD = `# Comprehensive Contract Review

## Executive Summary

This contract review identifies **3 critical** and *5 major* findings across
the Master Service Agreement dated January 15, 2025.

## Key Findings

### 1. Liability Cap Issues

The contract contains an unlimited liability clause in Section 4.2 which
states that "Vendor shall be liable for all direct and indirect damages."

| Issue | Severity | Section |
|-------|----------|---------|
| Unlimited liability | Critical | 4.2 |
| Missing indemnification | Major | 5.1 |
| No IP assignment | Major | 7.3 |

### 2. Termination Provisions

The termination clause allows for immediate termination without cause,
which is unusual for agreements of this type.

> "Either party may terminate this Agreement at any time upon written notice."
> — Section 8.1

### 3. Data Protection

- No data processing addendum (DPA) attached
- No reference to GDPR compliance
- Missing data retention policy

## Recommendations

1. Cap liability at **12 months of fees paid**
2. Add mutual indemnification clause
3. Require 30-day notice for termination without cause
4. Attach a DPA compliant with GDPR Article 28

---

*Generated by Lavern Legal Design System*
`;

const EMPTY_MD = '';
const HEADING_ONLY_MD = '# Just a Title';

// ── extractSoulBranding ────────────────────────────────────────────────

describe('extractSoulBranding', () => {
  it('returns undefined for empty/null input', () => {
    expect(extractSoulBranding()).toBeUndefined();
    expect(extractSoulBranding('')).toBeUndefined();
    expect(extractSoulBranding('   ')).toBeUndefined();
  });

  it('extracts firm name from "We are FIRM" pattern', () => {
    const result = extractSoulBranding('We are Baker & Associates.\nWe provide excellent legal service.');
    expect(result?.firmName).toBe('Baker & Associates');
  });

  it('extracts firm name from "At FIRM, we..." pattern', () => {
    const result = extractSoulBranding('At Lavern Legal, we believe in innovation.');
    expect(result?.firmName).toBe('Lavern Legal');
  });

  it('extracts firm name from "FIRM is..." pattern', () => {
    const result = extractSoulBranding('Sterling & Partners provides cutting-edge legal design.');
    expect(result?.firmName).toBe('Sterling & Partners');
  });

  it('uses first short line as firm name if no pattern matches', () => {
    const result = extractSoulBranding('Lavern LLP\nExcellence in legal design.');
    expect(result?.firmName).toBe('Lavern LLP');
  });

  it('skips first line starting with common words', () => {
    const result = extractSoulBranding('We value integrity above all.');
    // "We value..." starts with "we", so it won't be treated as firm name from strategy 2
    // But it doesn't match strategy 1 patterns either
    expect(result).toBeUndefined();
  });
});

// ── convertToHtml ──────────────────────────────────────────────────────

describe('convertToHtml', () => {
  const styles: DocumentStyle[] = ['traditional', 'elegant', 'accessible'];

  it('produces valid HTML with doctype and charset', () => {
    const html = convertToHtml(MINIMAL_MD, 'Test');
    expect(html).toContain('<!DOCTYPE html>');
    expect(html).toContain('<meta charset="UTF-8">');
    expect(html).toContain('<html lang="en">');
  });

  it('includes the document title in <title> and content', () => {
    const html = convertToHtml(MINIMAL_MD, 'My Document');
    expect(html).toContain('<title>My Document</title>');
  });

  it('renders markdown headings as HTML headings', () => {
    const html = convertToHtml(MINIMAL_MD, 'Test');
    expect(html).toContain('<h1');
    expect(html).toContain('<h2');
  });

  it('renders bold and italic text', () => {
    const html = convertToHtml(MINIMAL_MD, 'Test');
    expect(html).toContain('<strong>');
    expect(html).toContain('<em>');
  });

  it('renders tables in complex documents', () => {
    const html = convertToHtml(COMPLEX_MD, 'Review');
    expect(html).toContain('<table>');
    expect(html).toContain('<th');
    expect(html).toContain('<td');
  });

  it('renders blockquotes', () => {
    const html = convertToHtml(COMPLEX_MD, 'Review');
    expect(html).toContain('<blockquote>');
  });

  it('renders ordered and unordered lists', () => {
    const html = convertToHtml(COMPLEX_MD, 'Review');
    expect(html).toContain('<ol>');
    expect(html).toContain('<ul>');
    expect(html).toContain('<li>');
  });

  it('includes print-optimized CSS with @media print', () => {
    const html = convertToHtml(MINIMAL_MD, 'Test');
    expect(html).toContain('@media print');
    expect(html).toContain('page-break');
  });

  it('includes footer with Lavern branding', () => {
    const html = convertToHtml(MINIMAL_MD, 'Test');
    expect(html).toContain('Lavern');
    expect(html).toContain('doc-footer');
  });

  it('uses custom branding when provided', () => {
    const branding: SoulBranding = { firmName: 'Acme Legal', tagline: 'We litigate.' };
    const html = convertToHtml(MINIMAL_MD, 'Test', undefined, branding);
    expect(html).toContain('Acme Legal');
    expect(html).toContain('Powered by Lavern');
  });

  it('escapes HTML entities in title and branding', () => {
    const html = convertToHtml(MINIMAL_MD, 'A & B <script>alert(1)</script>');
    expect(html).not.toContain('<script>');
    expect(html).toContain('&amp;');
    expect(html).toContain('&lt;script&gt;');
  });

  for (const style of styles) {
    it(`renders with ${style} style without errors`, () => {
      const html = convertToHtml(COMPLEX_MD, 'Review', style);
      expect(html).toBeTruthy();
      expect(html.length).toBeGreaterThan(1000);
      expect(html).toContain('<!DOCTYPE html>');
    });
  }

  it('adds scope="col" to table headers for accessible style', () => {
    const html = convertToHtml(COMPLEX_MD, 'Review', 'accessible');
    expect(html).toContain('scope="col"');
  });

  it('includes Google Fonts import for elegant style', () => {
    const html = convertToHtml(MINIMAL_MD, 'Test', 'elegant');
    expect(html).toContain('fonts.googleapis.com');
  });

  it('handles empty markdown gracefully', () => {
    const html = convertToHtml(EMPTY_MD, 'Empty');
    expect(html).toContain('<!DOCTYPE html>');
    expect(html).toContain('<title>Empty</title>');
  });

  it('handles heading-only markdown', () => {
    const html = convertToHtml(HEADING_ONLY_MD, 'Title Only');
    expect(html).toContain('<h1');
  });

  it('includes cover page section', () => {
    const html = convertToHtml(MINIMAL_MD, 'Test');
    expect(html).toContain('class="cover"');
  });

  // ── XSS Sanitization (body markdown) ─────────────────────────────────

  it('strips script tags from markdown body', () => {
    const xssMd = '# Title\n\n<script>alert("xss")</script>\n\nSafe content.';
    const html = convertToHtml(xssMd, 'Test');
    expect(html).not.toContain('<script>');
    expect(html).not.toContain('alert("xss")');
    expect(html).toContain('Safe content.');
  });

  it('strips iframe tags from markdown body', () => {
    const xssMd = '# Title\n\n<iframe src="https://evil.com"></iframe>\n\nSafe.';
    const html = convertToHtml(xssMd, 'Test');
    expect(html).not.toContain('<iframe');
    expect(html).toContain('Safe.');
  });

  it('strips event handler attributes', () => {
    const xssMd = '# Title\n\n<img src=x onerror="alert(1)">\n\nSafe.';
    const html = convertToHtml(xssMd, 'Test');
    expect(html).not.toContain('onerror');
    expect(html).not.toContain('alert(1)');
  });

  it('strips javascript: URLs from links', () => {
    const xssMd = '# Title\n\n<a href="javascript:alert(1)">click</a>\n\nSafe.';
    const html = convertToHtml(xssMd, 'Test');
    expect(html).not.toContain('javascript:');
  });

  it('strips data: URLs from src attributes', () => {
    const xssMd = '# Title\n\n<img src="data:text/html,<script>alert(1)</script>">\n\nSafe.';
    const html = convertToHtml(xssMd, 'Test');
    expect(html).not.toContain('data:text/html');
  });

  it('strips object and embed tags', () => {
    const xssMd = '# Title\n\n<object data="evil.swf"></object><embed src="evil.swf">\n\nSafe.';
    const html = convertToHtml(xssMd, 'Test');
    expect(html).not.toContain('<object');
    expect(html).not.toContain('<embed');
  });

  it('strips form tags', () => {
    const xssMd = '# Title\n\n<form action="https://evil.com"><input></form>\n\nSafe.';
    const html = convertToHtml(xssMd, 'Test');
    expect(html).not.toContain('<form');
  });

  it('preserves safe HTML elements (tables, lists, links)', () => {
    const safeMd = '# Title\n\n| A | B |\n|---|---|\n| 1 | 2 |\n\n- item\n\n[link](https://safe.com)';
    const html = convertToHtml(safeMd, 'Test');
    expect(html).toContain('<table>');
    expect(html).toContain('<ul>');
    expect(html).toContain('href="https://safe.com"');
  });
});

// ── convertToDocx ──────────────────────────────────────────────────────

describe('convertToDocx', () => {
  const styles: DocumentStyle[] = ['traditional', 'elegant', 'accessible'];

  it('produces a Buffer', async () => {
    const buffer = await convertToDocx(MINIMAL_MD, 'Test');
    expect(buffer).toBeInstanceOf(Buffer);
    expect(buffer.length).toBeGreaterThan(0);
  });

  it('produces valid DOCX (ZIP magic bytes)', async () => {
    const buffer = await convertToDocx(MINIMAL_MD, 'Test');
    // DOCX files are ZIP archives: first 4 bytes are PK\x03\x04
    expect(buffer[0]).toBe(0x50); // P
    expect(buffer[1]).toBe(0x4B); // K
    expect(buffer[2]).toBe(0x03);
    expect(buffer[3]).toBe(0x04);
  });

  for (const style of styles) {
    it(`produces DOCX with ${style} style`, async () => {
      const buffer = await convertToDocx(COMPLEX_MD, 'Review', style);
      expect(buffer).toBeInstanceOf(Buffer);
      expect(buffer.length).toBeGreaterThan(1000);
      // Verify ZIP magic bytes
      expect(buffer[0]).toBe(0x50);
      expect(buffer[1]).toBe(0x4B);
    });
  }

  it('handles complex markdown with tables, lists, blockquotes', async () => {
    const buffer = await convertToDocx(COMPLEX_MD, 'Complex Review');
    expect(buffer).toBeInstanceOf(Buffer);
    expect(buffer.length).toBeGreaterThan(5000);
  });

  it('includes branding in DOCX', async () => {
    const branding: SoulBranding = { firmName: 'Acme Legal' };
    const buffer = await convertToDocx(MINIMAL_MD, 'Test', undefined, branding);
    expect(buffer).toBeInstanceOf(Buffer);
    expect(buffer.length).toBeGreaterThan(0);
  });

  it('handles empty markdown', async () => {
    const buffer = await convertToDocx(EMPTY_MD, 'Empty');
    expect(buffer).toBeInstanceOf(Buffer);
    // Should still produce a valid DOCX with at least headers/footers
    expect(buffer[0]).toBe(0x50);
  });

  it('handles heading-only markdown', async () => {
    const buffer = await convertToDocx(HEADING_ONLY_MD, 'Title');
    expect(buffer).toBeInstanceOf(Buffer);
    expect(buffer.length).toBeGreaterThan(0);
  });

  it('handles markdown with special characters', async () => {
    const md = '# Contract for A & B\n\n## Terms > Conditions\n\nPrice is < $100 per "unit".';
    const buffer = await convertToDocx(md, 'Special Chars');
    expect(buffer).toBeInstanceOf(Buffer);
    expect(buffer.length).toBeGreaterThan(0);
  });

  it('handles deeply nested headings', async () => {
    const md = `# H1\n## H2\n### H3\n#### H4\n##### H5\n###### H6\n\nContent at every level.`;
    const buffer = await convertToDocx(md, 'Deep Nesting');
    expect(buffer).toBeInstanceOf(Buffer);
  });
});

// ── convertToPdf ───────────────────────────────────────────────────────

describe('convertToPdf', () => {
  it('produces a result object with buffer and isRealPdf flag', async () => {
    const result = await convertToPdf(MINIMAL_MD, 'Test');
    expect(result).toHaveProperty('buffer');
    expect(result).toHaveProperty('isRealPdf');
    expect(result.buffer).toBeInstanceOf(Buffer);
    expect(result.buffer.length).toBeGreaterThan(0);
  }, 60_000);

  it('generates content for complex documents', async () => {
    const result = await convertToPdf(COMPLEX_MD, 'Review', 'traditional');
    expect(result.buffer.length).toBeGreaterThan(1000);
  }, 60_000);

  it('falls back gracefully if Puppeteer is unavailable', async () => {
    // In test environment, Puppeteer might or might not be available
    // Either way, the function should not throw
    const result = await convertToPdf(MINIMAL_MD, 'Test');
    expect(result.buffer).toBeInstanceOf(Buffer);

    if (result.isRealPdf) {
      // Real PDF starts with %PDF
      expect(result.buffer.toString('ascii', 0, 4)).toBe('%PDF');
    } else {
      // Fallback is HTML
      expect(result.buffer.toString('utf-8')).toContain('<!DOCTYPE html>');
    }
  }, 60_000);
});
