import path from 'node:path';
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
import { createEmbeddingPresignToken } from '@documenso/lib/server-only/embedding-presign/create-embedding-presign-token';
import { createApiToken } from '@documenso/lib/server-only/public-api/create-api-token';
import { prefixedId } from '@documenso/lib/universal/id';
import { mapSecondaryIdToDocumentId, mapSecondaryIdToTemplateId } from '@documenso/lib/utils/envelope';
import { formatDirectTemplatePath } from '@documenso/lib/utils/templates';
import { prisma } from '@documenso/prisma';
import {
  seedBlankDocument,
  seedCompletedDocument,
  seedPendingDocumentWithFullFields,
} from '@documenso/prisma/seed/documents';
import { seedBlankTemplate, seedDirectTemplate } from '@documenso/prisma/seed/templates';
import { seedUser } from '@documenso/prisma/seed/users';
import { expect, test } from '@playwright/test';
import { FieldType } from '@prisma/client';

import { apiSignin } from '../fixtures/authentication';

export const PDF_PAGE_SELECTOR = 'img[data-page-number]';

async function addSecondEnvelopeItem(envelopeId: string) {
  const firstItem = await prisma.envelopeItem.findFirstOrThrow({
    where: { envelopeId },
    orderBy: { order: 'asc' },
    include: { documentData: true },
  });

  const newDocumentData = await prisma.documentData.create({
    data: {
      type: firstItem.documentData.type,
      data: firstItem.documentData.data,
      initialData: firstItem.documentData.initialData,
    },
  });

  await prisma.envelopeItem.create({
    data: {
      id: prefixedId('envelope_item'),
      title: `${firstItem.title} - Page 2`,
      documentDataId: newDocumentData.id,
      order: 2,
      envelopeId,
    },
  });
}

test.describe('PDF Viewer Rendering', () => {
  test.describe('Authenticated Pages', () => {
    test('should render PDF on all authenticated pages (V1 and V2)', async ({ page }) => {
      const { user, team } = await seedUser();

      const documentV1 = await seedBlankDocument(user, team.id);
      const documentV2 = await seedBlankDocument(user, team.id, { internalVersion: 2 });
      await addSecondEnvelopeItem(documentV2.id);

      const templateV1 = await seedBlankTemplate(user, team.id);
      const templateV2 = await seedBlankTemplate(user, team.id, {
        createTemplateOptions: { internalVersion: 2 },
      });
      await addSecondEnvelopeItem(templateV2.id);

      await apiSignin({
        page,
        email: user.email,
        redirectPath: `/t/${team.url}/documents/${documentV1.id}`,
      });

      await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 });

      await page.goto(`/t/${team.url}/documents/${documentV2.id}`);
      await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 });
      await page.getByRole('button', { name: /Page 2/ }).click();
      await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 });

      await page.goto(`/t/${team.url}/templates/${templateV1.id}`);
      await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 });

      await page.goto(`/t/${team.url}/templates/${templateV2.id}`);
      await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 });
      await page.getByRole('button', { name: /Page 2/ }).click();
      await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 });

      await page.goto(`/t/${team.url}/documents/${documentV1.id}/edit`);
      await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 });

      await page.goto(`/t/${team.url}/documents/${documentV2.id}/edit?step=addFields`);
      await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 });
      await page.getByRole('button', { name: /Page 2/ }).click();
      await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 });

      await page.goto(`/t/${team.url}/templates/${templateV1.id}/edit`);
      await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 });

      await page.goto(`/t/${team.url}/templates/${templateV2.id}/edit?step=addFields`);
      await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 });
      await page.getByRole('button', { name: /Page 2/ }).click();
      await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 });

      await page.goto(`/t/${team.url}/documents/${documentV1.id}`);
      await page.locator(PDF_PAGE_SELECTOR).first().waitFor({ state: 'visible', timeout: 30_000 });
    });
  });

  test.describe('Recipient Signing', () => {
    test('should render PDF on signing page (V1 and V2)', async ({ page }) => {
      const { user, team } = await seedUser();

      const { recipients: recipientsV1 } = await seedPendingDocumentWithFullFields({
        owner: user,
        teamId: team.id,
        recipients: ['signer-v1@test.documenso.com'],
        fields: [FieldType.SIGNATURE],
      });

      const { document: documentV2, recipients: recipientsV2 } = await seedPendingDocumentWithFullFields({
        owner: user,
        teamId: team.id,
        recipients: ['signer-v2@test.documenso.com'],
        fields: [FieldType.SIGNATURE],
        updateDocumentOptions: { internalVersion: 2 },
      });
      await addSecondEnvelopeItem(documentV2.id);

      await page.goto(`/sign/${recipientsV1[0].token}`);
      await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 });

      await page.goto(`/sign/${recipientsV2[0].token}`);
      await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 });
      await page.getByRole('button', { name: /Page 2/ }).click();
      await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 });
    });
  });

  test.describe('Direct Template', () => {
    test('should render PDF on direct template page (V1 and V2)', async ({ page }) => {
      const { user, team } = await seedUser();

      const templateV1 = await seedDirectTemplate({
        title: 'PDF Viewer Test Template V1',
        userId: user.id,
        teamId: team.id,
      });

      const templateV2 = await seedDirectTemplate({
        title: 'PDF Viewer Test Template V2',
        userId: user.id,
        teamId: team.id,
        internalVersion: 2,
      });
      await addSecondEnvelopeItem(templateV2.id);

      await page.goto(formatDirectTemplatePath(templateV1.directLink?.token || ''));
      await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 });

      await page.goto(formatDirectTemplatePath(templateV2.directLink?.token || ''));
      await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 });
      await page.getByRole('button', { name: /Page 2/ }).click();
      await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 });
    });
  });

  test.describe('Share Page', () => {
    test('should render PDF on share page (V1 and V2)', async ({ page }) => {
      const { user, team } = await seedUser();

      const qrTokenV1 = prefixedId('qr');
      const qrTokenV2 = prefixedId('qr');

      const documentV1 = await seedCompletedDocument(user, team.id, ['share-v1@test.documenso.com'], {
        createDocumentOptions: { qrToken: qrTokenV1 },
      });

      const documentV2 = await seedCompletedDocument(user, team.id, ['share-v2@test.documenso.com'], {
        createDocumentOptions: { qrToken: qrTokenV2 },
        internalVersion: 2,
      });
      await addSecondEnvelopeItem(documentV2.id);

      await page.goto(`${NEXT_PUBLIC_WEBAPP_URL()}/share/${qrTokenV1}`);
      await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 });

      await page.goto(`${NEXT_PUBLIC_WEBAPP_URL()}/share/${qrTokenV2}`);
      await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 });

      await page.getByRole('button', { name: /Page 2/ }).click();
      await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 });
    });

    test('should not return 500 for invalid QR share token', async ({ page }) => {
      const response = await page.request.get('/share/qr_invalid_token_for_regression_check', {
        maxRedirects: 0,
      });

      expect(response.status()).toBe(302);
      expect(response.headers().location).toBe('/');
    });
  });

  test.describe('Embed Pages', () => {
    test('should render PDF on embed sign page (V1 and V2)', async ({ page }) => {
      const { user, team } = await seedUser();

      const { recipients: recipientsV1 } = await seedPendingDocumentWithFullFields({
        owner: user,
        teamId: team.id,
        recipients: ['embed-signer-v1@test.documenso.com'],
        fields: [FieldType.SIGNATURE],
      });

      const { document: documentV2, recipients: recipientsV2 } = await seedPendingDocumentWithFullFields({
        owner: user,
        teamId: team.id,
        recipients: ['embed-signer-v2@test.documenso.com'],
        fields: [FieldType.SIGNATURE],
        updateDocumentOptions: { internalVersion: 2 },
      });
      await addSecondEnvelopeItem(documentV2.id);

      await page.goto(`/embed/sign/${recipientsV1[0].token}`);
      await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 });

      await page.goto(`/embed/sign/${recipientsV2[0].token}`);
      await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 });

      // Todo: Multisign does not support multiple envelope items.
      // await page.getByRole('button', { name: /Page 2/ }).click();
      // await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 });
    });

    test('should render PDF on embed direct template page (V1 and V2)', async ({ page }) => {
      const { user, team } = await seedUser();

      const templateV1 = await seedDirectTemplate({
        title: 'Embed Direct Template V1',
        userId: user.id,
        teamId: team.id,
      });

      const templateV2 = await seedDirectTemplate({
        title: 'Embed Direct Template V2',
        userId: user.id,
        teamId: team.id,
        internalVersion: 2,
      });
      await addSecondEnvelopeItem(templateV2.id);

      await page.goto(`/embed/direct/${templateV1.directLink?.token || ''}`);
      await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 });

      await page.goto(`/embed/direct/${templateV2.directLink?.token || ''}`);
      await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 });
      await page.getByRole('button', { name: /Page 2/ }).click();
      await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 });
    });

    test('should render PDF on embed multisign page (V1 and V2)', async ({ page }) => {
      const { user, team } = await seedUser();

      const { recipients: recipientsV1 } = await seedPendingDocumentWithFullFields({
        owner: user,
        teamId: team.id,
        recipients: ['multisign-v1@test.documenso.com'],
        fields: [FieldType.SIGNATURE],
      });

      const { document: documentV2, recipients: recipientsV2 } = await seedPendingDocumentWithFullFields({
        owner: user,
        teamId: team.id,
        recipients: ['multisign-v2@test.documenso.com'],
        fields: [FieldType.SIGNATURE],
        updateDocumentOptions: { internalVersion: 2 },
      });
      await addSecondEnvelopeItem(documentV2.id);

      await page.goto(`/embed/v1/multisign?token=${recipientsV1[0].token}`);
      await expect(page.getByText('Sign Documents')).toBeVisible({ timeout: 15_000 });

      // Todo: Multisign does not support multiple envelope items.
      // await page.getByRole('button', { name: /View/i }).first().click();
      // await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 });

      await page.goto(`/embed/v1/multisign?token=${recipientsV2[0].token}`);
      await expect(page.getByText('Sign Documents')).toBeVisible({ timeout: 15_000 });
      await page.getByRole('button', { name: /View/i }).first().click();
      await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 });

      // Todo: Multisign does not support multiple envelope items.
      // await page.getByRole('button', { name: /Page 2/ }).click();
      // await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 });
    });

    test('should render PDF on embed authoring document create page', async ({ page }) => {
      const { user, team } = await seedUser();

      const { token: apiToken } = await createApiToken({
        userId: user.id,
        teamId: team.id,
        tokenName: 'pdf-viewer-test',
        expiresIn: null,
      });

      const { token: presignToken } = await createEmbeddingPresignToken({
        apiToken,
      });

      const embedParams = { darkModeDisabled: false, features: {} };
      const hash = btoa(encodeURIComponent(JSON.stringify(embedParams)));

      await page.goto(`${NEXT_PUBLIC_WEBAPP_URL()}/embed/v1/authoring/document/create?token=${presignToken}#${hash}`);

      await expect(page.getByText('Configure Document')).toBeVisible({ timeout: 15_000 });

      const titleInput = page.getByLabel('Title');
      await titleInput.click();
      await titleInput.fill('PDF Viewer E2E Test');

      const emailInput = page.getByPlaceholder('Email').first();
      await emailInput.click();
      await emailInput.fill('test-signer@documenso.com');

      const [fileChooser] = await Promise.all([
        page.waitForEvent('filechooser'),
        page
          .locator('input[type=file]')
          .first()
          .evaluate((el) => {
            if (el instanceof HTMLInputElement) {
              el.click();
            }
          }),
      ]);

      await fileChooser.setFiles(path.join(__dirname, '../../../../assets/example.pdf'));

      await page.getByRole('button', { name: 'Continue' }).click();

      await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 });
    });

    test('should render PDF on embed document edit page', async ({ page }) => {
      const { user, team } = await seedUser();

      const document = await seedBlankDocument(user, team.id);
      const documentId = mapSecondaryIdToDocumentId(document.secondaryId);

      const { token: apiToken } = await createApiToken({
        userId: user.id,
        teamId: team.id,
        tokenName: 'pdf-viewer-doc-edit-test',
        expiresIn: null,
      });

      const { token: presignToken } = await createEmbeddingPresignToken({
        apiToken,
        scope: `documentId:${documentId}`,
      });

      const embedParams = {
        darkModeDisabled: false,
        features: {},
        onlyEditFields: true,
      };
      const hash = btoa(encodeURIComponent(JSON.stringify(embedParams)));

      await page.goto(
        `${NEXT_PUBLIC_WEBAPP_URL()}/embed/v1/authoring/document/edit/${documentId}?token=${presignToken}#${hash}`,
      );

      await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 });
    });

    test('should render PDF on embed template edit page', async ({ page }) => {
      const { user, team } = await seedUser();

      const template = await seedBlankTemplate(user, team.id);
      const templateId = mapSecondaryIdToTemplateId(template.secondaryId);

      const { token: apiToken } = await createApiToken({
        userId: user.id,
        teamId: team.id,
        tokenName: 'pdf-viewer-template-edit-test',
        expiresIn: null,
      });

      const { token: presignToken } = await createEmbeddingPresignToken({
        apiToken,
        scope: `templateId:${templateId}`,
      });

      const embedParams = {
        darkModeDisabled: false,
        features: {},
        onlyEditFields: true,
      };
      const hash = btoa(encodeURIComponent(JSON.stringify(embedParams)));

      await page.goto(
        `${NEXT_PUBLIC_WEBAPP_URL()}/embed/v1/authoring/template/edit/${templateId}?token=${presignToken}#${hash}`,
      );

      await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 });
    });
  });
});
