import { getDocumentByToken } from '@documenso/lib/server-only/document/get-document-by-token';
import { getEnvelopeItemPdfUrl } from '@documenso/lib/utils/envelope-download';
import { prisma } from '@documenso/prisma';
import { seedPendingDocumentWithFullFields } from '@documenso/prisma/seed/documents';
import { seedTeam } from '@documenso/prisma/seed/teams';
import { seedUser } from '@documenso/prisma/seed/users';
import { PDF } from '@libpdf/core';
import { expect, test } from '@playwright/test';
import { DocumentStatus, FieldType } from '@prisma/client';

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

test.describe('Signing Certificate Tests', () => {
  test('individual document should always include signing certificate', async ({ page }) => {
    const { user, team } = await seedUser({
      isPersonalOrganisation: true,
    });

    const { document, recipients } = await seedPendingDocumentWithFullFields({
      owner: user,
      recipients: ['signer@example.com'],
      fields: [FieldType.SIGNATURE],
      teamId: team.id,
    });

    const recipient = recipients[0];

    const documentData = await prisma.envelopeItem
      .findFirstOrThrow({
        where: {
          envelopeId: document.id,
        },
      })
      .then(async (data) => {
        const documentUrl = getEnvelopeItemPdfUrl({
          type: 'download',
          envelopeItem: data,
          token: recipient.token,
          version: 'signed',
        });
        return fetch(documentUrl).then(async (res) => await res.arrayBuffer());
      });

    const originalPdf = await PDF.load(new Uint8Array(documentData));

    // Sign the document
    await page.goto(`/sign/${recipient.token}`);

    await signSignaturePad(page);

    for (const field of recipient.fields) {
      await page.locator(`#field-${field.id}`).getByRole('button').click();

      await expect(page.locator(`#field-${field.id}`)).toHaveAttribute('data-inserted', 'true');
    }

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

    await page.waitForTimeout(1000);

    await page.getByRole('button', { name: 'Sign' }).click({ force: true });
    await page.waitForURL(`/sign/${recipient.token}/complete`);

    await page.waitForTimeout(10000);

    await expect(async () => {
      const { status } = await getDocumentByToken({
        token: recipient.token,
      });

      expect(status).toBe(DocumentStatus.COMPLETED);
    }).toPass();

    await page.waitForTimeout(2500);

    // Get the completed document
    const completedDocument = await prisma.envelope.findFirstOrThrow({
      where: { id: document.id },
      include: {
        envelopeItems: {
          include: {
            documentData: true,
          },
        },
      },
    });

    const firstDocumentData = completedDocument.envelopeItems[0];

    const documentUrl = getEnvelopeItemPdfUrl({
      type: 'download',
      envelopeItem: firstDocumentData,
      token: recipient.token,
      version: 'signed',
    });

    const pdfData = await fetch(documentUrl).then(async (res) => await res.arrayBuffer());

    const completedDocumentData = new Uint8Array(pdfData);

    // Load the PDF and check number of pages
    const pdfDoc = await PDF.load(new Uint8Array(completedDocumentData));

    expect(pdfDoc.getPageCount()).toBe(originalPdf.getPageCount() + 1); // Original + Certificate
  });

  test('team document with signing certificate enabled should include certificate', async ({ page }) => {
    const { owner, team } = await seedTeam();

    const { document, recipients } = await seedPendingDocumentWithFullFields({
      owner: owner,
      recipients: ['signer@example.com'],
      fields: [FieldType.SIGNATURE],
      teamId: team.id,
    });

    const teamSettingsId = await prisma.teamGlobalSettings.findFirstOrThrow({
      where: {
        team: {
          id: team.id,
        },
      },
    });

    await prisma.teamGlobalSettings.update({
      where: {
        id: teamSettingsId.id,
      },
      data: {
        includeSigningCertificate: true,
      },
    });

    const recipient = recipients[0];

    const documentData = await prisma.envelopeItem
      .findFirstOrThrow({
        where: {
          envelopeId: document.id,
        },
      })
      .then(async (data) => {
        const documentUrl = getEnvelopeItemPdfUrl({
          type: 'download',
          envelopeItem: data,
          token: recipient.token,
          version: 'signed',
        });
        return fetch(documentUrl).then(async (res) => await res.arrayBuffer());
      });

    const originalPdf = await PDF.load(new Uint8Array(documentData));

    // Sign the document
    await page.goto(`/sign/${recipient.token}`);

    await signSignaturePad(page);

    for (const field of recipient.fields) {
      await page.locator(`#field-${field.id}`).getByRole('button').click();

      await expect(page.locator(`#field-${field.id}`)).toHaveAttribute('data-inserted', 'true');
    }

    await page.getByRole('button', { name: 'Complete' }).click();
    await page.getByRole('button', { name: 'Sign' }).click();
    await page.waitForURL(`/sign/${recipient.token}/complete`);

    await expect(async () => {
      const { status } = await getDocumentByToken({
        token: recipient.token,
      });

      expect(status).toBe(DocumentStatus.COMPLETED);
    }).toPass();

    await page.waitForTimeout(2500);

    // Get the completed document
    const completedDocument = await prisma.envelope.findFirstOrThrow({
      where: { id: document.id },
      include: {
        envelopeItems: {
          include: {
            documentData: true,
          },
        },
      },
    });

    const firstDocumentData = completedDocument.envelopeItems[0];

    const documentUrl = getEnvelopeItemPdfUrl({
      type: 'download',
      envelopeItem: firstDocumentData,
      token: recipient.token,
      version: 'signed',
    });

    const pdfData = await fetch(documentUrl).then(async (res) => await res.arrayBuffer());

    const completedDocumentData = new Uint8Array(pdfData);

    // Load the PDF and check number of pages
    const completedPdf = await PDF.load(new Uint8Array(completedDocumentData));

    expect(completedPdf.getPageCount()).toBe(originalPdf.getPageCount() + 1); // Original + Certificate
  });

  test('team document with signing certificate disabled should not include certificate', async ({ page }) => {
    const { owner, team } = await seedTeam();

    const { document, recipients } = await seedPendingDocumentWithFullFields({
      owner: owner,
      recipients: ['signer@example.com'],
      fields: [FieldType.SIGNATURE],
      teamId: team.id,
    });

    const teamSettingsId = await prisma.teamGlobalSettings.findFirstOrThrow({
      where: {
        team: {
          id: team.id,
        },
      },
    });

    await prisma.teamGlobalSettings.update({
      where: {
        id: teamSettingsId.id,
      },
      data: {
        includeSigningCertificate: false,
      },
    });

    const recipient = recipients[0];

    const documentData = await prisma.envelopeItem
      .findFirstOrThrow({
        where: {
          envelopeId: document.id,
        },
      })
      .then(async (data) => {
        const documentUrl = getEnvelopeItemPdfUrl({
          type: 'download',
          envelopeItem: data,
          token: recipient.token,
          version: 'signed',
        });
        return fetch(documentUrl).then(async (res) => await res.arrayBuffer());
      });

    const originalPdf = await PDF.load(new Uint8Array(documentData));

    // Sign the document
    await page.goto(`/sign/${recipient.token}`);

    await signSignaturePad(page);

    for (const field of recipient.fields) {
      await page.locator(`#field-${field.id}`).getByRole('button').click();

      await expect(page.locator(`#field-${field.id}`)).toHaveAttribute('data-inserted', 'true');
    }

    await page.getByRole('button', { name: 'Complete' }).click();
    await page.getByRole('button', { name: 'Sign' }).click();
    await page.waitForURL(`/sign/${recipient.token}/complete`);

    await expect(async () => {
      const { status } = await getDocumentByToken({
        token: recipient.token,
      });

      expect(status).toBe(DocumentStatus.COMPLETED);
    }).toPass();

    await page.waitForTimeout(2500);

    // Get the completed document
    const completedDocument = await prisma.envelope.findFirstOrThrow({
      where: { id: document.id },
      include: {
        envelopeItems: {
          include: {
            documentData: true,
          },
        },
      },
    });

    const documentUrl = getEnvelopeItemPdfUrl({
      type: 'download',
      envelopeItem: completedDocument.envelopeItems[0],
      token: recipient.token,
      version: 'signed',
    });

    const completedDocumentData = await fetch(documentUrl).then(async (res) => await res.arrayBuffer());

    // Load the PDF and check number of pages
    const completedPdf = await PDF.load(new Uint8Array(completedDocumentData));

    expect(completedPdf.getPageCount()).toBe(originalPdf.getPageCount());
  });

  test('team can toggle signing certificate setting', async ({ page }) => {
    const { owner, team } = await seedTeam();

    await apiSignin({
      page,
      email: owner.email,
      redirectPath: `/t/${team.url}/settings/certificates`,
    });

    await page.getByTestId('include-signing-certificate-trigger').click();
    await page.getByRole('option', { name: 'No' }).click();

    await page.getByRole('button', { name: 'Save changes' }).first().click();
    await expect(page.getByText('Your certificate preferences have been updated').first()).toBeVisible();

    // Verify the setting was saved
    const updatedTeam = await prisma.team.findFirstOrThrow({
      where: { id: team.id },
      include: { teamGlobalSettings: true },
    });

    expect(updatedTeam.teamGlobalSettings?.includeSigningCertificate).toBe(false);

    // Toggle the setting back to true
    await page.getByTestId('include-signing-certificate-trigger').click();
    await page.getByRole('option', { name: 'Yes' }).click();
    await page.getByRole('button', { name: 'Save changes' }).first().click();

    // The toast from the first save may still be visible, so poll the database
    // for the saved value instead of waiting on UI signals.
    await expect
      .poll(async () => {
        const updatedTeam = await prisma.team.findFirstOrThrow({
          where: { id: team.id },
          include: { teamGlobalSettings: true },
        });

        return updatedTeam.teamGlobalSettings?.includeSigningCertificate;
      })
      .toBe(true);
  });
});
