import { describe, expect, it } from "vitest";
import { z } from "zod";

import {
  ApiValidationError,
  isApiValidationError,
  parseBody,
} from "./http-validation.ts";

import type { Request } from "express";

function makeReq(body: unknown): Request {
  return { body } as Request;
}

describe("parseBody", () => {
  const Schema = z.object({
    batch_id: z.string().min(1, "缺少 batch_id 参数"),
    quantity: z.number().int().positive().optional(),
    items: z.array(z.object({ id: z.string() })).optional(),
  });

  it("returns typed data on success", () => {
    const out = parseBody(makeReq({ batch_id: "B-001", quantity: 5 }), Schema);
    expect(out).toEqual({ batch_id: "B-001", quantity: 5 });
    // type-level: `out.batch_id` is `string`
    const _check: string = out.batch_id;
    void _check;
  });

  it("strips unknown fields by default (zod object)", () => {
    const out = parseBody(
      makeReq({ batch_id: "B-001", extra: "ignored" }),
      Schema,
    );
    expect(out).toEqual({ batch_id: "B-001" });
  });

  it("throws ApiValidationError with 400 status on missing required field", () => {
    let caught: unknown;
    try {
      parseBody(makeReq({}), Schema);
    } catch (err) {
      caught = err;
    }
    expect(isApiValidationError(caught)).toBe(true);
    if (!isApiValidationError(caught)) return;
    expect(caught.statusCode).toBe(400);
    expect(caught.message).toContain("batch_id");
    expect(caught.issues).toHaveLength(1);
    expect(caught.issues[0]?.path).toEqual(["batch_id"]);
  });

  it("preserves nested issue paths", () => {
    let caught: unknown;
    try {
      parseBody(
        makeReq({ batch_id: "B-001", items: [{ id: "a" }, { id: 42 }] }),
        Schema,
      );
    } catch (err) {
      caught = err;
    }
    expect(isApiValidationError(caught)).toBe(true);
    if (!isApiValidationError(caught)) return;
    expect(caught.issues[0]?.path).toEqual(["items", 1, "id"]);
  });

  it("aggregates multiple issues into one error", () => {
    let caught: unknown;
    try {
      parseBody(makeReq({ batch_id: "", quantity: -1 }), Schema);
    } catch (err) {
      caught = err;
    }
    expect(isApiValidationError(caught)).toBe(true);
    if (!isApiValidationError(caught)) return;
    expect(caught.issues.length).toBeGreaterThanOrEqual(2);
    expect(caught.message).toMatch(/batch_id.*quantity|quantity.*batch_id/);
  });

  it("isApiValidationError returns false for unrelated errors", () => {
    expect(isApiValidationError(new Error("boom"))).toBe(false);
    expect(
      isApiValidationError(new ApiValidationError("x", [])),
    ).toBe(true);
  });
});
