import { nanoid } from '@documenso/lib/universal/id';
import { prisma } from '@documenso/prisma';
import { expect, type Page, test } from '@playwright/test';
import { DocumentDistributionMethod, DocumentVisibility } from '@prisma/client';

import {
  getEnvelopeEditorSettingsTrigger,
  openDocumentEnvelopeEditor,
  openEmbeddedEnvelopeEditor,
  openTemplateEnvelopeEditor,
  persistEmbeddedEnvelope,
  type TEnvelopeEditorSurface,
} from '../fixtures/envelope-editor';
import { expectToastTextToBeVisible } from '../fixtures/generic';

type SettingsFlowData = {
  externalId: string;
  isEmbedded: boolean;
};

const TEST_SETTINGS_VALUES = {
  replyTo: 'e2e-settings@example.com',
  redirectUrl: 'https://example.com/e2e-settings-complete',
  subject: 'E2E settings subject',
  message: 'E2E settings message',
  language: 'French',
  dateFormat: 'DD/MM/YYYY',
  timezone: 'Europe/London',
  distributionMethod: 'None',
  expirationMode: 'Custom duration',
  expirationAmount: 5,
  expirationUnit: 'Weeks',
  reminderMode: 'Enabled',
  reminderSendAfterAmount: 3,
  reminderSendAfterUnit: 'Days',
  reminderRepeatMode: 'Custom interval',
  reminderRepeatAmount: 7,
  reminderRepeatUnit: 'Days',
  accessAuth: 'Require account',
  actionAuth: 'Require password',
  visibility: 'Managers and above',
};

const DB_EXPECTED_VALUES = {
  language: 'fr',
  dateFormat: 'dd/MM/yyyy',
  timezone: 'Europe/London',
  distributionMethod: DocumentDistributionMethod.NONE,
  envelopeExpirationPeriod: { unit: 'week', amount: 5 },
  reminderSettings: {
    sendAfter: { unit: 'day', amount: 3 },
    repeatEvery: { unit: 'day', amount: 7 },
  },
  visibility: DocumentVisibility.MANAGER_AND_ABOVE,
  globalAccessAuth: ['ACCOUNT'],
  globalActionAuth: ['PASSWORD'],
  emailSettings: {
    recipientSigned: false,
    recipientSigningRequest: false,
    recipientRemoved: false,
    documentPending: false,
    documentCompleted: false,
    documentDeleted: false,
    ownerDocumentCompleted: false,
    ownerRecipientExpired: false,
    ownerDocumentCreated: false,
  },
};

const openSettingsDialog = async (root: Page) => {
  await getEnvelopeEditorSettingsTrigger(root).click();
  await expect(root.getByRole('heading', { name: 'Document Settings' })).toBeVisible();
};

const clickSettingsDialogHeader = async (root: Page) => {
  await root.locator('[data-testid="envelope-editor-settings-dialog-header"]').click();
};

const getComboboxByLabel = (root: Page, label: string) =>
  root.locator(`label:has-text("${label}")`).locator('xpath=..').locator('[role="combobox"]').first();

const selectMultiSelectOption = async (
  root: Page,
  dataTestId: 'documentAccessSelectValue' | 'documentActionSelectValue',
  optionLabel: string,
) => {
  const select = root.locator(`[data-testid="${dataTestId}"]`);

  await select.click();
  await root.locator('[cmdk-item]').filter({ hasText: optionLabel }).first().click();
  await clickSettingsDialogHeader(root);
};

const runSettingsFlow = async ({ root }: TEnvelopeEditorSurface, { externalId, isEmbedded }: SettingsFlowData) => {
  await openSettingsDialog(root);

  await getComboboxByLabel(root, 'Language').click();
  await root.getByRole('option', { name: TEST_SETTINGS_VALUES.language }).click();
  await clickSettingsDialogHeader(root);

  const signatureTypesCombobox = getComboboxByLabel(root, 'Allowed Signature Types');

  await signatureTypesCombobox.click();
  await root.getByRole('option', { name: 'Upload' }).click();
  await clickSettingsDialogHeader(root);

  await getComboboxByLabel(root, 'Date Format').click();
  await root.getByRole('option', { name: TEST_SETTINGS_VALUES.dateFormat, exact: true }).click();
  await clickSettingsDialogHeader(root);

  await getComboboxByLabel(root, 'Time Zone').click();
  await root.locator('[cmdk-input]').last().fill(TEST_SETTINGS_VALUES.timezone);
  await root.getByRole('option', { name: TEST_SETTINGS_VALUES.timezone }).click();
  await clickSettingsDialogHeader(root);

  await root.locator('input[name="externalId"]').fill(externalId);
  await root.locator('input[name="meta.redirectUrl"]').fill(TEST_SETTINGS_VALUES.redirectUrl);
  await root.getByRole('button', { name: 'Notifications' }).click();
  // Fill email-content fields and toggle recipient-facing notification checkboxes
  // while distributionMethod is still EMAIL. After it flips to NONE below, these
  // controls are disabled because no email is sent to recipients.
  await root.locator('input[name="meta.subject"]').fill(TEST_SETTINGS_VALUES.subject);
  await root.locator('textarea[name="meta.message"]').fill(TEST_SETTINGS_VALUES.message);
  await root.locator('input[name="meta.emailReplyTo"]').fill(TEST_SETTINGS_VALUES.replyTo);
  await root.locator('#recipientSigned').click();
  await root.locator('#recipientSigningRequest').click();
  await root.locator('#recipientRemoved').click();
  await root.locator('#documentPending').click();
  await root.locator('#documentCompleted').click();
  await root.locator('#documentDeleted').click();

  await root.getByRole('button', { name: 'General' }).click();

  await root.locator('[data-testid="documentDistributionMethodSelectValue"]').click();
  await root.getByRole('option', { name: TEST_SETTINGS_VALUES.distributionMethod }).click();
  await clickSettingsDialogHeader(root);

  await getComboboxByLabel(root, 'Expiration').click();
  await root.getByRole('option', { name: TEST_SETTINGS_VALUES.expirationMode }).click();
  await root.getByRole('spinbutton').clear();
  await root.getByRole('spinbutton').fill(String(TEST_SETTINGS_VALUES.expirationAmount));
  const expirationUnitTrigger = root
    .locator('button[role="combobox"]')
    .filter({ hasText: /Months|Days|Weeks|Years/ })
    .first();
  await expirationUnitTrigger.click();
  await root.getByRole('option', { name: TEST_SETTINGS_VALUES.expirationUnit }).click();
  await clickSettingsDialogHeader(root);

  // Configure reminder settings.
  await root.getByRole('button', { name: 'Reminders' }).click();

  await root.locator('[data-testid="reminder-mode-select"]').click();
  await root.getByRole('option', { name: TEST_SETTINGS_VALUES.reminderMode }).click();
  await clickSettingsDialogHeader(root);

  await root.locator('[data-testid="reminder-send-after-amount"]').clear();
  await root
    .locator('[data-testid="reminder-send-after-amount"]')
    .fill(String(TEST_SETTINGS_VALUES.reminderSendAfterAmount));

  await root.locator('[data-testid="reminder-send-after-unit"]').click();
  await root.getByRole('option', { name: TEST_SETTINGS_VALUES.reminderSendAfterUnit }).click();
  await clickSettingsDialogHeader(root);

  await root.locator('[data-testid="reminder-repeat-mode-select"]').click();
  await root.getByRole('option', { name: TEST_SETTINGS_VALUES.reminderRepeatMode }).click();
  await clickSettingsDialogHeader(root);

  await root.locator('[data-testid="reminder-repeat-amount"]').clear();
  await root.locator('[data-testid="reminder-repeat-amount"]').fill(String(TEST_SETTINGS_VALUES.reminderRepeatAmount));

  await root.locator('[data-testid="reminder-repeat-unit"]').click();
  await root.getByRole('option', { name: TEST_SETTINGS_VALUES.reminderRepeatUnit }).click();
  await clickSettingsDialogHeader(root);

  const spinbuttons = root.getByRole('spinbutton');
  await spinbuttons.first().clear();
  await spinbuttons.first().fill(String(TEST_SETTINGS_VALUES.reminderSendAfterAmount));

  const sendAfterUnitTrigger = root
    .locator('button[role="combobox"]')
    .filter({ hasText: /Days|Weeks|Months/ })
    .first();
  await sendAfterUnitTrigger.click();
  await root.getByRole('option', { name: TEST_SETTINGS_VALUES.reminderSendAfterUnit }).click();
  await clickSettingsDialogHeader(root);

  const repeatModeSelect = root
    .locator('button[role="combobox"]')
    .filter({ hasText: /Custom interval|Don't repeat/ })
    .first();
  await repeatModeSelect.click();
  await root.getByRole('option', { name: TEST_SETTINGS_VALUES.reminderRepeatMode }).click();
  await clickSettingsDialogHeader(root);

  await spinbuttons.last().clear();
  await spinbuttons.last().fill(String(TEST_SETTINGS_VALUES.reminderRepeatAmount));

  const repeatUnitTrigger = root
    .locator('button[role="combobox"]')
    .filter({ hasText: /Days|Weeks|Months/ })
    .last();
  await repeatUnitTrigger.click();
  await root.getByRole('option', { name: TEST_SETTINGS_VALUES.reminderRepeatUnit }).click();
  await clickSettingsDialogHeader(root);

  await root.getByRole('button', { name: 'Notifications' }).click();

  // Distribution is NONE: email-content fields stay rendered but disabled,
  // recipient-facing checkboxes are hidden entirely and replaced by an alert,
  // owner-facing checkboxes stay editable so we toggle them here.
  await expect(root.locator('input[name="meta.subject"]')).toBeDisabled();
  await expect(root.locator('textarea[name="meta.message"]')).toBeDisabled();
  await expect(root.locator('input[name="meta.emailReplyTo"]')).toBeDisabled();
  await expect(root.locator('#recipientSigned')).toHaveCount(0);
  await expect(root.locator('#recipientSigningRequest')).toHaveCount(0);
  await expect(root.locator('#recipientRemoved')).toHaveCount(0);
  await expect(root.locator('#documentPending')).toHaveCount(0);
  await expect(root.locator('#documentCompleted')).toHaveCount(0);
  await expect(root.locator('#documentDeleted')).toHaveCount(0);
  await expect(root.getByText(/Email distribution needs to be enabled/)).toBeVisible();

  // Email Sender select only renders when the org has the emailDomains feature
  // flag plus allowConfigureEmailSender, so the assertion is conditional.
  const emailSenderSelect = getComboboxByLabel(root, 'Email Sender');
  const hasEmailSenderSelect = (await emailSenderSelect.count()) > 0;

  if (hasEmailSenderSelect) {
    await expect(emailSenderSelect).toBeDisabled();
  }

  await expect(root.locator('#ownerDocumentCompleted')).toBeEnabled();
  await root.locator('#ownerDocumentCompleted').click();
  await root.locator('#ownerRecipientExpired').click();
  await root.locator('#ownerDocumentCreated').click();

  await root.getByRole('button', { name: 'Security' }).click();
  await selectMultiSelectOption(root, 'documentAccessSelectValue', TEST_SETTINGS_VALUES.accessAuth);

  const actionAuthSelect = root.locator('[data-testid="documentActionSelectValue"]');
  const hasActionAuthSelect = (await actionAuthSelect.count()) > 0;

  if (hasActionAuthSelect) {
    await selectMultiSelectOption(root, 'documentActionSelectValue', TEST_SETTINGS_VALUES.actionAuth);
  }

  if (isEmbedded) {
    await expect(root.locator('[data-testid="documentVisibilitySelectValue"]')).toHaveCount(0);
  } else {
    await root.locator('[data-testid="documentVisibilitySelectValue"]').click();
    await root.getByRole('option', { name: TEST_SETTINGS_VALUES.visibility }).click();
    await clickSettingsDialogHeader(root);
  }

  await root.getByRole('button', { name: 'Update' }).click();

  if (!isEmbedded) {
    await expectToastTextToBeVisible(root, 'Envelope updated');
  }

  await openSettingsDialog(root);

  await expect(root.locator('input[name="externalId"]')).toHaveValue(externalId);
  await expect(root.locator('input[name="meta.redirectUrl"]')).toHaveValue(TEST_SETTINGS_VALUES.redirectUrl);
  await expect(getComboboxByLabel(root, 'Language')).toContainText(TEST_SETTINGS_VALUES.language);
  await expect(getComboboxByLabel(root, 'Allowed Signature Types')).not.toContainText('Upload');
  await expect(getComboboxByLabel(root, 'Date Format')).toContainText(TEST_SETTINGS_VALUES.dateFormat);
  await expect(getComboboxByLabel(root, 'Time Zone')).toContainText(TEST_SETTINGS_VALUES.timezone);
  await expect(root.locator('[data-testid="documentDistributionMethodSelectValue"]')).toContainText(
    TEST_SETTINGS_VALUES.distributionMethod,
  );
  await expect(getComboboxByLabel(root, 'Expiration')).toContainText(TEST_SETTINGS_VALUES.expirationMode);
  await expect(root.getByRole('spinbutton')).toHaveValue(String(TEST_SETTINGS_VALUES.expirationAmount));
  await expect(
    root.locator('button[role="combobox"]').filter({ hasText: TEST_SETTINGS_VALUES.expirationUnit }).first(),
  ).toBeVisible();

  // Verify reminder settings persisted in UI.
  await root.getByRole('button', { name: 'Reminders' }).click();
  await expect(root.locator('[data-testid="reminder-mode-select"]')).toContainText(TEST_SETTINGS_VALUES.reminderMode);
  await expect(root.locator('[data-testid="reminder-send-after-amount"]')).toHaveValue(
    String(TEST_SETTINGS_VALUES.reminderSendAfterAmount),
  );
  await expect(root.locator('[data-testid="reminder-send-after-unit"]')).toContainText(
    TEST_SETTINGS_VALUES.reminderSendAfterUnit,
  );
  await expect(root.locator('[data-testid="reminder-repeat-mode-select"]')).toContainText(
    TEST_SETTINGS_VALUES.reminderRepeatMode,
  );
  await expect(root.locator('[data-testid="reminder-repeat-amount"]')).toHaveValue(
    String(TEST_SETTINGS_VALUES.reminderRepeatAmount),
  );
  await expect(root.locator('[data-testid="reminder-repeat-unit"]')).toContainText(
    TEST_SETTINGS_VALUES.reminderRepeatUnit,
  );

  await root.getByRole('button', { name: 'Notifications' }).click();
  // Distribution persisted as NONE: recipient-facing checkboxes are hidden, owner-facing
  // checkboxes remain visible and persist their stored values. Email-content fields are
  // still rendered (disabled) and persist their stored values.
  await expect(root.locator('#recipientSigned')).toHaveCount(0);
  await expect(root.locator('#recipientSigningRequest')).toHaveCount(0);
  await expect(root.locator('#recipientRemoved')).toHaveCount(0);
  await expect(root.locator('#documentPending')).toHaveCount(0);
  await expect(root.locator('#documentCompleted')).toHaveCount(0);
  await expect(root.locator('#documentDeleted')).toHaveCount(0);
  await expect(root.getByText(/Email distribution needs to be enabled/)).toBeVisible();
  await expect(root.locator('#ownerDocumentCompleted')).toHaveAttribute('aria-checked', 'false');
  await expect(root.locator('#ownerRecipientExpired')).toHaveAttribute('aria-checked', 'false');
  await expect(root.locator('#ownerDocumentCreated')).toHaveAttribute('aria-checked', 'false');
  await expect(root.locator('input[name="meta.emailReplyTo"]')).toHaveValue(TEST_SETTINGS_VALUES.replyTo);
  await expect(root.locator('input[name="meta.subject"]')).toHaveValue(TEST_SETTINGS_VALUES.subject);
  await expect(root.locator('textarea[name="meta.message"]')).toHaveValue(TEST_SETTINGS_VALUES.message);

  await root.getByRole('button', { name: 'Security' }).click();
  await expect(root.locator('[data-testid="documentAccessSelectValue"]')).toContainText(
    TEST_SETTINGS_VALUES.accessAuth,
  );

  if (hasActionAuthSelect) {
    await expect(root.locator('[data-testid="documentActionSelectValue"]')).toContainText(
      TEST_SETTINGS_VALUES.actionAuth,
    );
  }

  if (isEmbedded) {
    await expect(root.locator('[data-testid="documentVisibilitySelectValue"]')).toHaveCount(0);
  } else {
    await expect(root.locator('[data-testid="documentVisibilitySelectValue"]')).toContainText(
      TEST_SETTINGS_VALUES.visibility,
    );
  }

  await root.getByRole('button', { name: 'Update' }).click();

  if (!isEmbedded) {
    await expectToastTextToBeVisible(root, 'Envelope updated');
  }

  return {
    hasActionAuthSelect,
  };
};

const assertEnvelopeSettingsPersistedInDatabase = async ({
  externalId,
  surface,
  hasActionAuthSelect,
  shouldAssertVisibility,
}: {
  externalId: string;
  surface: TEnvelopeEditorSurface;
  hasActionAuthSelect: boolean;
  shouldAssertVisibility: boolean;
}) => {
  const envelope = await prisma.envelope.findFirstOrThrow({
    where: {
      externalId,
      userId: surface.userId,
      teamId: surface.teamId,
      type: surface.envelopeType,
    },
    orderBy: { createdAt: 'desc' },
    include: {
      documentMeta: true,
    },
  });

  expect(envelope.externalId).toBe(externalId);
  if (shouldAssertVisibility) {
    expect(envelope.visibility).toBe(DB_EXPECTED_VALUES.visibility);
  }
  expect(envelope.documentMeta.language).toBe(DB_EXPECTED_VALUES.language);
  expect(envelope.documentMeta.dateFormat).toBe(DB_EXPECTED_VALUES.dateFormat);
  expect(envelope.documentMeta.timezone).toBe(DB_EXPECTED_VALUES.timezone);
  expect(envelope.documentMeta.distributionMethod).toBe(DB_EXPECTED_VALUES.distributionMethod);
  expect(envelope.documentMeta.envelopeExpirationPeriod).toEqual(DB_EXPECTED_VALUES.envelopeExpirationPeriod);
  expect(envelope.documentMeta.reminderSettings).toEqual(DB_EXPECTED_VALUES.reminderSettings);
  expect(envelope.documentMeta.redirectUrl).toBe(TEST_SETTINGS_VALUES.redirectUrl);
  expect(envelope.documentMeta.emailReplyTo).toBe(TEST_SETTINGS_VALUES.replyTo);
  expect(envelope.documentMeta.subject).toBe(TEST_SETTINGS_VALUES.subject);
  expect(envelope.documentMeta.message).toBe(TEST_SETTINGS_VALUES.message);
  expect(envelope.documentMeta.drawSignatureEnabled).toBe(true);
  expect(envelope.documentMeta.typedSignatureEnabled).toBe(true);
  expect(envelope.documentMeta.uploadSignatureEnabled).toBe(false);
  expect(envelope.documentMeta.emailSettings).toMatchObject(DB_EXPECTED_VALUES.emailSettings);

  const authOptions = parseAuthOptions(envelope.authOptions);

  expect(authOptions.globalAccessAuth ?? []).toEqual(DB_EXPECTED_VALUES.globalAccessAuth);

  if (hasActionAuthSelect) {
    expect(authOptions.globalActionAuth ?? []).toEqual(DB_EXPECTED_VALUES.globalActionAuth);
  }
};

const parseAuthOptions = (authOptions: unknown): { globalAccessAuth: string[]; globalActionAuth: string[] } => {
  if (!isRecord(authOptions)) {
    return {
      globalAccessAuth: [],
      globalActionAuth: [],
    };
  }

  return {
    globalAccessAuth: Array.isArray(authOptions.globalAccessAuth)
      ? authOptions.globalAccessAuth.filter((entry): entry is string => typeof entry === 'string')
      : [],
    globalActionAuth: Array.isArray(authOptions.globalActionAuth)
      ? authOptions.globalActionAuth.filter((entry): entry is string => typeof entry === 'string')
      : [],
  };
};

const isRecord = (value: unknown): value is Record<string, unknown> => typeof value === 'object' && value !== null;

test.describe('document editor', () => {
  test('update and persist settings', async ({ page }) => {
    const surface = await openDocumentEnvelopeEditor(page);
    const externalId = `e2e-settings-${nanoid()}`;

    const { hasActionAuthSelect } = await runSettingsFlow(surface, {
      externalId,
      isEmbedded: false,
    });

    await assertEnvelopeSettingsPersistedInDatabase({
      externalId,
      surface,
      hasActionAuthSelect,
      shouldAssertVisibility: true,
    });
  });
});

test.describe('template editor', () => {
  test('update and persist settings', async ({ page }) => {
    const surface = await openTemplateEnvelopeEditor(page);
    const externalId = `e2e-settings-${nanoid()}`;

    const { hasActionAuthSelect } = await runSettingsFlow(surface, {
      externalId,
      isEmbedded: false,
    });

    await assertEnvelopeSettingsPersistedInDatabase({
      externalId,
      surface,
      hasActionAuthSelect,
      shouldAssertVisibility: true,
    });
  });
});

test.describe('embedded create', () => {
  test('update and persist settings', async ({ page }) => {
    const surface = await openEmbeddedEnvelopeEditor(page, {
      envelopeType: 'DOCUMENT',
      tokenNamePrefix: 'e2e-embed-settings',
    });
    const externalId = `e2e-settings-${nanoid()}`;

    const { hasActionAuthSelect } = await runSettingsFlow(surface, {
      externalId,
      isEmbedded: true,
    });

    await persistEmbeddedEnvelope(surface);

    await assertEnvelopeSettingsPersistedInDatabase({
      externalId,
      surface,
      hasActionAuthSelect,
      shouldAssertVisibility: false,
    });
  });
});

test.describe('embedded edit', () => {
  test('update and persist settings', async ({ page }) => {
    const surface = await openEmbeddedEnvelopeEditor(page, {
      envelopeType: 'TEMPLATE',
      mode: 'edit',
      tokenNamePrefix: 'e2e-embed-settings',
    });
    const externalId = `e2e-settings-${nanoid()}`;

    const { hasActionAuthSelect } = await runSettingsFlow(surface, {
      externalId,
      isEmbedded: true,
    });

    await persistEmbeddedEnvelope(surface);

    await assertEnvelopeSettingsPersistedInDatabase({
      externalId,
      surface,
      hasActionAuthSelect,
      shouldAssertVisibility: false,
    });
  });
});
