import { Hono } from "hono";
import { describe, it, expect, beforeEach } from "vitest";
import { z } from "zod";
import { validateBody, validateQuery, parseBody } from "./validation.js";

/* ── validateBody middleware tests ──────────────────────────────── */

describe("validateBody middleware", () => {
  const schema = z.object({
    name: z.string().min(1, "name is required").max(50),
    count: z.number().int().min(0).optional(),
  });

  let app: Hono<{ Variables: { validatedBody: unknown } }>;

  beforeEach(() => {
    app = new Hono<{ Variables: { validatedBody: unknown } }>();
    app.post("/test", validateBody(schema), (c) => {
      const data = c.get("validatedBody");
      return c.json({ ok: true, data });
    });
  });

  function req(body?: unknown) {
    const init: RequestInit = { method: "POST", headers: {} };
    if (body !== undefined) {
      (init.headers as Record<string, string>)["content-type"] = "application/json";
      init.body = JSON.stringify(body);
    }
    return app.request("/test", init);
  }

  it("passes valid body through to handler", async () => {
    const res = await req({ name: "hello", count: 5 });
    expect(res.status).toBe(200);
    const json = await res.json();
    expect(json.ok).toBe(true);
    expect(json.data).toEqual({ name: "hello", count: 5 });
  });

  it("returns 400 for missing required field", async () => {
    const res = await req({ count: 5 });
    expect(res.status).toBe(400);
    const json = await res.json();
    expect(json.error).toBe("Validation failed");
    expect(json.details).toBeInstanceOf(Array);
    expect(json.details.length).toBeGreaterThan(0);
    expect(json.details[0].path).toContain("name");
  });

  it("returns 400 for wrong type", async () => {
    const res = await req({ name: "hello", count: "not-a-number" });
    expect(res.status).toBe(400);
    const json = await res.json();
    expect(json.error).toBe("Validation failed");
    expect(json.details.some((d: { path: string }) => d.path.includes("count"))).toBe(true);
  });

  it("returns 400 for string too long", async () => {
    const res = await req({ name: "a".repeat(51) });
    expect(res.status).toBe(400);
    const json = await res.json();
    expect(json.error).toBe("Validation failed");
  });

  it("returns 400 for empty string when min(1)", async () => {
    const res = await req({ name: "" });
    expect(res.status).toBe(400);
    const json = await res.json();
    expect(json.error).toBe("Validation failed");
  });

  it("returns 400 for invalid JSON body", async () => {
    const res = await app.request("/test", {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: "not-json{{{",
    });
    expect(res.status).toBe(400);
    const json = await res.json();
    expect(json.error).toBe("Validation failed");
    expect(json.details[0].message).toBe("Invalid JSON body");
  });

  it("returns 400 for no body at all", async () => {
    const res = await app.request("/test", { method: "POST" });
    expect(res.status).toBe(400);
    const json = await res.json();
    expect(json.error).toBe("Validation failed");
  });
});

/* ── validateQuery middleware tests ─────────────────────────────── */

describe("validateQuery middleware", () => {
  const schema = z.object({
    page: z.string().optional(),
    limit: z.string().optional(),
    required: z.string().min(1, "required param needed"),
  });

  let app: Hono<{ Variables: { validatedQuery: unknown } }>;

  beforeEach(() => {
    app = new Hono<{ Variables: { validatedQuery: unknown } }>();
    app.get("/test", validateQuery(schema), (c) => {
      const data = c.get("validatedQuery");
      return c.json({ ok: true, data });
    });
  });

  it("passes valid query params", async () => {
    const res = await app.request("/test?required=yes&page=1&limit=10");
    expect(res.status).toBe(200);
    const json = await res.json();
    expect(json.data.required).toBe("yes");
  });

  it("returns 400 for missing required param", async () => {
    const res = await app.request("/test?page=1");
    expect(res.status).toBe(400);
    const json = await res.json();
    expect(json.error).toBe("Validation failed");
  });
});

/* ── parseBody utility tests ───────────────────────────────────── */

describe("parseBody utility", () => {
  const schema = z.object({
    name: z.string().min(1),
    active: z.boolean().optional(),
  });

  let app: Hono;

  beforeEach(() => {
    app = new Hono();
    app.post("/test", async (c) => {
      const parsed = await parseBody(c, schema);
      if (!parsed.success) return parsed.response;
      return c.json({ ok: true, data: parsed.data });
    });
  });

  function req(body?: unknown) {
    const init: RequestInit = { method: "POST", headers: {} };
    if (body !== undefined) {
      (init.headers as Record<string, string>)["content-type"] = "application/json";
      init.body = JSON.stringify(body);
    }
    return app.request("/test", init);
  }

  it("returns parsed data on valid input", async () => {
    const res = await req({ name: "test", active: true });
    expect(res.status).toBe(200);
    const json = await res.json();
    expect(json.data).toEqual({ name: "test", active: true });
  });

  it("returns 400 with details on invalid input", async () => {
    const res = await req({ active: true });
    expect(res.status).toBe(400);
    const json = await res.json();
    expect(json.error).toBe("Validation failed");
    expect(json.details.length).toBeGreaterThan(0);
  });

  it("returns 400 for broken JSON", async () => {
    const res = await app.request("/test", {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: "{broken",
    });
    expect(res.status).toBe(400);
    const json = await res.json();
    expect(json.details[0].message).toBe("Invalid JSON body");
  });
});
