import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
import { alphaid } from '@documenso/lib/universal/id';
import { prisma } from '@documenso/prisma';
import { seedBlankDocument } from '@documenso/prisma/seed/documents';
import { seedUser } from '@documenso/prisma/seed/users';
import { expect, test } from '@playwright/test';
import { WebhookCallStatus, WebhookTriggerEvents } from '@prisma/client';

import { apiSignin, apiSignout } from '../fixtures/authentication';
import { expectTextToBeVisible, openDropdownMenu } from '../fixtures/generic';

/**
 * Helper function to seed a webhook directly in the database for testing.
 */
const seedWebhook = async ({
  webhookUrl,
  eventTriggers,
  secret,
  enabled,
  userId,
  teamId,
}: {
  webhookUrl: string;
  eventTriggers: WebhookTriggerEvents[];
  secret?: string | null;
  enabled?: boolean;
  userId: number;
  teamId: number;
}) => {
  return await prisma.webhook.create({
    data: {
      webhookUrl,
      eventTriggers,
      secret: secret ?? null,
      enabled: enabled ?? true,
      userId,
      teamId,
    },
  });
};

test('[WEBHOOKS]: create webhook', async ({ page }) => {
  const { user, team } = await seedUser();

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

  const webhookUrl = `https://example.com/webhook-${Date.now()}`;

  // Click Create Webhook button
  await page.getByRole('button', { name: 'Create Webhook' }).click();

  // Fill in the form
  await page.getByLabel('Webhook URL*').fill(webhookUrl);

  // Select event trigger - click on the triggers field and select DOCUMENT_CREATED
  await page.getByLabel('Triggers').click();
  await page.waitForTimeout(200); // Wait for dropdown to open
  await page.getByText('document.created').click();

  // Click outside the triggers field to close the dropdown
  await page.getByText('The URL for Documenso to send webhook events to.').click();

  // Fill in the form
  await page.getByLabel('Secret').fill('secret');

  // Submit the form
  await page.getByRole('button', { name: 'Create' }).click();

  // Wait for success toast
  await expectTextToBeVisible(page, 'Webhook created');
  await expectTextToBeVisible(page, 'The webhook was successfully created.');

  // Verify webhook appears in the list
  await expect(page.getByText(webhookUrl)).toBeVisible();

  // Directly check database
  const dbWebhook = await prisma.webhook.findFirstOrThrow({
    where: {
      userId: user.id,
    },
  });

  expect(dbWebhook?.eventTriggers).toEqual([WebhookTriggerEvents.DOCUMENT_CREATED]);
  expect(dbWebhook?.secret).toBe('secret');
  expect(dbWebhook?.enabled).toBe(true);
});

test('[WEBHOOKS]: view webhooks', async ({ page }) => {
  const { user, team } = await seedUser();

  const webhookUrl = `https://example.com/webhook-${Date.now()}`;

  // Create a webhook via seeding
  const webhook = await seedWebhook({
    webhookUrl,
    eventTriggers: [WebhookTriggerEvents.DOCUMENT_CREATED, WebhookTriggerEvents.DOCUMENT_SENT],
    userId: user.id,
    teamId: team.id,
    enabled: true,
  });

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

  // Verify webhook is visible in the table
  await expect(page.getByText(webhookUrl)).toBeVisible();
  await expect(page.getByText('Enabled')).toBeVisible();
  await expect(page.getByText('2 Events')).toBeVisible();

  // Click on webhook to navigate to detail page
  await page.getByText(webhookUrl).click();

  // Verify detail page shows webhook information
  await page.waitForURL(`/t/${team.url}/settings/webhooks/${webhook.id}`);
  await expect(page.getByText(webhookUrl)).toBeVisible();
  await expect(page.getByText('Enabled')).toBeVisible();
});

test('[WEBHOOKS]: delete webhook', async ({ page }) => {
  const { user, team } = await seedUser();

  const webhookUrl = `https://example.com/webhook-${Date.now()}`;

  // Create a webhook via seeding
  const webhook = await seedWebhook({
    webhookUrl,
    eventTriggers: [WebhookTriggerEvents.DOCUMENT_CREATED],
    userId: user.id,
    teamId: team.id,
  });

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

  // Verify webhook is visible
  await expect(page.getByText(webhookUrl)).toBeVisible();

  // Find the row with the webhook and click the action dropdown
  const webhookRow = page.locator('tr', { hasText: webhookUrl });
  const actionBtn = webhookRow.getByTestId('webhook-table-action-btn');
  await openDropdownMenu(page, actionBtn);

  // Click Delete menu item
  await expect(page.getByRole('menuitem', { name: 'Delete' })).toBeVisible();
  await page.getByRole('menuitem', { name: 'Delete' }).click();

  // Fill in confirmation field
  const deleteMessage = `delete ${webhookUrl}`;
  // The label contains "Confirm by typing:" followed by the delete message
  await page.getByLabel(/Confirm by typing/).fill(deleteMessage);

  // Click delete button
  await page.getByRole('button', { name: 'Delete' }).click();

  // Wait for success toast
  await expectTextToBeVisible(page, 'Webhook deleted');
  await expectTextToBeVisible(page, 'The webhook has been successfully deleted.');

  // Verify webhook is removed from the list
  await expect(page.getByText(webhookUrl)).not.toBeVisible();
});

test('[WEBHOOKS]: update webhook', async ({ page }) => {
  const { user, team } = await seedUser();

  const originalWebhookUrl = `https://example.com/webhook-original-${Date.now()}`;
  const updatedWebhookUrl = `https://example.com/webhook-updated-${Date.now()}`;

  // Create a webhook via seeding with initial values
  const webhook = await seedWebhook({
    webhookUrl: originalWebhookUrl,
    eventTriggers: [WebhookTriggerEvents.DOCUMENT_CREATED, WebhookTriggerEvents.DOCUMENT_SENT],
    userId: user.id,
    teamId: team.id,
    enabled: true,
  });

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

  // Verify webhook is visible with original values
  await expect(page.getByText(originalWebhookUrl)).toBeVisible();
  await expect(page.getByText('Enabled')).toBeVisible();
  await expect(page.getByText('2 Events')).toBeVisible();

  // Find the row with the webhook and click the action dropdown
  const webhookRow = page.locator('tr', { hasText: originalWebhookUrl });
  const actionBtn = webhookRow.getByTestId('webhook-table-action-btn');
  await openDropdownMenu(page, actionBtn);

  // Click Edit menu item
  await expect(page.getByRole('menuitem', { name: 'Edit' })).toBeVisible();
  await page.getByRole('menuitem', { name: 'Edit' }).click();

  // Wait for dialog to open
  await page.waitForTimeout(200);

  // Change the webhook URL
  await page.getByLabel('Webhook URL').clear();
  await page.getByLabel('Webhook URL').fill(updatedWebhookUrl);

  // Disable the webhook (toggle the switch)
  const enabledSwitch = page.getByLabel('Enabled');
  const isChecked = await enabledSwitch.isChecked();
  if (isChecked) {
    await enabledSwitch.click();
  }

  // Change the event triggers - remove one existing event and add a new one
  // The selected items are shown as badges with remove buttons
  // Remove one of the existing events (DOCUMENT_SENT) by clicking its remove button
  const removeButtons = page.locator('button[aria-label="Remove"]');
  const removeButtonCount = await removeButtons.count();

  // Remove the "DOCUMENT_SENT" event (this will remove one of the two)
  if (removeButtonCount > 0) {
    await removeButtons.nth(1).click();
    await page.waitForTimeout(200);
  }

  // Add new event triggers
  await page.getByLabel('Triggers').click();
  await page.waitForTimeout(200);

  // Select DOCUMENT_COMPLETED (this will be added to the remaining DOCUMENT_CREATED)
  await page.getByText('document.completed').click();
  await page.waitForTimeout(200);

  // Click outside to close the dropdown
  await page.getByText('The URL for Documenso to send webhook events to.').click();

  // Submit the form
  await page.getByRole('button', { name: 'Update' }).click();

  // Wait for success toast
  await expectTextToBeVisible(page, 'Webhook updated');
  await expectTextToBeVisible(page, 'The webhook has been updated successfully.');

  // Verify changes are reflected in the list
  // The old URL should be gone and new URL should be visible
  await expect(page.getByText(originalWebhookUrl)).not.toBeVisible();
  await expect(page.getByText(updatedWebhookUrl)).toBeVisible();
  // Verify webhook is disabled
  await expect(page.getByText('Disabled')).toBeVisible();
  // Verify event count is still 2 (one removed, one added - DOCUMENT_CREATED and DOCUMENT_COMPLETED)
  await expect(page.getByText('2 Events')).toBeVisible();

  // Check the database directly to verify
  const dbWebhook = await prisma.webhook.findUnique({
    where: {
      id: webhook.id,
    },
  });

  expect(dbWebhook?.eventTriggers).toEqual([
    WebhookTriggerEvents.DOCUMENT_CREATED,
    WebhookTriggerEvents.DOCUMENT_COMPLETED,
  ]);
  expect(dbWebhook?.enabled).toBe(false);
  expect(dbWebhook?.webhookUrl).toBe(updatedWebhookUrl);
  expect(dbWebhook?.secret).toBe('');
});

test('[WEBHOOKS]: cannot see unrelated webhooks', async ({ page }) => {
  // Create two separate users with teams
  const user1Data = await seedUser();
  const user2Data = await seedUser();

  const webhookUrl1 = `https://example.com/webhook-team1-${alphaid(12)}`;
  const webhookUrl2 = `https://example.com/webhook-team2-${alphaid(12)}`;

  // Create webhooks for both teams with DOCUMENT_CREATED event
  const webhook1 = await seedWebhook({
    webhookUrl: webhookUrl1,
    eventTriggers: [WebhookTriggerEvents.DOCUMENT_CREATED],
    userId: user1Data.user.id,
    teamId: user1Data.team.id,
    enabled: true,
  });

  const webhook2 = await seedWebhook({
    webhookUrl: webhookUrl2,
    eventTriggers: [WebhookTriggerEvents.DOCUMENT_SENT],
    userId: user2Data.user.id,
    teamId: user2Data.team.id,
  });

  // Create a document on team1 to trigger the webhook
  const document = await seedBlankDocument(user1Data.user, user1Data.team.id, {
    createDocumentOptions: {
      title: 'Test Document for Webhook',
    },
  });

  // Create a webhook call for team1's webhook (simulating the webhook being triggered)
  // Since webhooks are triggered via jobs which may not run in tests, we create the call directly
  const webhookCall1 = await prisma.webhookCall.create({
    data: {
      webhookId: webhook1.id,
      url: webhookUrl1,
      event: WebhookTriggerEvents.DOCUMENT_CREATED,
      status: WebhookCallStatus.SUCCESS,
      responseCode: 200,
      requestBody: {
        event: WebhookTriggerEvents.DOCUMENT_CREATED,
        payload: {
          id: document.id,
          title: document.title,
        },
        createdAt: new Date().toISOString(),
        webhookEndpoint: webhookUrl1,
      },
    },
  });

  // Sign in as user1
  await apiSignin({
    page,
    email: user1Data.user.email,
    redirectPath: `/t/${user1Data.team.url}/settings/webhooks`,
  });

  // Verify user1 can see their webhook
  await expect(page.getByText(webhookUrl1)).toBeVisible();
  // Verify user1 cannot see user2's webhook
  await expect(page.getByText(webhookUrl2)).not.toBeVisible();

  // Navigate to team1's webhook logs page
  await page.goto(`${NEXT_PUBLIC_WEBAPP_URL()}/t/${user1Data.team.url}/settings/webhooks/${webhook1.id}`);

  // Verify user1 can see their webhook logs
  // The webhook call should be visible in the table
  await expect(page.getByText(webhookCall1.id)).toBeVisible();
  await expect(page.getByText('200')).toBeVisible(); // Response code

  // Sign out and sign in as user2
  await apiSignout({ page });
  await apiSignin({
    page,
    email: user2Data.user.email,
    redirectPath: `/t/${user2Data.team.url}/settings/webhooks`,
  });

  // Verify user2 can see their webhook
  await expect(page.getByText(webhookUrl2)).toBeVisible();
  // Verify user2 cannot see user1's webhook
  await expect(page.getByText(webhookUrl1)).not.toBeVisible();

  // Navigate to team2's webhook logs page
  await page.goto(`${NEXT_PUBLIC_WEBAPP_URL()}/t/${user2Data.team.url}/settings/webhooks/${webhook2.id}`);

  // Verify user2 cannot see team1's webhook logs
  // The webhook call from team1 should not be visible
  await expect(page.getByText(webhookCall1.id)).not.toBeVisible();

  // Attempt to access user1's webhook detail page directly via URL
  await page.goto(`${NEXT_PUBLIC_WEBAPP_URL()}/t/${user2Data.team.url}/settings/webhooks/${webhook1.id}`);

  // Verify access is denied - should show error or redirect
  // Based on the component, it shows a 404 error page
  await expect(page.getByRole('heading', { name: 'Webhook not found' })).toBeVisible();
});
