import fs from 'node:fs';
import path from 'node:path';
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
import { createApiToken } from '@documenso/lib/server-only/public-api/create-api-token';
import { prisma } from '@documenso/prisma';
import { seedUser } from '@documenso/prisma/seed/users';
import type {
  TCreateEnvelopePayload,
  TCreateEnvelopeResponse,
} from '@documenso/trpc/server/envelope-router/create-envelope.types';
import type { TCreateEnvelopeRecipientsRequest } from '@documenso/trpc/server/envelope-router/envelope-recipients/create-envelope-recipients.types';
import type { TGetEnvelopeResponse } from '@documenso/trpc/server/envelope-router/get-envelope.types';
import { expect, type Page, test } from '@playwright/test';
import { EnvelopeType, FieldType, RecipientRole } from '@prisma/client';

import { apiSignin } from '../fixtures/authentication';
import {
  clickAddMyselfButton,
  clickEnvelopeEditorStep,
  getRecipientEmailInputs,
  openDocumentEnvelopeEditor,
} from '../fixtures/envelope-editor';
import { expectToastTextToBeVisible } from '../fixtures/generic';

const WEBAPP_BASE_URL = NEXT_PUBLIC_WEBAPP_URL();
const V2_API_BASE_URL = `${WEBAPP_BASE_URL}/api/v2-beta`;

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

/**
 * Place a field on the PDF canvas in the envelope editor.
 */
const placeFieldOnPdf = async (root: Page, fieldName: 'Signature' | 'Text', position: { x: number; y: number }) => {
  await root.getByRole('button', { name: fieldName, exact: true }).click();

  const canvas = root.locator('.konva-container canvas').first();
  await expect(canvas).toBeVisible();
  await canvas.click({ position });
};

/**
 * Create a V2 document envelope via the API with a single SIGNER recipient
 * and a SIGNATURE field, suitable for testing save-as-template with data.
 *
 * Returns the envelope ID, user, team, and recipient email.
 */
const createDocumentWithRecipientAndField = async () => {
  const { user, team } = await seedUser();

  const { token } = await createApiToken({
    userId: user.id,
    teamId: team.id,
    tokenName: `e2e-save-as-template-${Date.now()}`,
    expiresIn: null,
  });

  const recipientEmail = `save-template-${Date.now()}@test.documenso.com`;

  // 1. Create envelope with a PDF.
  const payload = {
    type: EnvelopeType.DOCUMENT,
    title: `E2E Save as Template ${Date.now()}`,
  } satisfies TCreateEnvelopePayload;

  const formData = new FormData();
  formData.append('payload', JSON.stringify(payload));
  formData.append('files', new File([examplePdfBuffer], 'example.pdf', { type: 'application/pdf' }));

  const createRes = await fetch(`${V2_API_BASE_URL}/envelope/create`, {
    method: 'POST',
    headers: { Authorization: `Bearer ${token}` },
    body: formData,
  });

  expect(createRes.ok).toBeTruthy();
  const createResponse = (await createRes.json()) as TCreateEnvelopeResponse;

  // 2. Create a SIGNER recipient.
  const createRecipientsRequest: TCreateEnvelopeRecipientsRequest = {
    envelopeId: createResponse.id,
    data: [
      {
        email: recipientEmail,
        name: 'Template Test Signer',
        role: RecipientRole.SIGNER,
        accessAuth: [],
        actionAuth: [],
      },
    ],
  };

  const recipientsRes = await fetch(`${V2_API_BASE_URL}/envelope/recipient/create-many`, {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${token}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(createRecipientsRequest),
  });

  expect(recipientsRes.ok).toBeTruthy();
  const recipientsResponse = await recipientsRes.json();
  const recipients = recipientsResponse.data;

  // 3. Get envelope to find the envelope item ID.
  const getRes = await fetch(`${V2_API_BASE_URL}/envelope/${createResponse.id}`, {
    headers: { Authorization: `Bearer ${token}` },
  });

  const envelope = (await getRes.json()) as TGetEnvelopeResponse;
  const envelopeItem = envelope.envelopeItems[0];

  // 4. Create a SIGNATURE field for the recipient.
  const createFieldsRequest = {
    envelopeId: createResponse.id,
    data: [
      {
        recipientId: recipients[0].id,
        envelopeItemId: envelopeItem.id,
        type: FieldType.SIGNATURE,
        page: 1,
        positionX: 100,
        positionY: 100,
        width: 50,
        height: 50,
      },
    ],
  };

  const fieldsRes = await fetch(`${V2_API_BASE_URL}/envelope/field/create-many`, {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${token}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(createFieldsRequest),
  });

  expect(fieldsRes.ok).toBeTruthy();

  return {
    user,
    team,
    envelopeId: createResponse.id,
    recipientEmail,
  };
};

test.describe('document editor', () => {
  test('save document as template from editor sidebar', async ({ page }) => {
    const surface = await openDocumentEnvelopeEditor(page);

    // Add the current user as a recipient via the UI.
    await clickAddMyselfButton(surface.root);
    await expect(getRecipientEmailInputs(surface.root).first()).toHaveValue(surface.userEmail);

    // Navigate to the add fields step and place a signature field.
    await clickEnvelopeEditorStep(surface.root, 'addFields');
    await expect(surface.root.locator('.konva-container canvas').first()).toBeVisible();
    await placeFieldOnPdf(surface.root, 'Signature', { x: 120, y: 140 });

    // Navigate back to the upload step so the sidebar actions are available.
    await clickEnvelopeEditorStep(surface.root, 'upload');

    // Click the "Save as Template" sidebar action.
    await page.locator('button[title="Save as Template"]').click();

    // The save as template dialog should appear.
    await expect(page.getByRole('heading', { name: 'Save as Template' })).toBeVisible();

    // Both checkboxes should be checked by default.
    await expect(page.locator('#envelopeIncludeRecipients')).toBeChecked();
    await expect(page.locator('#envelopeIncludeFields')).toBeChecked();

    // Click "Save as Template".
    await page.getByRole('button', { name: 'Save as Template' }).click();

    // Assert toast appears.
    await expectToastTextToBeVisible(page, 'Template Created');

    // The page should have navigated to the new template's edit page.
    await expect(page).toHaveURL(/\/templates\/.*\/edit/);

    // Verify the new template envelope was created in the database with correct type and secondaryId.
    const templateEnvelopes = await prisma.envelope.findMany({
      where: {
        userId: surface.userId,
        teamId: surface.teamId,
        type: 'TEMPLATE',
      },
      include: {
        recipients: {
          include: {
            fields: true,
          },
        },
      },
    });

    expect(templateEnvelopes.length).toBeGreaterThanOrEqual(1);

    const createdTemplate = templateEnvelopes.find((e) => e.title.includes('(copy)'));
    expect(createdTemplate).toBeDefined();

    // CRITICAL: Verify the secondaryId uses the template_ prefix, not document_.
    // This confirms incrementTemplateId was called, not incrementDocumentId.
    expect(createdTemplate!.secondaryId).toMatch(/^template_\d+$/);
    expect(createdTemplate!.type).toBe(EnvelopeType.TEMPLATE);

    // Verify recipients were included.
    expect(createdTemplate!.recipients.length).toBe(1);
    expect(createdTemplate!.recipients[0].email).toBe(surface.userEmail);

    // Verify fields were included.
    expect(createdTemplate!.recipients[0].fields.length).toBe(1);
    expect(createdTemplate!.recipients[0].fields[0].type).toBe(FieldType.SIGNATURE);
  });

  test('save document as template without recipients', async ({ page }) => {
    const { user, team, envelopeId, recipientEmail } = await createDocumentWithRecipientAndField();

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

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

    // Click the "Save as Template" sidebar action.
    await page.locator('button[title="Save as Template"]').click();

    // The dialog should appear.
    await expect(page.getByRole('heading', { name: 'Save as Template' })).toBeVisible();

    // Uncheck "Include Recipients" - "Include Fields" should auto-disable.
    await page.locator('#envelopeIncludeRecipients').click();
    await expect(page.locator('#envelopeIncludeRecipients')).not.toBeChecked();
    await expect(page.locator('#envelopeIncludeFields')).not.toBeChecked();
    await expect(page.locator('#envelopeIncludeFields')).toBeDisabled();

    // Click "Save as Template".
    await page.getByRole('button', { name: 'Save as Template' }).click();

    // Assert toast appears.
    await expectToastTextToBeVisible(page, 'Template Created');

    // The page should have navigated to the new template's edit page.
    await expect(page).toHaveURL(/\/templates\/.*\/edit/);

    // Verify the template was created without recipients.
    const templateEnvelopes = await prisma.envelope.findMany({
      where: {
        userId: user.id,
        teamId: team.id,
        type: 'TEMPLATE',
      },
      include: {
        recipients: true,
      },
    });

    const createdTemplate = templateEnvelopes.find((e) => e.title.includes('(copy)'));
    expect(createdTemplate).toBeDefined();

    // CRITICAL: Verify the secondaryId uses the template_ prefix.
    expect(createdTemplate!.secondaryId).toMatch(/^template_\d+$/);

    // Verify no recipients were included.
    expect(createdTemplate!.recipients.length).toBe(0);
  });

  test('save document as template without fields but with recipients', async ({ page }) => {
    const { user, team, envelopeId, recipientEmail } = await createDocumentWithRecipientAndField();

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

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

    // Click the "Save as Template" sidebar action.
    await page.locator('button[title="Save as Template"]').click();

    // The dialog should appear.
    await expect(page.getByRole('heading', { name: 'Save as Template' })).toBeVisible();

    // Uncheck "Include Fields" but keep "Include Recipients".
    await page.locator('#envelopeIncludeFields').click();
    await expect(page.locator('#envelopeIncludeRecipients')).toBeChecked();
    await expect(page.locator('#envelopeIncludeFields')).not.toBeChecked();

    // Click "Save as Template".
    await page.getByRole('button', { name: 'Save as Template' }).click();

    // Assert toast appears.
    await expectToastTextToBeVisible(page, 'Template Created');

    // The page should have navigated to the new template's edit page.
    await expect(page).toHaveURL(/\/templates\/.*\/edit/);

    // Verify the template was created with recipients but no fields.
    const templateEnvelopes = await prisma.envelope.findMany({
      where: {
        userId: user.id,
        teamId: team.id,
        type: 'TEMPLATE',
      },
      include: {
        recipients: {
          include: {
            fields: true,
          },
        },
      },
    });

    const createdTemplate = templateEnvelopes.find((e) => e.title.includes('(copy)'));
    expect(createdTemplate).toBeDefined();

    // CRITICAL: Verify the secondaryId uses the template_ prefix.
    expect(createdTemplate!.secondaryId).toMatch(/^template_\d+$/);

    // Verify recipients were included.
    expect(createdTemplate!.recipients.length).toBe(1);
    expect(createdTemplate!.recipients[0].email).toBe(recipientEmail);

    // Verify no fields were included.
    expect(createdTemplate!.recipients[0].fields.length).toBe(0);
  });
});

test.describe('documents table', () => {
  test('save as template from document row dropdown', async ({ page }) => {
    const { user, team, envelopeId, recipientEmail } = await createDocumentWithRecipientAndField();

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

    // Wait for the documents table to load.
    await expect(page.getByRole('heading', { name: 'Documents' })).toBeVisible();

    // Click the actions dropdown for the document row.
    await page.getByTestId('document-table-action-btn').first().click();

    // Click "Save as Template" in the dropdown.
    await page.getByRole('menuitem', { name: 'Save as Template' }).click();

    // The dialog should appear.
    await expect(page.getByRole('heading', { name: 'Save as Template' })).toBeVisible();

    // Click "Save as Template".
    await page.getByRole('button', { name: 'Save as Template' }).click();

    // Assert toast appears.
    await expectToastTextToBeVisible(page, 'Template Created');

    // The page should have navigated to the new template's edit page.
    await expect(page).toHaveURL(/\/templates\/.*\/edit/);

    // Verify the template was created with correct type and secondaryId.
    const templateEnvelopes = await prisma.envelope.findMany({
      where: {
        userId: user.id,
        teamId: team.id,
        type: 'TEMPLATE',
      },
      include: {
        recipients: {
          include: {
            fields: true,
          },
        },
      },
    });

    const createdTemplate = templateEnvelopes.find((e) => e.title.includes('(copy)'));
    expect(createdTemplate).toBeDefined();

    // CRITICAL: Verify the secondaryId uses the template_ prefix.
    expect(createdTemplate!.secondaryId).toMatch(/^template_\d+$/);
    expect(createdTemplate!.type).toBe(EnvelopeType.TEMPLATE);

    // Verify recipients and fields were included (defaults are both checked).
    expect(createdTemplate!.recipients.length).toBe(1);
    expect(createdTemplate!.recipients[0].fields.length).toBe(1);
  });
});

test.describe('document index page', () => {
  test('save as template from document page dropdown', async ({ page }) => {
    const { user, team, envelopeId, recipientEmail } = await createDocumentWithRecipientAndField();

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

    // Wait for the document page to load.
    await expect(page.getByRole('heading', { name: /E2E Save as Template/ })).toBeVisible();

    // Click the more actions dropdown trigger.
    await page.getByTestId('document-page-view-action-btn').click();

    // Click "Save as Template" in the dropdown.
    await page.getByRole('menuitem', { name: 'Save as Template' }).click();

    // The dialog should appear.
    await expect(page.getByRole('heading', { name: 'Save as Template' })).toBeVisible();

    // Click "Save as Template".
    await page.getByRole('button', { name: 'Save as Template' }).click();

    // Assert toast appears.
    await expectToastTextToBeVisible(page, 'Template Created');

    // The page should have navigated to the new template's edit page.
    await expect(page).toHaveURL(/\/templates\/.*\/edit/);

    // Verify the template was created with correct type and secondaryId.
    const templateEnvelopes = await prisma.envelope.findMany({
      where: {
        userId: user.id,
        teamId: team.id,
        type: 'TEMPLATE',
      },
    });

    const createdTemplate = templateEnvelopes.find((e) => e.title.includes('(copy)'));
    expect(createdTemplate).toBeDefined();

    // CRITICAL: Verify the secondaryId uses the template_ prefix.
    expect(createdTemplate!.secondaryId).toMatch(/^template_\d+$/);
    expect(createdTemplate!.type).toBe(EnvelopeType.TEMPLATE);
  });
});

test.describe('legacy ID correctness', () => {
  test('save as template uses template counter, not document counter', async ({ page }) => {
    // Record the current counter values before the operation.
    const [documentCounterBefore, templateCounterBefore] = await Promise.all([
      prisma.counter.findUnique({ where: { id: 'document' } }),
      prisma.counter.findUnique({ where: { id: 'template' } }),
    ]);

    const surface = await openDocumentEnvelopeEditor(page);

    // Click the "Save as Template" sidebar action.
    await page.locator('button[title="Save as Template"]').click();
    await expect(page.getByRole('heading', { name: 'Save as Template' })).toBeVisible();

    // Click "Save as Template".
    await page.getByRole('button', { name: 'Save as Template' }).click();
    await expectToastTextToBeVisible(page, 'Template Created');
    await expect(page).toHaveURL(/\/templates\/.*\/edit/);

    // Record the counter values after the operation.
    const [documentCounterAfter, templateCounterAfter] = await Promise.all([
      prisma.counter.findUnique({ where: { id: 'document' } }),
      prisma.counter.findUnique({ where: { id: 'template' } }),
    ]);

    // The template counter MUST have incremented (at least once - could be more due to
    // the seedBlankDocument call in openDocumentEnvelopeEditor seeding other templates).
    expect(templateCounterAfter!.value).toBeGreaterThan(templateCounterBefore!.value);

    // Verify the created template's secondaryId matches the template counter.
    const createdTemplate = await prisma.envelope.findFirst({
      where: {
        userId: surface.userId,
        teamId: surface.teamId,
        type: 'TEMPLATE',
      },
      orderBy: { createdAt: 'desc' },
    });

    expect(createdTemplate).not.toBeNull();
    expect(createdTemplate!.secondaryId).toBe(`template_${templateCounterAfter!.value}`);
    expect(createdTemplate!.secondaryId).not.toMatch(/^document_/);
  });
});
