import { describe, test, expect } from "bun:test";
import { Hono } from "hono";
import { apiResponse } from "../../src/modules/api-utils/apiResponse";
import { ZodError } from "zod";
import GenericError from "../../src/Errors/GenericError";

/**
 * Tests the error-handling middleware registered in server.ts.
 * We replicate the onError logic on a fresh Hono instance to test
 * in isolation without importing all routes.
 */
function createAppWithErrorHandling() {
  const app = new Hono();

  app.onError((err, ctx) => {
    const errClassName = err.constructor.name;

    if (
      errClassName.toLowerCase().includes("prisma") ||
      err.message.toLowerCase().includes("prisma") ||
      err.name.toLowerCase().includes("prisma")
    ) {
      return ctx.json(apiResponse.internalError(), 500);
    }

    if (err instanceof ZodError || err.name === "ZodError") {
      const zodResp = apiResponse.zodError(err as ZodError);
      return ctx.json(zodResp, zodResp.status as any);
    }

    const response = apiResponse.error(err);
    return ctx.json(response, response.status);
  });

  return app;
}

describe("Server error handling", () => {
  test("Prisma errors return 500 InternalServerError", async () => {
    const app = createAppWithErrorHandling();
    app.get("/test", () => {
      const err = new Error("prisma query failed");
      throw err;
    });
    const res = await app.request("/test");
    expect(res.status).toBe(500);
    const body: any = await res.json();
    expect(body.error).toBe("InternalServerError");
    expect(body.successful).toBe(false);
  });

  test("Prisma errors detected by class name", async () => {
    const app = createAppWithErrorHandling();
    app.get("/test", () => {
      class PrismaClientError extends Error {
        constructor() {
          super("something");
          this.name = "PrismaClientError";
        }
      }
      throw new PrismaClientError();
    });
    const res = await app.request("/test");
    expect(res.status).toBe(500);
  });

  test("ZodError returns 400 with error array", async () => {
    const app = createAppWithErrorHandling();
    app.get("/test", (c) => {
      // In Zod v4, we need to use z.parse to generate a proper ZodError
      const { z } = require("zod");
      const schema = z.object({ name: z.string() });
      schema.parse({}); // throws ZodError
      return c.text("unreachable");
    });
    const res = await app.request("/test");
    expect(res.status).toBe(400);
    const body: any = await res.json();
    expect(body.message).toBe("TypeError");
    expect(body.successful).toBe(false);
    expect(Array.isArray(body.error)).toBe(true);
  });

  test("GenericError returns custom status", async () => {
    const app = createAppWithErrorHandling();
    app.get("/test", () => {
      throw new GenericError({
        name: "NotFound",
        message: "Resource not found",
        status: 404,
      });
    });
    const res = await app.request("/test");
    expect(res.status).toBe(404);
    const body: any = await res.json();
    expect(body.error).toBe("NotFound");
    expect(body.message).toBe("Resource not found");
    expect(body.successful).toBe(false);
  });

  test("plain Error defaults to 400", async () => {
    const app = createAppWithErrorHandling();
    app.get("/test", () => {
      throw new Error("Unexpected error");
    });
    const res = await app.request("/test");
    expect(res.status).toBe(400);
    const body: any = await res.json();
    expect(body.successful).toBe(false);
  });
});
