import { describe, expect, it, vi } from "vitest";
import { postgresTest } from "@internal/testcontainers";
import { Prisma, PrismaClient } from "@trigger.dev/database";
import {
  captureInfrastructureErrors,
  clientSafeErrorMessage,
  infraErrorAlreadyLogged,
  logTransactionInfrastructureError,
} from "~/utils/prismaErrors";

vi.setConfig({ testTimeout: 60_000 });

function capturingLogger() {
  const captured: Array<{ message: string; fields?: Record<string, unknown> }> = [];
  return {
    captured,
    error: (message: string, fields?: Record<string, unknown>) => {
      captured.push({ message, fields });
    },
  };
}

describe("captureInfrastructureErrors", () => {
  postgresTest(
    "P2025 (not found) passes through with code intact and unlogged",
    async ({ prisma }) => {
      const log = capturingLogger();
      const client = captureInfrastructureErrors(prisma, log);

      const error = await client.secretStore
        .update({ where: { key: "does-not-exist" }, data: { version: "2" } })
        .then(() => undefined)
        .catch((e) => e);

      expect(error).toBeInstanceOf(Prisma.PrismaClientKnownRequestError);
      expect((error as Prisma.PrismaClientKnownRequestError).code).toBe("P2025");
      expect(log.captured).toHaveLength(0);
    }
  );

  postgresTest(
    "P2002 (unique violation) passes through with code intact and unlogged",
    async ({ prisma }) => {
      const log = capturingLogger();
      const client = captureInfrastructureErrors(prisma, log);

      await client.secretStore.create({ data: { key: "dup-key", value: { a: 1 } } });

      const error = await client.secretStore
        .create({ data: { key: "dup-key", value: { a: 2 } } })
        .then(() => undefined)
        .catch((e) => e);

      expect(error).toBeInstanceOf(Prisma.PrismaClientKnownRequestError);
      expect((error as Prisma.PrismaClientKnownRequestError).code).toBe("P2002");
      expect(log.captured).toHaveLength(0);
    }
  );

  postgresTest(
    "errors raised inside an interactive $transaction keep their code",
    async ({ prisma }) => {
      const log = capturingLogger();
      const client = captureInfrastructureErrors(prisma, log);

      // Proves $allOperations fires per-statement inside a transaction — the
      // basis for transaction retry logic (which branches on error.code) staying
      // intact.
      const error = await client
        .$transaction(async (tx) => {
          await tx.secretStore.update({ where: { key: "missing-in-tx" }, data: { version: "2" } });
        })
        .then(() => undefined)
        .catch((e) => e);

      expect(error).toBeInstanceOf(Prisma.PrismaClientKnownRequestError);
      expect((error as Prisma.PrismaClientKnownRequestError).code).toBe("P2025");
      expect(log.captured).toHaveLength(0);
    }
  );

  postgresTest("raw queries (model undefined) are wrapped without crashing", async ({ prisma }) => {
    const log = capturingLogger();
    const client = captureInfrastructureErrors(prisma, log);

    const rows = await client.$queryRaw<Array<{ one: number }>>(Prisma.sql`SELECT 1 as one`);
    expect(rows[0].one).toBe(1);

    // A failing raw query (non-infra) must still rethrow rather than throw on
    // the undefined `model`.
    const error = await client
      .$queryRaw(Prisma.sql`SELECT 1 / 0`)
      .then(() => undefined)
      .catch((e) => e);

    expect(error).toBeInstanceOf(Error);
    expect(log.captured).toHaveLength(0);
  });

  postgresTest("a genuine connectivity failure is logged with model/operation/code", async () => {
    const log = capturingLogger();
    // Point at a closed port to provoke a real P1001 / initialization error —
    // no mocking.
    const unreachable = new PrismaClient({
      datasources: {
        db: { url: "postgresql://postgres:postgres@127.0.0.1:1/postgres?connect_timeout=2" },
      },
    });
    const client = captureInfrastructureErrors(unreachable, log);

    try {
      const error = await client.secretStore
        .findFirst({ where: { key: "anything" } })
        .then(() => undefined)
        .catch((e) => e);

      expect(error).toBeInstanceOf(Error);
      expect(log.captured).toHaveLength(1);
      expect(log.captured[0].message).toBe("prisma infrastructure error");
      expect(log.captured[0].fields?.operation).toBe("findFirst");
      expect(log.captured[0].fields?.model).toBe("SecretStore");

      // Dedupe: the extension tagged it, so a $transaction-boundary logger
      // seeing the same error must NOT log it a second time.
      expect(infraErrorAlreadyLogged(error)).toBe(true);
      const boundaryLog = capturingLogger();
      expect(logTransactionInfrastructureError(error, boundaryLog)).toBe(false);
      expect(boundaryLog.captured).toHaveLength(0);
    } finally {
      await unreachable.$disconnect();
    }
  });
});

describe("logTransactionInfrastructureError", () => {
  // Covers the transaction boundary, which $allOperations cannot reach.
  it("logs an uncoded infra error (PrismaClientInitializationError)", () => {
    const log = capturingLogger();
    const error = new Prisma.PrismaClientInitializationError(
      "Can't reach database server",
      "6.14.0",
      "P1001"
    );

    expect(logTransactionInfrastructureError(error, log)).toBe(true);
    expect(log.captured).toHaveLength(1);
    expect(log.captured[0].message).toBe("prisma.$transaction infrastructure error");
    expect(log.captured[0].fields?.name).toBe("PrismaClientInitializationError");
  });

  it("skips a coded infra error (transac's callback already logs those)", () => {
    const log = capturingLogger();
    const error = new Prisma.PrismaClientKnownRequestError("Can't reach database server", {
      code: "P1001",
      clientVersion: "6.14.0",
    });

    expect(logTransactionInfrastructureError(error, log)).toBe(false);
    expect(log.captured).toHaveLength(0);
  });

  it("skips a non-infra coded error (P2002)", () => {
    const log = capturingLogger();
    const error = new Prisma.PrismaClientKnownRequestError("Unique constraint failed", {
      code: "P2002",
      clientVersion: "6.14.0",
    });

    expect(logTransactionInfrastructureError(error, log)).toBe(false);
    expect(log.captured).toHaveLength(0);
  });

  it("skips a plain non-Prisma error", () => {
    const log = capturingLogger();

    expect(logTransactionInfrastructureError(new Error("boom"), log)).toBe(false);
    expect(log.captured).toHaveLength(0);
  });
});

describe("clientSafeErrorMessage", () => {
  // Guards the API-route leak: an infra error's message carries the DB hostname.
  it("obfuscates a coded infra error (P1001) message", () => {
    const error = new Prisma.PrismaClientKnownRequestError(
      "Can't reach database server at `db-internal.example.com:5432`",
      { code: "P1001", clientVersion: "6.14.0" }
    );

    expect(clientSafeErrorMessage(error)).toBe("Internal Server Error");
  });

  it("obfuscates an uncoded infra error (PrismaClientInitializationError) message", () => {
    const error = new Prisma.PrismaClientInitializationError(
      "Can't reach database server at `db-internal.example.com:5432`",
      "6.14.0",
      "P1001"
    );

    expect(clientSafeErrorMessage(error)).toBe("Internal Server Error");
  });

  it("leaves non-infra Prisma error (P2002) messages unchanged", () => {
    const error = new Prisma.PrismaClientKnownRequestError("Unique constraint failed on email", {
      code: "P2002",
      clientVersion: "6.14.0",
    });

    expect(clientSafeErrorMessage(error)).toBe("Unique constraint failed on email");
  });

  it("leaves plain domain/validation error messages unchanged", () => {
    expect(clientSafeErrorMessage(new Error("Invalid delay value"))).toBe("Invalid delay value");
  });
});
