import { describe, test, expect, beforeAll, afterAll } from "vitest";
import type { PrismaClient } from "@trigger.dev/database";
import type * as DeliverErrorGroupAlertModule from "../app/v3/services/alerts/deliverErrorGroupAlert.server.js";
import type * as SecretStoreModule from "../app/services/secrets/secretStore.server.js";

let DeliverErrorGroupAlertService: typeof DeliverErrorGroupAlertModule.DeliverErrorGroupAlertService;
let prisma: PrismaClient;
let getSecretStore: typeof SecretStoreModule.getSecretStore;

type ErrorAlertPayload = {
  channelId: string;
  projectId: string;
  classification: "new_issue" | "regression" | "unignored";
  error: {
    fingerprint: string;
    environmentId: string;
    environmentSlug: string;
    environmentName: string;
    taskIdentifier: string;
    errorType: string;
    errorMessage: string;
    sampleStackTrace: string;
    firstSeen: string;
    lastSeen: string;
    occurrenceCount: number;
  };
};

let testChannelId: string;
let testProjectId: string;
let testOrganizationId: string;
let testSecretKey: string;
let testSecretReferenceId: string;

// Helper to create mock error payloads
function createMockErrorPayload(
  overrides: Partial<Omit<ErrorAlertPayload, "error">> & {
    error?: Partial<ErrorAlertPayload["error"]>;
  } = {}
): ErrorAlertPayload {
  const { error: errorOverrides, ...payloadOverrides } = overrides;

  const defaultError: ErrorAlertPayload["error"] = {
    fingerprint: "fp_test_" + Date.now(),
    environmentId: "env_test_dev",
    environmentSlug: "dev",
    environmentName: "Development",
    taskIdentifier: "process-payment",
    errorType: "TypeError",
    errorMessage: "Cannot read property 'id' of undefined",
    sampleStackTrace: `TypeError: Cannot read property 'id' of undefined
    at processPayment (src/tasks/payment.ts:42:15)
    at Object.run (src/tasks/payment.ts:15:20)
    at TaskExecutor.execute (node_modules/@trigger.dev/core/dist/index.js:234:18)`,
    firstSeen: Date.now().toString(),
    lastSeen: Date.now().toString(),
    occurrenceCount: 42,
    ...errorOverrides,
  };

  return {
    channelId: testChannelId,
    projectId: testProjectId,
    classification: "new_issue",
    ...payloadOverrides,
    error: defaultError,
  };
}

// Skip tests if Slack credentials not configured
const hasSlackCredentials =
  !!process.env.TEST_SLACK_CHANNEL_ID && !!process.env.TEST_SLACK_BOT_TOKEN;

describe.skipIf(!hasSlackCredentials)("Slack Error Alert Visual Tests", () => {
  beforeAll(async () => {
    const dbModule = await import("../app/db.server.js");
    prisma = dbModule.prisma;
    const secretModule = await import("../app/services/secrets/secretStore.server.js");
    getSecretStore = secretModule.getSecretStore;
    const alertModule = await import("../app/v3/services/alerts/deliverErrorGroupAlert.server.js");
    DeliverErrorGroupAlertService = alertModule.DeliverErrorGroupAlertService;

    const organization = await prisma.organization.create({
      data: {
        title: "Slack Test Org",
        slug: "slack-test-org-" + Date.now(),
      },
    });
    testOrganizationId = organization.id;

    // Create test project
    const project = await prisma.project.create({
      data: {
        name: "Slack Test Project",
        slug: "slack-test-project-" + Date.now(),
        externalRef: "proj_slack_test_" + Date.now(),
        organizationId: organization.id,
      },
    });
    testProjectId = project.id;

    const secretStore = getSecretStore("DATABASE");
    testSecretKey = `slack-test-token-${Date.now()}`;

    await secretStore.setSecret(testSecretKey, {
      botAccessToken: process.env.TEST_SLACK_BOT_TOKEN!,
    });

    const secretReference = await prisma.secretReference.create({
      data: {
        key: testSecretKey,
        provider: "DATABASE",
      },
    });
    testSecretReferenceId = secretReference.id;

    // Create Slack organization integration
    const integration = await prisma.organizationIntegration.create({
      data: {
        friendlyId: "integration_test_" + Date.now(),
        organizationId: organization.id,
        service: "SLACK",
        integrationData: {},
        tokenReferenceId: secretReference.id,
      },
    });

    // Create alert channel
    const channel = await prisma.projectAlertChannel.create({
      data: {
        friendlyId: "channel_test_" + Date.now(),
        name: "Test Slack Channel",
        type: "SLACK",
        enabled: true,
        projectId: project.id,
        integrationId: integration.id,
        properties: {
          channelId: process.env.TEST_SLACK_CHANNEL_ID!,
          channelName: "test-slack-alerts",
          integrationId: integration.id,
        },
      },
    });
    testChannelId = channel.id;
  });

  afterAll(async () => {
    if (testChannelId) {
      await prisma.projectAlertChannel.deleteMany({
        where: { id: testChannelId },
      });
    }
    if (testOrganizationId) {
      await prisma.organizationIntegration.deleteMany({
        where: { organizationId: testOrganizationId },
      });
    }
    if (testSecretReferenceId) {
      await prisma.secretReference.deleteMany({
        where: { id: testSecretReferenceId },
      });
    }
    if (testSecretKey) {
      const secretStore = getSecretStore("DATABASE");
      await secretStore.deleteSecret(testSecretKey);
    }
    if (testProjectId) {
      await prisma.project.deleteMany({
        where: { id: testProjectId },
      });
    }
    if (testOrganizationId) {
      await prisma.organization.deleteMany({
        where: { id: testOrganizationId },
      });
    }
  });

  test("new_issue classification", async () => {
    const payload = createMockErrorPayload({
      classification: "new_issue",
      error: {
        taskIdentifier: "process-order",
        errorMessage: "Failed to process order due to invalid payment method",
        errorType: "PaymentError",
      },
    });

    const service = new DeliverErrorGroupAlertService();
    await service.call(payload);

    // Message sent - check Slack channel visually
    expect(true).toBe(true);
  });

  test("regression classification", async () => {
    const payload = createMockErrorPayload({
      classification: "regression",
      error: {
        taskIdentifier: "send-email",
        errorMessage: "SMTP connection timeout after 30 seconds",
        errorType: "TimeoutError",
        occurrenceCount: 156,
      },
    });

    const service = new DeliverErrorGroupAlertService();
    await service.call(payload);

    expect(true).toBe(true);
  });

  test("unignored (resurfaced) classification", async () => {
    const payload = createMockErrorPayload({
      classification: "unignored",
      error: {
        taskIdentifier: "sync-database",
        errorMessage: "Connection pool exhausted",
        errorType: "DatabaseError",
        occurrenceCount: 99,
      },
    });

    const service = new DeliverErrorGroupAlertService();
    await service.call(payload);

    expect(true).toBe(true);
  });

  test("short error message", async () => {
    const payload = createMockErrorPayload({
      error: {
        errorMessage: "Not found",
        errorType: "NotFoundError",
        sampleStackTrace: "NotFoundError: Not found\n    at findUser (src/db.ts:10:5)",
      },
    });

    const service = new DeliverErrorGroupAlertService();
    await service.call(payload);

    expect(true).toBe(true);
  });

  test("long stack trace", async () => {
    const longStackTrace = `ReferenceError: processData is not defined
    at handler (src/tasks/data-processor.ts:125:15)
    at async TaskRunner.execute (node_modules/@trigger.dev/sdk/dist/runner.js:89:12)
    at async WorkerThread.processTask (node_modules/@trigger.dev/sdk/dist/worker.js:234:18)
    at async WorkerPool.run (src/lib/worker-pool.ts:56:10)
    at async TaskQueue.dequeue (src/lib/queue.ts:142:8)
    at async Orchestrator.processNextTask (src/orchestrator.ts:98:5)
    at async Orchestrator.start (src/orchestrator.ts:45:7)
    at async main (src/index.ts:12:3)
    at Object.<anonymous> (src/index.ts:20:1)
    at Module._compile (node:internal/modules/cjs/loader:1376:14)
    at Module._extensions..js (node:internal/modules/cjs/loader:1435:10)
    at Module.load (node:internal/modules/cjs/loader:1207:32)
    at Module._load (node:internal/modules/cjs/loader:1023:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:135:12)
    at node:internal/main/run_main_module:28:49`;

    const payload = createMockErrorPayload({
      error: {
        errorType: "ReferenceError",
        errorMessage: "processData is not defined",
        sampleStackTrace: longStackTrace,
        taskIdentifier: "data-processor",
      },
    });

    const service = new DeliverErrorGroupAlertService();
    await service.call(payload);

    expect(true).toBe(true);
  });

  test("very long error message (triggers truncation)", async () => {
    // Create a message that's over 3000 characters
    const longMessage = "x".repeat(3500);
    const longStackTrace = `Error: ${longMessage}
    at verylongfunctionname (src/tasks/long-task.ts:1:1)
    ${"    at stackframe (file.ts:1:1)\n".repeat(100)}`;

    const payload = createMockErrorPayload({
      error: {
        errorMessage: longMessage,
        sampleStackTrace: longStackTrace,
        taskIdentifier: "long-error-task",
        errorType: "Error",
      },
    });

    const service = new DeliverErrorGroupAlertService();
    await service.call(payload);

    // Should see truncation message in Slack
    expect(true).toBe(true);
  });

  test("special characters in error", async () => {
    const payload = createMockErrorPayload({
      error: {
        errorMessage: "Unexpected token `<` in JSON at position 0",
        errorType: "SyntaxError",
        sampleStackTrace: `SyntaxError: Unexpected token \`<\` in JSON at position 0
    at JSON.parse (<anonymous>)
    at parseResponse (src/api/client.ts:42:15)
    at fetch("https://api.example.com/data?query=test&limit=10")`,
        taskIdentifier: "api-fetch-task",
      },
    });

    const service = new DeliverErrorGroupAlertService();
    await service.call(payload);

    expect(true).toBe(true);
  });

  test("unicode and emoji in error", async () => {
    const payload = createMockErrorPayload({
      error: {
        errorMessage: "Failed to process emoji 🔥 in message: Hello 世界",
        errorType: "EncodingError",
        sampleStackTrace: `EncodingError: Failed to process emoji 🔥 in message: Hello 世界
    at encodeMessage (src/utils/encoding.ts:15:10)
    at sendMessage (src/tasks/messaging.ts:42:8)`,
        taskIdentifier: "messaging-task",
      },
    });

    const service = new DeliverErrorGroupAlertService();
    await service.call(payload);

    expect(true).toBe(true);
  });

  test("different error types - TypeError", async () => {
    const payload = createMockErrorPayload({
      error: {
        errorType: "TypeError",
        errorMessage: "Cannot call method 'map' on undefined",
        sampleStackTrace: `TypeError: Cannot call method 'map' on undefined
    at transformData (src/transformers/data.ts:18:25)`,
      },
    });

    const service = new DeliverErrorGroupAlertService();
    await service.call(payload);

    expect(true).toBe(true);
  });

  test("different error types - ReferenceError", async () => {
    const payload = createMockErrorPayload({
      error: {
        errorType: "ReferenceError",
        errorMessage: "userConfig is not defined",
        sampleStackTrace: `ReferenceError: userConfig is not defined
    at initializeApp (src/app.ts:32:10)`,
      },
    });

    const service = new DeliverErrorGroupAlertService();
    await service.call(payload);

    expect(true).toBe(true);
  });

  test("different error types - Custom Error", async () => {
    const payload = createMockErrorPayload({
      error: {
        errorType: "InvalidConfigurationError",
        errorMessage: "API key is missing or invalid",
        sampleStackTrace: `InvalidConfigurationError: API key is missing or invalid
    at validateConfig (src/config/validator.ts:45:11)
    at loadConfig (src/config/loader.ts:23:5)`,
        taskIdentifier: "config-loader",
      },
    });

    const service = new DeliverErrorGroupAlertService();
    await service.call(payload);

    expect(true).toBe(true);
  });

  test("error with no stack trace", async () => {
    const payload = createMockErrorPayload({
      error: {
        errorMessage: "An unknown error occurred",
        errorType: "Error",
        sampleStackTrace: "",
      },
    });

    const service = new DeliverErrorGroupAlertService();
    await service.call(payload);

    expect(true).toBe(true);
  });
});
