import { extractDocumentAuthMethods } from '@documenso/lib/utils/document-auth';
import { prisma } from '@documenso/prisma';
import { seedTeam, seedTeamMember } from '@documenso/prisma/seed/teams';
import { seedBlankTemplate } from '@documenso/prisma/seed/templates';
import { seedUser } from '@documenso/prisma/seed/users';
import { expect, test } from '@playwright/test';
import { DocumentDataType, TeamMemberRole } from '@prisma/client';
import path from 'path';

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

const EXAMPLE_PDF_PATH = path.join(__dirname, '../../../../assets/example.pdf');
const FIELD_ALIGNMENT_TEST_PDF_PATH = path.join(__dirname, '../../../../assets/field-font-alignment.pdf');

/**
 * 1. Create a template with all settings filled out
 * 2. Create a document from the template
 * 3. Ensure all values are correct
 *
 * Note: There is a direct copy paste of this test below for teams.
 *
 * If you update this test please update that test as well.
 */
test('[TEMPLATE]: should create a document from a template', async ({ page }) => {
  const { user, team } = await seedUser();
  const template = await seedBlankTemplate(user, team.id);

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

  // Set template title.
  await page.getByLabel('Title').fill('TEMPLATE_TITLE');

  // Set template document access.
  await page.getByTestId('documentAccessSelectValue').click();
  await page.getByRole('option').filter({ hasText: 'Require account' }).click();
  await expect(page.getByTestId('documentAccessSelectValue')).toContainText('Require account');

  // Set email options.
  await page.getByRole('button', { name: 'Email Options' }).click();
  await page.getByLabel('Subject (Optional)').fill('SUBJECT');
  await page.getByLabel('Message (Optional)').fill('MESSAGE');

  // Set advanced options.
  await page.getByRole('button', { name: 'Advanced Options' }).click();
  await page.locator('button').filter({ hasText: 'YYYY-MM-DD hh:mm AM/PM' }).click();
  await page.getByLabel('DD/MM/YYYY HH:mm', { exact: true }).click();

  await page.locator('.time-zone-field').click();
  await page.getByRole('option', { name: 'Etc/UTC' }).click();
  await page.getByLabel('Redirect URL').fill('https://documenso.com');
  await page.getByRole('button', { name: 'Continue' }).click();

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

  // Add 2 signers.
  await page.getByPlaceholder('Email').fill('recipient1@documenso.com');
  await page.getByPlaceholder('Name').fill('Recipient 1');
  await page.getByRole('button', { name: 'Add Placeholder Recipient' }).click();
  await page.getByPlaceholder('Email').nth(1).fill('recipient2@documenso.com');
  await page.getByPlaceholder('Name').nth(1).fill('Recipient 2');

  await page.getByRole('button', { name: 'Continue' }).click();
  await expect(page.getByRole('heading', { name: 'Add Fields' })).toBeVisible();

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

  // Use template
  await page.waitForURL(`/t/${team.url}/templates`);
  await page.getByRole('button', { name: 'Use Template' }).click();
  await page.getByRole('button', { name: 'Create as draft' }).click();

  // Review that the document was created with the correct values.
  await page.waitForURL(new RegExp(`/t/${team.url}/documents/envelope_.*`));

  const documentId = page.url().split('/').pop();

  const document = await prisma.envelope.findFirstOrThrow({
    where: {
      id: documentId,
    },
    include: {
      recipients: true,
      documentMeta: true,
    },
  });

  const documentAuth = extractDocumentAuthMethods({
    documentAuth: document.authOptions,
  });

  expect(document.title).toEqual('TEMPLATE_TITLE');
  expect(documentAuth.documentAuthOption.globalAccessAuth).toContain('ACCOUNT');

  expect(document.documentMeta?.dateFormat).toEqual('dd/MM/yyyy HH:mm');
  expect(document.documentMeta?.message).toEqual('MESSAGE');
  expect(document.documentMeta?.redirectUrl).toEqual('https://documenso.com');
  expect(document.documentMeta?.subject).toEqual('SUBJECT');
  expect(document.documentMeta?.timezone).toEqual('Etc/UTC');

  const recipientOne = document.recipients[0];
  const recipientTwo = document.recipients[1];

  const recipientOneAuth = extractDocumentAuthMethods({
    documentAuth: document.authOptions,
    recipientAuth: recipientOne.authOptions,
  });

  const recipientTwoAuth = extractDocumentAuthMethods({
    documentAuth: document.authOptions,
    recipientAuth: recipientTwo.authOptions,
  });

  expect(recipientOneAuth.derivedRecipientAccessAuth).toContain('ACCOUNT');
  expect(recipientTwoAuth.derivedRecipientAccessAuth).toContain('ACCOUNT');
});

/**
 * This is a direct copy paste of the above test but for teams.
 */
test('[TEMPLATE]: should create a team document from a team template', async ({ page }) => {
  const { team, owner, organisation } = await seedTeam({
    createTeamMembers: 2,
  });

  const template = await seedBlankTemplate(owner, team.id);

  await apiSignin({
    page,
    email: owner.email,
    redirectPath: `/t/${team.url}/templates/${template.id}/edit`,
  });

  // Set template title.
  await page.getByLabel('Title').fill('TEMPLATE_TITLE');

  // Set template document access.
  await page.getByTestId('documentAccessSelectValue').click();
  await page.getByRole('option').filter({ hasText: 'Require account' }).click();
  await expect(page.getByTestId('documentAccessSelectValue')).toContainText('Require account');

  // Set email options.
  await page.getByRole('button', { name: 'Email Options' }).click();
  await page.getByLabel('Subject (Optional)').fill('SUBJECT');
  await page.getByLabel('Message (Optional)').fill('MESSAGE');

  // Set advanced options.
  await page.getByRole('button', { name: 'Advanced Options' }).click();
  await page.locator('button').filter({ hasText: 'YYYY-MM-DD hh:mm AM/PM' }).click();
  await page.getByLabel('DD/MM/YYYY HH:mm', { exact: true }).click();

  await page.locator('.time-zone-field').click();
  await page.getByRole('option', { name: 'Etc/UTC' }).click();
  await page.getByLabel('Redirect URL').fill('https://documenso.com');
  await page.getByRole('button', { name: 'Continue' }).click();

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

  // Add 2 signers.
  await page.getByPlaceholder('Email').fill('recipient1@documenso.com');
  await page.getByPlaceholder('Name').fill('Recipient 1');
  await page.getByRole('button', { name: 'Add Placeholder Recipient' }).click();
  await page.getByPlaceholder('Email').nth(1).fill('recipient2@documenso.com');
  await page.getByPlaceholder('Name').nth(1).fill('Recipient 2');

  await page.getByRole('button', { name: 'Continue' }).click();
  await expect(page.getByRole('heading', { name: 'Add Fields' })).toBeVisible();

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

  // Use template
  await page.waitForURL(`/t/${team.url}/templates`);
  await page.getByRole('button', { name: 'Use Template' }).click();
  await page.getByRole('button', { name: 'Create as draft' }).click();

  // Review that the document was created with the correct values.
  await page.waitForURL(new RegExp(`/t/${team.url}/documents/envelope_.*`));

  const documentId = page.url().split('/').pop();

  const document = await prisma.envelope.findFirstOrThrow({
    where: {
      id: documentId,
    },
    include: {
      recipients: true,
      documentMeta: true,
    },
  });

  expect(document.teamId).toEqual(team.id);

  const documentAuth = extractDocumentAuthMethods({
    documentAuth: document.authOptions,
  });

  expect(document.title).toEqual('TEMPLATE_TITLE');
  expect(documentAuth.documentAuthOption.globalAccessAuth).toContain('ACCOUNT');
  expect(document.documentMeta?.dateFormat).toEqual('dd/MM/yyyy HH:mm');
  expect(document.documentMeta?.message).toEqual('MESSAGE');
  expect(document.documentMeta?.redirectUrl).toEqual('https://documenso.com');
  expect(document.documentMeta?.subject).toEqual('SUBJECT');
  expect(document.documentMeta?.timezone).toEqual('Etc/UTC');

  const recipientOne = document.recipients[0];
  const recipientTwo = document.recipients[1];

  const recipientOneAuth = extractDocumentAuthMethods({
    documentAuth: document.authOptions,
    recipientAuth: recipientOne.authOptions,
  });

  const recipientTwoAuth = extractDocumentAuthMethods({
    documentAuth: document.authOptions,
    recipientAuth: recipientTwo.authOptions,
  });

  expect(recipientOneAuth.derivedRecipientAccessAuth).toContain('ACCOUNT');
  expect(recipientTwoAuth.derivedRecipientAccessAuth).toContain('ACCOUNT');
});

/**
 * This test verifies that we can create a document from a template using a custom document
 * instead of the template's default document.
 */
test('[TEMPLATE]: should create a document from a template with custom document', async ({ page }) => {
  const { user, team } = await seedUser();
  const template = await seedBlankTemplate(user, team.id);

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

  // Set template title
  await page.getByLabel('Title').fill('TEMPLATE_WITH_CUSTOM_DOC');

  await page.getByRole('button', { name: 'Continue' }).click();
  await expect(page.getByRole('heading', { name: 'Add Placeholder' })).toBeVisible();

  // Add a signer
  await page.getByPlaceholder('Email').fill('recipient@documenso.com');
  await page.getByPlaceholder('Name').fill('Recipient');

  await page.getByRole('button', { name: 'Continue' }).click();
  await expect(page.getByRole('heading', { name: 'Add Fields' })).toBeVisible();

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

  // Use template with custom document
  await page.waitForURL(`/t/${team.url}/templates`);
  await page.getByRole('button', { name: 'Use Template' }).click();

  // Enable custom document upload and upload file
  await page.getByLabel('Upload custom document').check();

  // Upload document.
  const [fileChooser] = await Promise.all([
    page.waitForEvent('filechooser'),
    page.locator(`#template-use-dialog-file-input-${template.envelopeItems[0].id}`).evaluate((e) => {
      if (e instanceof HTMLInputElement) {
        e.click();
      }
    }),
  ]);

  await fileChooser.setFiles(FIELD_ALIGNMENT_TEST_PDF_PATH);

  // Wait for upload to complete
  await expect(page.getByText('Remove')).toBeVisible();

  // Create document with custom document data
  await page.getByRole('button', { name: 'Create as draft' }).click();

  // Review that the document was created with the custom document data
  await page.waitForURL(new RegExp(`/t/${team.url}/documents/envelope_.*`));

  const documentId = page.url().split('/').pop();

  const document = await prisma.envelope.findFirstOrThrow({
    where: {
      id: documentId,
    },
    include: {
      envelopeItems: {
        include: {
          documentData: true,
        },
      },
    },
  });

  const firstDocumentData = document.envelopeItems[0].documentData;

  const expectedDocumentDataType =
    process.env.NEXT_PUBLIC_UPLOAD_TRANSPORT === 's3' ? DocumentDataType.S3_PATH : DocumentDataType.BYTES_64;

  expect(document.title).toEqual('TEMPLATE_WITH_CUSTOM_DOC');
  expect(firstDocumentData.type).toEqual(expectedDocumentDataType);

  if (expectedDocumentDataType === DocumentDataType.BYTES_64) {
    // Todo: Doesn't really work due to normalization of the PDF which won't let us directly compare the data.
    // Probably need to do a pixel match
    expect(firstDocumentData.data).not.toEqual(template.envelopeItems[0].documentData.data);
    expect(firstDocumentData.initialData).not.toEqual(template.envelopeItems[0].documentData.initialData);
  } else {
    // For S3, we expect the data/initialData to be the S3 path (non-empty string)
    expect(firstDocumentData.data).toBeTruthy();
    expect(firstDocumentData.initialData).toBeTruthy();
  }
});

/**
 * This test verifies that we can create a team document from a template using a custom document
 * instead of the template's default document.
 */
test('[TEMPLATE]: should create a team document from a template with custom document', async ({ page }) => {
  const { team, owner, organisation } = await seedTeam({
    createTeamMembers: 2,
  });

  const template = await seedBlankTemplate(owner, team.id);

  await apiSignin({
    page,
    email: owner.email,
    redirectPath: `/t/${team.url}/templates/${template.id}/edit`,
  });

  // Set template title
  await page.getByLabel('Title').fill('TEAM_TEMPLATE_WITH_CUSTOM_DOC');

  await page.getByRole('button', { name: 'Continue' }).click();
  await expect(page.getByRole('heading', { name: 'Add Placeholder' })).toBeVisible();

  // Add a signer
  await page.getByPlaceholder('Email').fill('recipient@documenso.com');
  await page.getByPlaceholder('Name').fill('Recipient');

  await page.getByRole('button', { name: 'Continue' }).click();
  await expect(page.getByRole('heading', { name: 'Add Fields' })).toBeVisible();

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

  // Use template with custom document
  await page.waitForURL(`/t/${team.url}/templates`);
  await page.getByRole('button', { name: 'Use Template' }).click();

  // Enable custom document upload and upload file
  await page.getByLabel('Upload custom document').check();

  // Upload document.
  const [fileChooser] = await Promise.all([
    page.waitForEvent('filechooser'),
    page.locator(`#template-use-dialog-file-input-${template.envelopeItems[0].id}`).evaluate((e) => {
      if (e instanceof HTMLInputElement) {
        e.click();
      }
    }),
  ]);

  await fileChooser.setFiles(FIELD_ALIGNMENT_TEST_PDF_PATH);

  // Wait for upload to complete
  await expect(page.getByText('Remove')).toBeVisible();

  // Create document with custom document data
  await page.getByRole('button', { name: 'Create as draft' }).click();

  // Review that the document was created with the custom document data
  await page.waitForURL(new RegExp(`/t/${team.url}/documents/envelope_.*`));

  const documentId = page.url().split('/').pop();

  const document = await prisma.envelope.findFirstOrThrow({
    where: {
      id: documentId,
    },
    include: {
      envelopeItems: {
        include: {
          documentData: true,
        },
      },
    },
  });

  const expectedDocumentDataType =
    process.env.NEXT_PUBLIC_UPLOAD_TRANSPORT === 's3' ? DocumentDataType.S3_PATH : DocumentDataType.BYTES_64;

  const firstDocumentData = document.envelopeItems[0].documentData;

  expect(document.teamId).toEqual(team.id);
  expect(document.title).toEqual('TEAM_TEMPLATE_WITH_CUSTOM_DOC');
  expect(firstDocumentData.type).toEqual(expectedDocumentDataType);

  if (expectedDocumentDataType === DocumentDataType.BYTES_64) {
    // Todo: Doesn't really work due to normalization of the PDF which won't let us directly compare the data.
    // Probably need to do a pixel match
    expect(firstDocumentData.data).not.toEqual(template.envelopeItems[0].documentData.data);
    expect(firstDocumentData.initialData).not.toEqual(template.envelopeItems[0].documentData.initialData);
  } else {
    // For S3, we expect the data/initialData to be the S3 path (non-empty string)
    expect(firstDocumentData.data).toBeTruthy();
    expect(firstDocumentData.initialData).toBeTruthy();
  }
});

/**
 * This test verifies that when custom document upload is not enabled,
 * the document uses the template's original document data.
 */
test('[TEMPLATE]: should create a document from a template using template document when custom document is not enabled', async ({
  page,
}) => {
  const { user, team } = await seedUser();
  const template = await seedBlankTemplate(user, team.id);

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

  // Set template title
  await page.getByLabel('Title').fill('TEMPLATE_WITH_ORIGINAL_DOC');

  await page.getByRole('button', { name: 'Continue' }).click();
  await expect(page.getByRole('heading', { name: 'Add Placeholder' })).toBeVisible();

  // Add a signer
  await page.getByPlaceholder('Email').fill('recipient@documenso.com');
  await page.getByPlaceholder('Name').fill('Recipient');

  await page.getByRole('button', { name: 'Continue' }).click();
  await expect(page.getByRole('heading', { name: 'Add Fields' })).toBeVisible();

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

  // Use template without custom document
  await page.waitForURL(`/t/${team.url}/templates`);
  await page.getByRole('button', { name: 'Use Template' }).click();

  // Verify custom document upload is not checked by default
  await expect(page.getByLabel('Upload custom document')).not.toBeChecked();

  // Create document without custom document data
  await page.getByRole('button', { name: 'Create as draft' }).click();

  // Review that the document was created with the template's document data
  await page.waitForURL(new RegExp(`/t/${team.url}/documents/envelope_.*`));

  const documentId = page.url().split('/').pop();

  const document = await prisma.envelope.findFirstOrThrow({
    where: {
      id: documentId,
    },
    include: {
      envelopeItems: {
        include: {
          documentData: true,
        },
      },
    },
  });

  const firstDocumentData = document.envelopeItems[0].documentData;

  const templateWithData = await prisma.envelope.findFirstOrThrow({
    where: {
      id: template.id,
    },
    include: {
      envelopeItems: {
        include: {
          documentData: true,
        },
      },
    },
  });

  expect(document.title).toEqual('TEMPLATE_WITH_ORIGINAL_DOC');
  expect(firstDocumentData.initialData).toEqual(templateWithData.envelopeItems[0].documentData.data);
  expect(firstDocumentData.initialData).toEqual(templateWithData.envelopeItems[0].documentData.initialData);
  expect(firstDocumentData.type).toEqual(templateWithData.envelopeItems[0].documentData.type);
});

test('[TEMPLATE]: should persist document visibility when creating from template', async ({ page }) => {
  const { team, owner, organisation } = await seedTeam({
    createTeamMembers: 2,
  });

  const template = await seedBlankTemplate(owner, team.id);

  await apiSignin({
    page,
    email: owner.email,
    redirectPath: `/t/${team.url}/templates/${template.id}/edit`,
  });

  // Set template title and visibility
  await page.getByLabel('Title').fill('TEMPLATE_WITH_VISIBILITY');
  await page.getByTestId('documentVisibilitySelectValue').click();
  await page.getByLabel('Managers and above').click();
  await expect(page.getByTestId('documentVisibilitySelectValue')).toContainText('Managers and above');

  await page.getByRole('button', { name: 'Continue' }).click();
  await expect(page.getByRole('heading', { name: 'Add Placeholder' })).toBeVisible();

  // Add a signer
  await page.getByPlaceholder('Email').fill('recipient@documenso.com');
  await page.getByPlaceholder('Name').fill('Recipient');

  await page.getByRole('button', { name: 'Continue' }).click();
  await expect(page.getByRole('heading', { name: 'Add Fields' })).toBeVisible();

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

  // Test creating document as team manager
  const managerUser = await seedTeamMember({
    teamId: team.id,
    role: TeamMemberRole.MANAGER,
  });

  await apiSignin({
    page,
    email: managerUser.email,
    redirectPath: `/t/${team.url}/templates`,
  });

  await page.getByRole('button', { name: 'Use Template' }).click();
  await page.getByRole('button', { name: 'Create as draft' }).click();

  // Review that the document was created with the correct visibility
  await page.waitForURL(new RegExp(`/t/${team.url}/documents/envelope_.*`));

  const documentId = page.url().split('/').pop();

  const document = await prisma.envelope.findFirstOrThrow({
    where: {
      id: documentId,
    },
    include: {
      envelopeItems: {
        include: {
          documentData: true,
        },
      },
    },
  });

  expect(document.title).toEqual('TEMPLATE_WITH_VISIBILITY');
  expect(document.visibility).toEqual('MANAGER_AND_ABOVE');
  expect(document.teamId).toEqual(team.id);

  // Test that regular member cannot create document from restricted template
  const memberUser = await seedTeamMember({
    teamId: team.id,
    role: TeamMemberRole.MEMBER,
  });

  await apiSignin({
    page,
    email: memberUser.email,
    redirectPath: `/t/${team.url}/templates`,
  });

  // Template should not be visible to regular member
  await expect(page.getByRole('button', { name: 'Use Template' })).not.toBeVisible();
});
