import { describe, expect, test } from "bun:test";
import type { FetchLike } from "./kanboard-client";
import { KanboardRpcError, createKanboardClient } from "./kanboard-client";

const BASE_URL = "http://127.0.0.1:31339/jsonrpc.php";
const TOKEN = "local-incident-token";

describe("createKanboardClient.call", () => {
  test("throws on malformed JSON", async () => {
    const client = createKanboardClient({
      baseUrl: BASE_URL,
      token: TOKEN,
      fetchImpl: async () => new Response("not-json", { status: 200 }),
    });

    await expect(
      client.call("getProjectByIdentifier", { identifier: "OVERDECKINCIDENTS" }),
    ).rejects.toBeInstanceOf(KanboardRpcError);
  });

  test("throws when response id is missing", async () => {
    const fetchImpl: FetchLike = async (input, init) => {
      const request = JSON.parse(String(init?.body));
      return new Response(JSON.stringify({ jsonrpc: "2.0", result: { id: "1", identifier: "OVER", name: "Incident Board" } }), {
        status: 200,
      });
    };
    const client = createKanboardClient({ baseUrl: BASE_URL, token: TOKEN, fetchImpl });

    await expect(
      client.call("getProjectByIdentifier", { identifier: "OVERDECKINCIDENTS" }),
    ).rejects.toBeInstanceOf(KanboardRpcError);
  });

  test("throws on response id mismatch", async () => {
    const fetchImpl: FetchLike = async (input, init) => {
      const request = JSON.parse(String(init?.body));
      return new Response(
        JSON.stringify({
          jsonrpc: "2.0",
          id: `${String(request.id)}-mismatch`,
          result: { id: "1", identifier: "OVER", name: "Incident Board" },
        }),
        { status: 200 },
      );
    };
    const client = createKanboardClient({ baseUrl: BASE_URL, token: TOKEN, fetchImpl });

    await expect(
      client.call("getProjectByIdentifier", { identifier: "OVERDECKINCIDENTS" }),
    ).rejects.toBeInstanceOf(KanboardRpcError);
  });

  test("includes issue paths when response schema is invalid", async () => {
    const fetchImpl: FetchLike = async (_input, init) => {
      const request = JSON.parse(String(init?.body));
      return new Response(
        JSON.stringify({
          jsonrpc: "2.0",
          id: request.id,
          result: {
            id: 1,
            identifier: "OVERDECKINCIDENTS",
            name: 42,
          },
        }),
        { status: 200 },
      );
    };
    const client = createKanboardClient({ baseUrl: BASE_URL, token: TOKEN, fetchImpl });

    await expect(
      client.call("getProjectByIdentifier", { identifier: "OVERDECKINCIDENTS" }),
    ).rejects.toMatchObject({
      code: "RESPONSE_SCHEMA_INVALID",
      details: ["name"],
    });
  });

  test("throws on json-rpc false failure result", async () => {
    const fetchImpl: FetchLike = async (input, init) => {
      const request = JSON.parse(String(init?.body));
      return new Response(JSON.stringify({ jsonrpc: "2.0", id: request.id, result: false }), { status: 200 });
    };
    const client = createKanboardClient({ baseUrl: BASE_URL, token: TOKEN, fetchImpl });

    await expect(
      client.call("getProjectByIdentifier", { identifier: "OVERDECKINCIDENTS" }),
    ).rejects.toBeInstanceOf(KanboardRpcError);
  });

  test("throws on json-rpc error envelope", async () => {
    const fetchImpl: FetchLike = async (input, init) => {
      const request = JSON.parse(String(init?.body));
      return new Response(
        JSON.stringify({ jsonrpc: "2.0", id: request.id, error: { code: -32000, message: "call failed" } }),
        { status: 200 },
      );
    };
    const client = createKanboardClient({ baseUrl: BASE_URL, token: TOKEN, fetchImpl });

    await expect(
      client.call("getProjectByIdentifier", { identifier: "OVERDECKINCIDENTS" }),
    ).rejects.toBeInstanceOf(KanboardRpcError);
  });
});

describe("createKanboardClient.batch", () => {
  test("fails when batch response is partial", async () => {
    const fetchImpl: FetchLike = async (input, init) => {
      const request = JSON.parse(String(init?.body));
      return new Response(
        JSON.stringify([
          {
            jsonrpc: "2.0",
            id: request[0].id,
            result: { id: "1", identifier: "OVERDECKINCIDENTS", name: "Overdeck Incidents" },
          },
        ]),
        { status: 200 },
      );
    };
    const client = createKanboardClient({ baseUrl: BASE_URL, token: TOKEN, fetchImpl });

    await expect(
      client.batch([
        { method: "getProjectByIdentifier", params: { identifier: "OVERDECKINCIDENTS" } },
        { method: "getProjectByIdentifier", params: { identifier: "OTHER" } },
      ]),
    ).rejects.toBeInstanceOf(KanboardRpcError);
  });

  test("fails when a batch element has false result", async () => {
    const fetchImpl: FetchLike = async (input, init) => {
      const request = JSON.parse(String(init?.body));
      return new Response(
        JSON.stringify([
          {
            jsonrpc: "2.0",
            id: request[0].id,
            result: { id: "1", identifier: "OVERDECKINCIDENTS", name: "Overdeck Incidents" },
          },
          {
            jsonrpc: "2.0",
            id: request[1].id,
            result: false,
          },
        ]),
        { status: 200 },
      );
    };
    const client = createKanboardClient({ baseUrl: BASE_URL, token: TOKEN, fetchImpl });

    await expect(
      client.batch([
        { method: "getProjectByIdentifier", params: { identifier: "OVERDECKINCIDENTS" } },
        { method: "getProjectByIdentifier", params: { identifier: "OTHER" } },
      ]),
    ).rejects.toBeInstanceOf(KanboardRpcError);
  });
});
