import fs from 'node:fs';
import path from 'node:path';
import { getEnvelopeItemPdfUrl } from '@documenso/lib/utils/envelope-download';
import { prisma } from '@documenso/prisma';
import { seedUser } from '@documenso/prisma/seed/users';
import { type APIRequestContext, expect, type Page, test } from '@playwright/test';
import { DocumentStatus, EnvelopeType, FieldType } from '@prisma/client';
import * as pdfjsLib from 'pdfjs-dist/legacy/build/pdf.mjs';

import { NEXT_PUBLIC_WEBAPP_URL } from '../../../lib/constants/app';
import { createApiToken } from '../../../lib/server-only/public-api/create-api-token';
import { RecipientRole } from '../../../prisma/generated/types';
import type {
  TCreateEnvelopePayload,
  TCreateEnvelopeResponse,
} from '../../../trpc/server/envelope-router/create-envelope.types';
import type { TDistributeEnvelopeRequest } from '../../../trpc/server/envelope-router/distribute-envelope.types';
import { apiSignin } from '../fixtures/authentication';

const WEBAPP_BASE_URL = NEXT_PUBLIC_WEBAPP_URL();
const baseUrl = `${WEBAPP_BASE_URL}/api/v2`;

test.describe.configure({ mode: 'parallel', timeout: 60000 });

const signAndVerifyPageDimensions = async ({
  page,
  request,
  pdfFile,
  identifier,
  title,
  expectedWidth,
  expectedHeight,
}: {
  page: Page;
  request: APIRequestContext;
  pdfFile: string;
  identifier: string;
  title: string;
  expectedWidth: number;
  expectedHeight: number;
}) => {
  const { user, team } = await seedUser();

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

  const pdfBuffer = fs.readFileSync(path.join(__dirname, `../../../../assets/${pdfFile}`));

  const formData = new FormData();

  const createEnvelopePayload: TCreateEnvelopePayload = {
    type: EnvelopeType.DOCUMENT,
    title,
    recipients: [
      {
        email: user.email,
        name: user.name || '',
        role: RecipientRole.SIGNER,
        fields: [
          {
            identifier,
            type: FieldType.SIGNATURE,
            fieldMeta: { type: 'signature' },
            page: 1,
            positionX: 10,
            positionY: 10,
            width: 40,
            height: 10,
          },
        ],
      },
    ],
  };

  formData.append('payload', JSON.stringify(createEnvelopePayload));
  formData.append('files', new File([pdfBuffer], identifier, { type: 'application/pdf' }));

  const createResponse = await request.post(`${baseUrl}/envelope/create`, {
    headers: { Authorization: `Bearer ${token}` },
    multipart: formData,
  });

  expect(createResponse.ok()).toBeTruthy();

  const { id: envelopeId }: TCreateEnvelopeResponse = await createResponse.json();

  const envelope = await prisma.envelope.findUniqueOrThrow({
    where: { id: envelopeId },
    include: { recipients: true },
  });

  const distributeResponse = await request.post(`${baseUrl}/envelope/distribute`, {
    headers: { Authorization: `Bearer ${token}` },
    data: { envelopeId: envelope.id } satisfies TDistributeEnvelopeRequest,
  });

  expect(distributeResponse.ok()).toBeTruthy();

  // Pre-insert all fields via Prisma so we can skip the UI field interaction.
  const fields = await prisma.field.findMany({
    where: { envelopeId: envelope.id, inserted: false },
  });

  for (const field of fields) {
    await prisma.field.update({
      where: { id: field.id },
      data: {
        inserted: true,
        signature: {
          create: {
            recipientId: envelope.recipients[0].id,
            typedSignature: 'Test Signature',
          },
        },
      },
    });
  }

  const recipientToken = envelope.recipients[0].token;
  const signUrl = `/sign/${recipientToken}`;

  await apiSignin({
    page,
    email: user.email,
    redirectPath: signUrl,
  });

  await expect(page.getByRole('heading', { name: 'Sign Document' })).toBeVisible();

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

  await expect(async () => {
    const { status } = await prisma.envelope.findFirstOrThrow({
      where: { id: envelope.id },
    });

    expect(status).toBe(DocumentStatus.COMPLETED);
  }).toPass({ timeout: 10000 });

  const completedEnvelope = await prisma.envelope.findFirstOrThrow({
    where: { id: envelope.id },
    include: {
      envelopeItems: {
        orderBy: { order: 'asc' },
        include: { documentData: true },
      },
    },
  });

  for (const item of completedEnvelope.envelopeItems) {
    const documentUrl = getEnvelopeItemPdfUrl({
      type: 'download',
      envelopeItem: item,
      token: recipientToken,
      version: 'signed',
    });

    const pdfData = await fetch(documentUrl).then(async (res) => await res.arrayBuffer());
    const loadingTask = pdfjsLib.getDocument({ data: new Uint8Array(pdfData) });
    const pdf = await loadingTask.promise;

    expect(pdf.numPages).toBeGreaterThan(1);

    for (let i = 1; i <= pdf.numPages; i++) {
      const pdfPage = await pdf.getPage(i);
      const viewport = pdfPage.getViewport({ scale: 1 });

      expect(Math.round(viewport.width)).toBe(expectedWidth);
      expect(Math.round(viewport.height)).toBe(expectedHeight);
    }
  }
};

test('cert and audit log pages match letter page dimensions', async ({ page, request }) => {
  await signAndVerifyPageDimensions({
    page,
    request,
    pdfFile: 'letter-size.pdf',
    identifier: 'letter-doc',
    title: 'Letter Size Dimension Test',
    expectedWidth: 612,
    expectedHeight: 792,
  });
});

test('cert and audit log pages match A4 page dimensions', async ({ page, request }) => {
  await signAndVerifyPageDimensions({
    page,
    request,
    pdfFile: 'a4-size.pdf',
    identifier: 'a4-doc',
    title: 'A4 Size Dimension Test',
    expectedWidth: 595,
    expectedHeight: 842,
  });
});

test('cert and audit log pages match tabloid landscape page dimensions', async ({ page, request }) => {
  await signAndVerifyPageDimensions({
    page,
    request,
    pdfFile: 'tabloid-landscape.pdf',
    identifier: 'tabloid-doc',
    title: 'Tabloid Landscape Dimension Test',
    expectedWidth: 1224,
    expectedHeight: 792,
  });
});
