import { Prisma, WebhookSource } from "@prisma/client";
import { cleanup } from "@testing-library/react";
import { afterEach, describe, expect, test, vi } from "vitest";
import { prisma } from "@formbricks/database";
import { DatabaseError, InvalidInputError, ValidationError } from "@formbricks/types/errors";
import { createWebhook } from "@/app/api/v1/webhooks/lib/webhook";
import { TWebhookInput } from "@/app/api/v1/webhooks/types/webhooks";
import { validateInputs } from "@/lib/utils/validate";
import { validateWebhookUrl } from "@/lib/utils/validate-webhook-url";

vi.mock("@formbricks/database", () => ({
  prisma: {
    webhook: {
      create: vi.fn(),
    },
  },
}));

vi.mock("@/lib/utils/validate", () => ({
  validateInputs: vi.fn(),
}));

vi.mock("@/lib/crypto", () => ({
  generateWebhookSecret: vi.fn(() => "whsec_test_secret_1234567890"),
}));

vi.mock("@/lib/utils/validate-webhook-url", () => ({
  validateWebhookUrl: vi.fn().mockResolvedValue(undefined),
}));

describe("createWebhook", () => {
  afterEach(() => {
    cleanup();
  });

  test("should create a webhook", async () => {
    const webhookInput: TWebhookInput = {
      environmentId: "test-env-id",
      name: "Test Webhook",
      url: "https://example.com",
      source: "user",
      triggers: ["responseCreated"],
      surveyIds: ["survey1", "survey2"],
    };

    const createdWebhook = {
      id: "webhook-id",
      environmentId: "test-env-id",
      name: "Test Webhook",
      url: "https://example.com",
      source: "user" as WebhookSource,
      triggers: ["responseCreated"],
      surveyIds: ["survey1", "survey2"],
      createdAt: new Date(),
      updatedAt: new Date(),
    } as any;

    vi.mocked(prisma.webhook.create).mockResolvedValueOnce(createdWebhook);

    const result = await createWebhook(webhookInput);

    expect(validateInputs).toHaveBeenCalled();

    expect(prisma.webhook.create).toHaveBeenCalledWith({
      data: {
        url: webhookInput.url,
        name: webhookInput.name,
        source: webhookInput.source,
        surveyIds: webhookInput.surveyIds,
        triggers: webhookInput.triggers,
        secret: "whsec_test_secret_1234567890",
        environment: {
          connect: {
            id: webhookInput.environmentId,
          },
        },
      },
    });

    expect(result).toEqual(createdWebhook);
  });

  test("should call validateWebhookUrl with the provided URL", async () => {
    const webhookInput: TWebhookInput = {
      environmentId: "test-env-id",
      name: "Test Webhook",
      url: "https://example.com",
      source: "user",
      triggers: ["responseCreated"],
      surveyIds: ["survey1"],
    };

    vi.mocked(prisma.webhook.create).mockResolvedValueOnce({} as any);

    await createWebhook(webhookInput);

    expect(validateWebhookUrl).toHaveBeenCalledWith("https://example.com");
  });

  test("should throw InvalidInputError and skip Prisma create when URL fails SSRF validation", async () => {
    const webhookInput: TWebhookInput = {
      environmentId: "test-env-id",
      name: "Test Webhook",
      url: "http://169.254.169.254/latest/meta-data/",
      source: "user",
      triggers: ["responseCreated"],
      surveyIds: ["survey1"],
    };

    vi.mocked(validateWebhookUrl).mockRejectedValueOnce(
      new InvalidInputError("Webhook URL must not point to private or internal IP addresses")
    );

    await expect(createWebhook(webhookInput)).rejects.toThrow(InvalidInputError);
    expect(prisma.webhook.create).not.toHaveBeenCalled();
  });

  test("should throw a ValidationError if the input data does not match the ZWebhookInput schema", async () => {
    const invalidWebhookInput = {
      environmentId: "test-env-id",
      name: "Test Webhook",
      url: 123, // Invalid URL
      source: "user" as WebhookSource,
      triggers: ["responseCreated"],
      surveyIds: ["survey1", "survey2"],
    };

    vi.mocked(validateInputs).mockImplementation(() => {
      throw new ValidationError("Validation failed");
    });

    await expect(createWebhook(invalidWebhookInput as any)).rejects.toThrowError(ValidationError);
  });

  test("should throw a DatabaseError if a PrismaClientKnownRequestError occurs", async () => {
    const webhookInput: TWebhookInput = {
      environmentId: "test-env-id",
      name: "Test Webhook",
      url: "https://example.com",
      source: "user",
      triggers: ["responseCreated"],
      surveyIds: ["survey1", "survey2"],
    };

    vi.mocked(prisma.webhook.create).mockRejectedValueOnce(
      new Prisma.PrismaClientKnownRequestError("Test error", {
        code: "P2002",
        clientVersion: "5.0.0",
      })
    );

    await expect(createWebhook(webhookInput)).rejects.toThrowError(DatabaseError);
  });

  test("should throw a DatabaseError when provided with invalid surveyIds", async () => {
    const webhookInput: TWebhookInput = {
      environmentId: "test-env-id",
      name: "Test Webhook",
      url: "https://example.com",
      source: "user",
      triggers: ["responseCreated"],
      surveyIds: ["invalid-survey-id"],
    };

    vi.mocked(prisma.webhook.create).mockRejectedValueOnce(new Error("Foreign key constraint violation"));

    await expect(createWebhook(webhookInput)).rejects.toThrowError(DatabaseError);
  });

  test("should handle edge case URLs that are technically valid but problematic", async () => {
    const webhookInput: TWebhookInput = {
      environmentId: "test-env-id",
      name: "Test Webhook",
      url: "http://localhost:3000", // Example of a potentially problematic URL
      source: "user",
      triggers: ["responseCreated"],
      surveyIds: ["survey1", "survey2"],
    };

    vi.mocked(prisma.webhook.create).mockRejectedValueOnce(new DatabaseError("Invalid URL"));

    await expect(createWebhook(webhookInput)).rejects.toThrowError(DatabaseError);

    expect(validateInputs).toHaveBeenCalled();
    expect(prisma.webhook.create).toHaveBeenCalledWith({
      data: {
        url: webhookInput.url,
        name: webhookInput.name,
        source: webhookInput.source,
        surveyIds: webhookInput.surveyIds,
        triggers: webhookInput.triggers,
        secret: "whsec_test_secret_1234567890",
        environment: {
          connect: {
            id: webhookInput.environmentId,
          },
        },
      },
    });
  });
});
