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

describe("apiResponse", () => {
  // -------------------------------------------------------------------
  // successful
  // -------------------------------------------------------------------
  describe("successful()", () => {
    test("returns status 200 and successful: true", () => {
      const res = apiResponse.successful("OK");
      expect(res.status).toBe(200);
      expect(res.successful).toBe(true);
      expect(res.message).toBe("OK");
    });

    test("includes data when provided", () => {
      const data = { id: 1, name: "Test" };
      const res = apiResponse.successful("OK", data);
      expect(res.data).toEqual(data);
    });

    test("data is undefined when not provided", () => {
      const res = apiResponse.successful("OK");
      expect(res.data).toBeUndefined();
    });

    test("includes meta.timestamp", () => {
      const before = Date.now();
      const res = apiResponse.successful("OK");
      const after = Date.now();
      expect(res.meta.timestamp).toBeGreaterThanOrEqual(before);
      expect(res.meta.timestamp).toBeLessThanOrEqual(after);
    });

    test("accepts optional meta parameter (overridden by timestamp)", () => {
      const res = apiResponse.successful("OK", null, { custom: true } as any);
      // The implementation replaces meta entirely with { timestamp }
      expect(res.meta.timestamp).toBeDefined();
    });
  });

  // -------------------------------------------------------------------
  // created
  // -------------------------------------------------------------------
  describe("created()", () => {
    test("returns status 201 and successful: true", () => {
      const res = apiResponse.created("Created");
      expect(res.status).toBe(201);
      expect(res.successful).toBe(true);
      expect(res.message).toBe("Created");
    });

    test("includes data when provided", () => {
      const res = apiResponse.created("Created", { id: "abc" });
      expect(res.data).toEqual({ id: "abc" });
    });

    test("data is undefined when not provided", () => {
      const res = apiResponse.created("Created");
      expect(res.data).toBeUndefined();
    });

    test("includes meta.timestamp", () => {
      const res = apiResponse.created("Created");
      expect(res.meta.timestamp).toBeDefined();
      expect(typeof res.meta.timestamp).toBe("number");
    });
  });

  // -------------------------------------------------------------------
  // error
  // -------------------------------------------------------------------
  describe("error()", () => {
    test("reads status from error object", () => {
      const err = new GenericError({
        name: "Oops",
        message: "bad",
        status: 422,
      });
      const res = apiResponse.error(err);
      expect(res.status).toBe(422);
      expect(res.message).toBe("bad");
      expect(res.error).toBe("Oops");
      expect(res.successful).toBe(false);
    });

    test("defaults status to 400 when error has no status", () => {
      const err = new Error("plain error");
      const res = apiResponse.error(err);
      expect(res.status).toBe(400);
    });

    test("includes meta.timestamp", () => {
      const err = new Error("x");
      const res = apiResponse.error(err);
      expect(res.meta.timestamp).toBeDefined();
    });
  });

  // -------------------------------------------------------------------
  // internalError
  // -------------------------------------------------------------------
  describe("internalError()", () => {
    test("returns status 500", () => {
      const res = apiResponse.internalError();
      expect(res.status).toBe(500);
      expect(res.successful).toBe(false);
      expect(res.error).toBe("InternalServerError");
      expect(res.message).toContain("Internal Server Error");
    });

    test("includes meta.timestamp", () => {
      const res = apiResponse.internalError();
      expect(res.meta.timestamp).toBeDefined();
    });
  });

  // -------------------------------------------------------------------
  // zodError
  // -------------------------------------------------------------------
  describe("zodError()", () => {
    test("returns status 400 with parsed error data", () => {
      const schema = z.object({ name: z.string() });
      let zodErr: z.ZodError;
      try {
        schema.parse({ name: 123 });
        throw new Error("should not reach");
      } catch (e) {
        zodErr = e as z.ZodError;
      }
      const res = apiResponse.zodError(zodErr!);
      expect(res.status).toBe(400);
      expect(res.successful).toBe(false);
      expect(res.message).toBe("TypeError");
      expect(Array.isArray(res.error)).toBe(true);
      expect(res.error[0].code).toBe("invalid_type");
    });

    test("includes meta.timestamp", () => {
      const schema = z.object({ x: z.string() });
      let zodErr: z.ZodError;
      try {
        schema.parse({});
        throw new Error("should not reach");
      } catch (e) {
        zodErr = e as z.ZodError;
      }
      const res = apiResponse.zodError(zodErr!);
      expect(res.meta.timestamp).toBeDefined();
    });
  });
});
