import { Hono } from "hono";
import { http, HttpResponse } from "msw";
import { setupServer } from "msw/node";
import { afterAll, afterEach, beforeAll, describe, expect, test } from "vitest";
import { z } from "zod";
import { UnkeyContext, unkey } from "./index";

const key = "test_key";
const apiId = "api_test";

const server = setupServer(
  // @ts-expect-error
  http.post("https://api.unkey.dev/v1/keys.verifyKey", async ({ request }) => {
    const req = z
      .object({
        apiId: z.string(),
        key: z.string(),
      })
      .parse(await request.json());

    if (req.apiId !== apiId) {
      return HttpResponse.json({
        valid: false,
        code: "FORBDIDDEN",
      });
    }
    if (req.key !== key) {
      return HttpResponse.json({
        valid: false,
        code: "NOT_FOUND",
      });
    }

    return HttpResponse.json({
      valid: true,
      environment: "test",
    });
  }),
);

beforeAll(() => {
  server.listen();
});
afterEach(() => {
  server.resetHandlers();
});
afterAll(() => {
  server.close();
});

describe("No custom Config", () => {
  describe("happy path", () => {
    const app = new Hono<{ Variables: { unkey: UnkeyContext } }>();

    app.use(
      "*",
      unkey({
        apiId,
      }),
    );

    app.get("/", (c) => c.json(c.get("unkey")));

    test("Should be hello message", async () => {
      const res = await app.request("http://localhost/", {
        headers: {
          Authorization: `Bearer ${key}`,
        },
      });
      expect(res.status).toBe(200);
      expect(await res.json()).toMatchObject({ valid: true });
    });
  });

  describe("No Authorization header", () => {
    const app = new Hono<{ Variables: { unkey: UnkeyContext } }>();

    app.use(
      "*",
      unkey({
        apiId,
      }),
    );

    app.get("/", (c) => c.json(c.get("unkey")));

    test("should be unauthorized", async () => {
      const res = await app.request("http://localhost/");
      expect(res.status).toBe(401);
      expect(await res.json()).toMatchObject({
        error: "unauthorized",
      });
    });
  });
});

describe("with key environment", () => {
  const app = new Hono<{ Variables: { unkey: UnkeyContext } }>();

  app.use(
    "/*",
    unkey({
      apiId,
    }),
  );

  let returnedEnvironment: string | undefined = undefined;

  app.get("/", (c) => {
    returnedEnvironment = c.get("unkey").environment;
    return c.text("hello");
  });

  test("environment should be returned in context", async () => {
    const res = await app.request("http://localhost/", {
      headers: {
        Authorization: `Bearer ${key}`,
      },
    });
    expect(res.status).toBe(200);
    expect(returnedEnvironment).toBe("test");
  });
});

describe("With custom key getter", () => {
  describe("No Authorization header", () => {
    const app = new Hono<{ Variables: { unkey: UnkeyContext } }>();

    app.use(
      "/*",
      unkey({
        apiId,
        getKey: (c) => {
          return c.text("oh well");
        },
      }),
    );

    app.get("/", (c) => c.json(c.get("unkey")));

    test("should be called with response", async () => {
      const res = await app.request("http://localhost/");
      expect(res.status).toBe(200);
      expect(await res.text()).toEqual("oh well");
    });
  });
});

describe("With custom invald handler", () => {
  describe("No Authorization header", () => {
    const app = new Hono<{ Variables: { unkey: UnkeyContext } }>();

    let calledWith: UnkeyContext | null = null;
    app.use(
      "/*",
      unkey({
        apiId,
        handleInvalidKey: (c, result) => {
          calledWith = result;
          return c.text("oh well");
        },
      }),
    );

    app.get("/", (c) => c.json(c.get("unkey")));

    test("should be called with response", async () => {
      const res = await app.request("http://localhost/", {
        headers: {
          Authorization: "Bearer notakey",
        },
      });
      expect(res.status).toBe(200);
      expect(await res.text()).toEqual("oh well");
      expect(calledWith).toMatchObject({
        valid: false,
        code: "NOT_FOUND",
      });
    });
  });
});
