import { promises as fs } from 'node:fs';
import path from 'node:path';
import os from 'node:os';

import { afterAll, beforeAll, describe, expect, it } from 'vitest';
import { PDFDocument } from 'pdf-lib';

import { fillEoiFormFields, loadEoiTemplatePdf } from '@/lib/pdf/fill-eoi-form';
import type { EoiContext } from '@/lib/services/eoi-context';

// ─── Test PDF builder (synthetic source PDF with the same field names) ───────

async function buildSyntheticEoiPdf(): Promise<Uint8Array> {
  const doc = await PDFDocument.create();
  const page = doc.addPage([600, 800]);
  const form = doc.getForm();

  const textFieldNames = [
    'Name',
    'Email',
    'Address',
    'Yacht Name',
    'Length',
    'Width',
    'Draft',
    'Berth Number',
  ];
  textFieldNames.forEach((name, i) => {
    const f = form.createTextField(name);
    f.addToPage(page, { x: 50, y: 700 - i * 40, width: 300, height: 24 });
  });

  for (const name of ['Lease_10', 'Purchase']) {
    const cb = form.createCheckBox(name);
    cb.addToPage(page, { x: 400, y: 700 - (name === 'Purchase' ? 0 : 40), width: 12, height: 12 });
  }

  return doc.save();
}

function makeContext(overrides: Partial<EoiContext> = {}): EoiContext {
  return {
    client: {
      id: 'client-test-1',
      fullName: 'Alice Smith',
      nationality: 'US',
      primaryEmail: 'alice@example.com',
      primaryPhone: '+1-555-0100',
      address: {
        street: '123 Main St',
        city: 'Austin',
        subdivision: 'TX',
        postalCode: '78701',
        country: 'United States',
        countryIso: 'US',
      },
    },
    yacht: {
      id: 'yacht-test-1',
      name: 'Sea Breeze',
      lengthFt: '45',
      widthFt: '14',
      draftFt: '6',
      lengthM: null,
      widthM: null,
      draftM: null,
      lengthUnit: 'ft' as const,
      widthUnit: 'ft' as const,
      draftUnit: 'ft' as const,
      hullNumber: 'HN-1',
      flag: 'US',
      yearBuilt: 2020,
    },
    company: null,
    owner: { type: 'client', name: 'Alice Smith' },
    berth: {
      mooringNumber: 'A12',
      area: 'North',
      lengthFt: '50',
      price: '1000',
      priceCurrency: 'USD',
      tenureType: 'permanent',
    },
    eoiBerthRange: '',
    interest: { stage: 'open', leadCategory: null, dateFirstContact: null, notes: null },
    port: { name: 'Port Nimara', defaultCurrency: 'USD' },
    date: { today: '2026-04-26', year: '2026' },
    ...overrides,
  };
}

// ─── fillEoiFormFields ────────────────────────────────────────────────────────

describe('fillEoiFormFields', () => {
  it('flattens the form and writes metadata (Title/Author/Lang)', async () => {
    const sourcePdf = await buildSyntheticEoiPdf();
    const filled = await fillEoiFormFields(sourcePdf, makeContext());

    const out = await PDFDocument.load(filled);
    // Flatten removes the interactive fields from the form - the values
    // are baked into the page content stream so the signer can't edit
    // them after the fact.
    expect(out.getForm().getFields()).toEqual([]);

    // PDF metadata (M-1). pdf-lib doesn't expose `getLanguage`, but the
    // other setters round-trip through standard PDF info-dict getters.
    expect(out.getTitle()).toBe('EOI – Alice Smith');
    expect(out.getAuthor()).toBe('Port Nimara');
    expect(out.getSubject()).toBe('Expression of Interest');
  });

  it('produces a non-empty PDF and a single page with the synthetic source', async () => {
    const sourcePdf = await buildSyntheticEoiPdf();
    const filled = await fillEoiFormFields(sourcePdf, makeContext());

    // Round-trip the saved bytes. With the AcroForm flattened, the doc
    // still loads as a valid PDF and retains the original page count -
    // the text-field widgets have been baked into the content stream.
    const out = await PDFDocument.load(filled);
    expect(out.getPageCount()).toBe(1);
    expect(filled.byteLength).toBeGreaterThan(500);
  });

  it('handles null primary email and null address gracefully', async () => {
    const sourcePdf = await buildSyntheticEoiPdf();
    const filled = await fillEoiFormFields(
      sourcePdf,
      makeContext({
        client: {
          id: 'client-test-2',
          fullName: 'Bob',
          nationality: null,
          primaryEmail: null,
          primaryPhone: null,
          address: null,
        },
      }),
    );

    // Still flattens cleanly and round-trips as a valid PDF even with
    // null email/address - the doc doesn't error out.
    const out = await PDFDocument.load(filled);
    expect(out.getForm().getFields()).toEqual([]);
    expect(out.getTitle()).toBe('EOI – Bob');
  });

  it('skips fields silently if the source PDF lacks them', async () => {
    // Build a PDF with only a subset of fields and ensure no error.
    const doc = await PDFDocument.create();
    const page = doc.addPage([600, 800]);
    const form = doc.getForm();
    const f = form.createTextField('Name');
    f.addToPage(page, { x: 50, y: 700, width: 300, height: 24 });
    const sparse = await doc.save();

    await expect(fillEoiFormFields(sparse, makeContext())).resolves.toBeInstanceOf(Uint8Array);
  });
});

// ─── loadEoiTemplatePdf ───────────────────────────────────────────────────────

describe('loadEoiTemplatePdf', () => {
  let tmpFile: string;
  const originalEnv = process.env.EOI_TEMPLATE_PDF_PATH;

  beforeAll(async () => {
    const sourcePdf = await buildSyntheticEoiPdf();
    tmpFile = path.join(os.tmpdir(), `eoi-template-${Date.now()}.pdf`);
    await fs.writeFile(tmpFile, sourcePdf);
  });

  afterAll(async () => {
    if (originalEnv === undefined) delete process.env.EOI_TEMPLATE_PDF_PATH;
    else process.env.EOI_TEMPLATE_PDF_PATH = originalEnv;
    await fs.unlink(tmpFile).catch(() => undefined);
  });

  it('reads the PDF from EOI_TEMPLATE_PDF_PATH override', async () => {
    process.env.EOI_TEMPLATE_PDF_PATH = tmpFile;
    const bytes = await loadEoiTemplatePdf();
    expect(bytes.byteLength).toBeGreaterThan(100);
    // Round-trip: should re-load as a valid PDF.
    await expect(PDFDocument.load(bytes)).resolves.toBeInstanceOf(PDFDocument);
  });

  it('throws a clear error with instructions when the file is missing', async () => {
    process.env.EOI_TEMPLATE_PDF_PATH = '/nope/does-not-exist.pdf';
    await expect(loadEoiTemplatePdf()).rejects.toThrow(/EOI source PDF not found/);
  });
});
