import fs from 'node:fs';
import path from 'node:path';
import { nanoid } from '@documenso/lib/universal/id';
import { prisma } from '@documenso/prisma';
import { expect, type Page, test } from '@playwright/test';

import {
  addEnvelopeItemPdf,
  clickAddMyselfButton,
  clickEnvelopeEditorStep,
  getEnvelopeEditorSettingsTrigger,
  openDocumentEnvelopeEditor,
  openEmbeddedEnvelopeEditor,
  openTemplateEnvelopeEditor,
  persistEmbeddedEnvelope,
  setRecipientEmail,
  setRecipientName,
  type TEnvelopeEditorSurface,
} from '../fixtures/envelope-editor';
import { expectToastTextToBeVisible } from '../fixtures/generic';

test.use({
  storageState: {
    cookies: [],
    origins: [],
  },
});

const examplePdfBuffer = fs.readFileSync(path.join(__dirname, '../../../../assets/example.pdf'));

const multiPagePdfBuffer = fs.readFileSync(path.join(__dirname, '../../../../assets/field-font-alignment.pdf'));

// --- Shared helpers ---

const openSettingsDialog = async (root: Page) => {
  await getEnvelopeEditorSettingsTrigger(root).click();
  await expect(root.getByRole('heading', { name: 'Document Settings' })).toBeVisible();
};

const updateExternalId = async (surface: TEnvelopeEditorSurface, externalId: string) => {
  await openSettingsDialog(surface.root);
  await surface.root.locator('input[name="externalId"]').fill(externalId);
  await surface.root.getByRole('button', { name: 'Update' }).click();

  if (!surface.isEmbedded) {
    await expectToastTextToBeVisible(surface.root, 'Envelope updated');
  }
};

const getEditButton = (root: Page, index: number) =>
  root.locator('[data-testid^="envelope-item-edit-button-"]').nth(index);

const getEditDialog = (root: Page) => root.getByRole('dialog');

const getEditDialogTitleInput = (root: Page) => root.locator('[data-testid="envelope-item-edit-title-input"]');

const getEditDialogDropzone = (root: Page) => root.locator('[data-testid="envelope-item-edit-dropzone"]');

const getEditDialogSelectedFile = (root: Page) => root.locator('[data-testid="envelope-item-edit-selected-file"]');

const getEditDialogClearFileButton = (root: Page) => root.locator('[data-testid="envelope-item-edit-clear-file"]');

const getEditDialogUpdateButton = (root: Page) => root.locator('[data-testid="envelope-item-edit-update-button"]');

const assertPdfPageCount = async (root: Page, expectedCount: number) => {
  await expect(root.locator('[data-pdf-content]').first()).toHaveAttribute('data-page-count', String(expectedCount), {
    timeout: 15000,
  });
};

const navigateToFieldsPage = async (surface: TEnvelopeEditorSurface) => {
  // Set up a recipient first so the fields page is functional.
  if (surface.isEmbedded) {
    await setRecipientEmail(surface.root, 0, `test-${nanoid(4)}@example.com`);
    await setRecipientName(surface.root, 0, 'Test User');
  } else {
    await clickAddMyselfButton(surface.root);
  }

  await clickEnvelopeEditorStep(surface.root, 'addFields');

  // Wait for the file selector to be visible on the fields page.
  await expect(getEditButton(surface.root, 0)).toBeAttached({ timeout: 10000 });
};

const openEditDialogOnFieldsPage = async (root: Page, index = 0) => {
  // Hover to reveal the edit button, then click it.
  const editButton = getEditButton(root, index);
  await editButton.click({ force: true });
  await expect(getEditDialog(root)).toBeVisible();
};

// --- Flows ---

const runRenameFlow = async (surface: TEnvelopeEditorSurface) => {
  const externalId = `e2e-edit-rename-${nanoid()}`;

  if (surface.isEmbedded && !surface.envelopeId) {
    await addEnvelopeItemPdf(surface.root, 'rename-test.pdf');
  }

  await updateExternalId(surface, externalId);
  await navigateToFieldsPage(surface);

  // Open edit dialog and change the title.
  await openEditDialogOnFieldsPage(surface.root);

  const titleInput = getEditDialogTitleInput(surface.root);
  await expect(titleInput).toBeVisible();

  await titleInput.clear();
  await titleInput.fill('Renamed Document');

  // Update button should be disabled without a replacement file.
  await expect(getEditDialogUpdateButton(surface.root)).toBeDisabled();

  // A replacement file is required for the Update button to be enabled.
  const dropzone = getEditDialogDropzone(surface.root);
  await expect(dropzone).toBeVisible();

  const fileInput = dropzone.locator('input[type="file"]');
  await fileInput.setInputFiles({
    name: 'example.pdf',
    mimeType: 'application/pdf',
    buffer: examplePdfBuffer,
  });

  await expect(getEditDialogSelectedFile(surface.root)).toBeVisible();

  await getEditDialogUpdateButton(surface.root).click();

  // Dialog should close.
  await expect(getEditDialog(surface.root)).not.toBeVisible({ timeout: 10000 });

  return { externalId };
};

const runReplacePdfFlow = async (surface: TEnvelopeEditorSurface) => {
  const externalId = `e2e-edit-replace-${nanoid()}`;

  if (surface.isEmbedded && !surface.envelopeId) {
    await addEnvelopeItemPdf(surface.root, 'replace-test.pdf');
  }

  await updateExternalId(surface, externalId);
  await navigateToFieldsPage(surface);

  // First, assert the page count is 1 (example.pdf has 1 page).
  await assertPdfPageCount(surface.root, 1);

  // Open edit dialog.
  await openEditDialogOnFieldsPage(surface.root);

  // Select a multi-page PDF via the dropzone.
  const dropzone = getEditDialogDropzone(surface.root);
  await expect(dropzone).toBeVisible();

  const fileInput = dropzone.locator('input[type="file"]');
  await fileInput.setInputFiles({
    name: 'field-font-alignment.pdf',
    mimeType: 'application/pdf',
    buffer: multiPagePdfBuffer,
  });

  // Verify file is shown in the selected file display.
  await expect(getEditDialogSelectedFile(surface.root)).toBeVisible();

  // Click Update.
  await getEditDialogUpdateButton(surface.root).click();

  // Dialog should close.
  await expect(getEditDialog(surface.root)).not.toBeVisible({ timeout: 15000 });

  // After replacement, the page count should be 3 (field-font-alignment.pdf has 3 pages).
  await assertPdfPageCount(surface.root, 3);

  return { externalId };
};

const runClearFileSelectionFlow = async (surface: TEnvelopeEditorSurface) => {
  if (surface.isEmbedded && !surface.envelopeId) {
    await addEnvelopeItemPdf(surface.root, 'clear-file-test.pdf');
  }

  await navigateToFieldsPage(surface);

  // Open edit dialog.
  await openEditDialogOnFieldsPage(surface.root);

  // Select a file.
  const dropzone = getEditDialogDropzone(surface.root);
  await expect(dropzone).toBeVisible();

  const fileInput = dropzone.locator('input[type="file"]');
  await fileInput.setInputFiles({
    name: 'to-be-cleared.pdf',
    mimeType: 'application/pdf',
    buffer: examplePdfBuffer,
  });

  // Verify file appears.
  await expect(getEditDialogSelectedFile(surface.root)).toBeVisible();
  await expect(getEditDialogDropzone(surface.root)).not.toBeVisible();

  // Click X to clear.
  await getEditDialogClearFileButton(surface.root).click();

  // Dropzone should reappear.
  await expect(getEditDialogDropzone(surface.root)).toBeVisible();
  await expect(getEditDialogSelectedFile(surface.root)).not.toBeVisible();
};

// --- DB assertion ---

const assertRenamePersistedInDatabase = async ({
  surface,
  externalId,
}: {
  surface: TEnvelopeEditorSurface;
  externalId: string;
}) => {
  const envelope = await prisma.envelope.findFirstOrThrow({
    where: {
      externalId,
      userId: surface.userId,
      teamId: surface.teamId,
      type: surface.envelopeType,
    },
    include: {
      envelopeItems: {
        orderBy: { order: 'asc' },
      },
    },
    orderBy: { createdAt: 'desc' },
  });

  expect(envelope.envelopeItems.length).toBeGreaterThanOrEqual(1);
  expect(envelope.envelopeItems[0].title).toBe('Renamed Document');
};

// --- Tests ---

test.describe('document editor', () => {
  test('should rename an envelope item from the fields page', async ({ page }) => {
    const surface = await openDocumentEnvelopeEditor(page);
    const result = await runRenameFlow(surface);

    await assertRenamePersistedInDatabase({ surface, ...result });
  });

  test('should replace a PDF from the fields page', async ({ page }) => {
    const surface = await openDocumentEnvelopeEditor(page);
    await runReplacePdfFlow(surface);
  });

  test('should clear a selected file before submitting', async ({ page }) => {
    const surface = await openDocumentEnvelopeEditor(page);
    await runClearFileSelectionFlow(surface);
  });
});

test.describe('template editor', () => {
  test('should rename an envelope item from the fields page', async ({ page }) => {
    const surface = await openTemplateEnvelopeEditor(page);
    const result = await runRenameFlow(surface);

    await assertRenamePersistedInDatabase({ surface, ...result });
  });
});

test.describe('embedded create', () => {
  test('should rename an envelope item from the fields page', async ({ page }) => {
    const surface = await openEmbeddedEnvelopeEditor(page, {
      envelopeType: 'DOCUMENT',
      tokenNamePrefix: 'e2e-edit-dialog-create',
    });

    const result = await runRenameFlow(surface);

    await clickEnvelopeEditorStep(surface.root, 'upload');
    await persistEmbeddedEnvelope(surface);

    await assertRenamePersistedInDatabase({ surface, ...result });
  });

  test('should replace a PDF from the fields page', async ({ page }) => {
    const surface = await openEmbeddedEnvelopeEditor(page, {
      envelopeType: 'DOCUMENT',
      tokenNamePrefix: 'e2e-edit-dialog-replace',
    });

    await runReplacePdfFlow(surface);
  });
});

test.describe('embedded edit', () => {
  test('should rename an envelope item from the fields page', async ({ page }) => {
    const surface = await openEmbeddedEnvelopeEditor(page, {
      envelopeType: 'TEMPLATE',
      mode: 'edit',
      tokenNamePrefix: 'e2e-edit-dialog-edit',
    });

    const result = await runRenameFlow(surface);

    await clickEnvelopeEditorStep(surface.root, 'upload');
    await persistEmbeddedEnvelope(surface);

    await assertRenamePersistedInDatabase({ surface, ...result });
  });
});
