import { describe, expect, it } from "vitest";
import { __testing, createMatrixQaClient, provisionMatrixQaRoom } from "./client.js";
import { buildDefaultMatrixQaTopologySpec } from "./topology.js";

function resolveRequestUrl(input: RequestInfo | URL) {
  if (typeof input === "string") {
    return input;
  }
  if (input instanceof URL) {
    return input.toString();
  }
  return input.url;
}

function parseJsonRequestBody(init?: RequestInit) {
  if (typeof init?.body !== "string") {
    return {};
  }
  return JSON.parse(init.body) as Record<string, unknown>;
}

describe("matrix driver client", () => {
  it("builds Matrix HTML mentions for QA driver messages", () => {
    expect(
      __testing.buildMatrixQaMessageContent({
        body: "@sut:matrix-qa.test reply with exactly: TOKEN",
        mentionUserIds: ["@sut:matrix-qa.test"],
      }),
    ).toEqual({
      body: "@sut:matrix-qa.test reply with exactly: TOKEN",
      msgtype: "m.text",
      format: "org.matrix.custom.html",
      formatted_body:
        '<a href="https://matrix.to/#/%40sut%3Amatrix-qa.test">@sut:matrix-qa.test</a> reply with exactly: TOKEN',
      "m.mentions": {
        user_ids: ["@sut:matrix-qa.test"],
      },
    });
  });

  it("omits Matrix HTML markup when the body has no visible mention token", () => {
    expect(
      __testing.buildMatrixQaMessageContent({
        body: "reply with exactly: TOKEN",
        mentionUserIds: ["@sut:matrix-qa.test"],
      }),
    ).toEqual({
      body: "reply with exactly: TOKEN",
      msgtype: "m.text",
      "m.mentions": {
        user_ids: ["@sut:matrix-qa.test"],
      },
    });
  });

  it("builds trimmed Matrix reaction relations for QA driver events", () => {
    expect(__testing.buildMatrixReactionRelation(" $msg-1 ", " 👍 ")).toEqual({
      "m.relates_to": {
        rel_type: "m.annotation",
        event_id: "$msg-1",
        key: "👍",
      },
    });
  });

  it("advances Matrix registration through token then dummy auth stages", () => {
    const firstStage = __testing.resolveNextRegistrationAuth({
      registrationToken: "reg-token",
      response: {
        session: "uiaa-session",
        flows: [{ stages: ["m.login.registration_token", "m.login.dummy"] }],
      },
    });

    expect(firstStage).toEqual({
      session: "uiaa-session",
      type: "m.login.registration_token",
      token: "reg-token",
    });

    expect(
      __testing.resolveNextRegistrationAuth({
        registrationToken: "reg-token",
        response: {
          session: "uiaa-session",
          completed: ["m.login.registration_token"],
          flows: [{ stages: ["m.login.registration_token", "m.login.dummy"] }],
        },
      }),
    ).toEqual({
      session: "uiaa-session",
      type: "m.login.dummy",
    });
  });

  it("rejects Matrix UIAA flows that require unsupported stages", () => {
    expect(() =>
      __testing.resolveNextRegistrationAuth({
        registrationToken: "reg-token",
        response: {
          session: "uiaa-session",
          flows: [{ stages: ["m.login.registration_token", "m.login.recaptcha", "m.login.dummy"] }],
        },
      }),
    ).toThrow("Matrix registration requires unsupported auth stages:");
  });

  it("issues Matrix room membership control requests for QA topology changes", async () => {
    const requests: Array<{ body: Record<string, unknown>; url: string }> = [];
    const fetchImpl: typeof fetch = async (input, init) => {
      requests.push({
        body: parseJsonRequestBody(init),
        url: resolveRequestUrl(input),
      });
      return new Response(JSON.stringify({}), {
        status: 200,
        headers: { "content-type": "application/json" },
      });
    };

    const client = createMatrixQaClient({
      accessToken: "token",
      baseUrl: "http://127.0.0.1:28008/",
      fetchImpl,
    });

    await client.inviteUserToRoom({
      roomId: "!room:matrix-qa.test",
      userId: "@observer:matrix-qa.test",
    });
    await client.kickUserFromRoom({
      reason: "topology reset",
      roomId: "!room:matrix-qa.test",
      userId: "@observer:matrix-qa.test",
    });
    await client.leaveRoom("!room:matrix-qa.test");

    expect(requests).toEqual([
      {
        url: "http://127.0.0.1:28008/_matrix/client/v3/rooms/!room%3Amatrix-qa.test/invite",
        body: {
          user_id: "@observer:matrix-qa.test",
        },
      },
      {
        url: "http://127.0.0.1:28008/_matrix/client/v3/rooms/!room%3Amatrix-qa.test/kick",
        body: {
          reason: "topology reset",
          user_id: "@observer:matrix-qa.test",
        },
      },
      {
        url: "http://127.0.0.1:28008/_matrix/client/v3/rooms/!room%3Amatrix-qa.test/leave",
        body: {},
      },
    ]);
  });

  it("sends Matrix reactions through the protocol send endpoint", async () => {
    const fetchImpl: typeof fetch = async (input, init) => {
      expect(resolveRequestUrl(input)).toContain(
        "/_matrix/client/v3/rooms/!room%3Amatrix-qa.test/send/m.reaction/",
      );
      expect(parseJsonRequestBody(init)).toEqual({
        "m.relates_to": {
          rel_type: "m.annotation",
          event_id: "$msg-1",
          key: "👍",
        },
      });
      return new Response(JSON.stringify({ event_id: "$reaction-1" }), {
        status: 200,
        headers: { "content-type": "application/json" },
      });
    };

    const client = createMatrixQaClient({
      accessToken: "token",
      baseUrl: "http://127.0.0.1:28008/",
      fetchImpl,
    });

    await expect(
      client.sendReaction({
        emoji: "👍",
        messageId: "$msg-1",
        roomId: "!room:matrix-qa.test",
      }),
    ).resolves.toBe("$reaction-1");
  });

  it("provisions a three-member room so Matrix QA runs in a group context", async () => {
    const createRoomBodies: Array<Record<string, unknown>> = [];
    const fetchImpl: typeof fetch = async (input, init) => {
      const url = resolveRequestUrl(input);
      const body = parseJsonRequestBody(init);
      if (url.endsWith("/_matrix/client/v3/register")) {
        const username = typeof body.username === "string" ? body.username : "";
        const auth = typeof body.auth === "object" && body.auth ? body.auth : undefined;
        if (!auth) {
          return new Response(
            JSON.stringify({
              session: `session-${username}`,
              flows: [{ stages: ["m.login.registration_token", "m.login.dummy"] }],
            }),
            { status: 401, headers: { "content-type": "application/json" } },
          );
        }
        if ((auth as { type?: string }).type === "m.login.registration_token") {
          return new Response(
            JSON.stringify({
              session: `session-${username}`,
              completed: ["m.login.registration_token"],
              flows: [{ stages: ["m.login.registration_token", "m.login.dummy"] }],
            }),
            { status: 401, headers: { "content-type": "application/json" } },
          );
        }
        return new Response(
          JSON.stringify({
            access_token: `token-${username}`,
            device_id: `device-${username}`,
            user_id: `@${username}:matrix-qa.test`,
          }),
          { status: 200, headers: { "content-type": "application/json" } },
        );
      }
      if (url.endsWith("/_matrix/client/v3/createRoom")) {
        createRoomBodies.push(body);
        return new Response(JSON.stringify({ room_id: "!room:matrix-qa.test" }), {
          status: 200,
          headers: { "content-type": "application/json" },
        });
      }
      if (url.includes("/_matrix/client/v3/join/")) {
        return new Response(JSON.stringify({ room_id: "!room:matrix-qa.test" }), {
          status: 200,
          headers: { "content-type": "application/json" },
        });
      }
      throw new Error(`unexpected fetch ${url}`);
    };

    const result = await provisionMatrixQaRoom({
      baseUrl: "http://127.0.0.1:28008/",
      driverLocalpart: "qa-driver",
      observerLocalpart: "qa-observer",
      registrationToken: "reg-token",
      roomName: "OpenClaw Matrix QA",
      sutLocalpart: "qa-sut",
      fetchImpl,
      topology: buildDefaultMatrixQaTopologySpec({
        defaultRoomName: "OpenClaw Matrix QA",
      }),
    });

    expect(result.roomId).toBe("!room:matrix-qa.test");
    expect(result.topology).toMatchObject({
      defaultRoomId: "!room:matrix-qa.test",
      defaultRoomKey: "main",
      rooms: [
        {
          key: "main",
          kind: "group",
          memberRoles: ["driver", "observer", "sut"],
          memberUserIds: [
            "@qa-driver:matrix-qa.test",
            "@qa-observer:matrix-qa.test",
            "@qa-sut:matrix-qa.test",
          ],
          requireMention: true,
          roomId: "!room:matrix-qa.test",
        },
      ],
    });
    expect(result.observer.userId).toBe("@qa-observer:matrix-qa.test");
    expect(createRoomBodies).toEqual([
      expect.objectContaining({
        invite: ["@qa-observer:matrix-qa.test", "@qa-sut:matrix-qa.test"],
        is_direct: false,
        preset: "private_chat",
      }),
    ]);
  });

  it("provisions direct-message topology rooms with Matrix direct-room flags", async () => {
    const createRoomBodies: Array<Record<string, unknown>> = [];
    const roomIds = ["!group:matrix-qa.test", "!dm:matrix-qa.test"];
    let registerCount = 0;
    const fetchImpl: typeof fetch = async (input, init) => {
      const url = resolveRequestUrl(input);
      const body = parseJsonRequestBody(init);
      if (url.endsWith("/_matrix/client/v3/register")) {
        registerCount += 1;
        const role = ["driver", "sut", "observer"][registerCount - 1];
        return new Response(
          JSON.stringify({
            access_token: `token-${role}`,
            user_id: `@qa-${role}:matrix-qa.test`,
          }),
          { status: 200, headers: { "content-type": "application/json" } },
        );
      }
      if (url.endsWith("/_matrix/client/v3/createRoom")) {
        createRoomBodies.push(body);
        return new Response(JSON.stringify({ room_id: roomIds.shift() }), {
          status: 200,
          headers: { "content-type": "application/json" },
        });
      }
      if (url.includes("/_matrix/client/v3/join/")) {
        return new Response(JSON.stringify({ room_id: "!joined:matrix-qa.test" }), {
          status: 200,
          headers: { "content-type": "application/json" },
        });
      }
      throw new Error(`unexpected fetch ${url}`);
    };

    const result = await provisionMatrixQaRoom({
      baseUrl: "http://127.0.0.1:28008/",
      driverLocalpart: "qa-driver",
      observerLocalpart: "qa-observer",
      registrationToken: "reg-token",
      roomName: "unused",
      sutLocalpart: "qa-sut",
      fetchImpl,
      topology: {
        defaultRoomKey: "group",
        rooms: [
          {
            key: "group",
            kind: "group",
            members: ["driver", "observer", "sut"],
            name: "Matrix Group",
            requireMention: true,
          },
          {
            key: "sut-dm",
            kind: "dm",
            members: ["driver", "sut"],
            name: "Matrix Driver/SUT DM",
          },
        ],
      },
    });

    expect(result.topology.rooms).toMatchObject([
      { key: "group", kind: "group", roomId: "!group:matrix-qa.test", requireMention: true },
      { key: "sut-dm", kind: "dm", roomId: "!dm:matrix-qa.test", requireMention: false },
    ]);
    expect(createRoomBodies).toEqual([
      expect.objectContaining({
        invite: ["@qa-observer:matrix-qa.test", "@qa-sut:matrix-qa.test"],
        is_direct: false,
        name: "Matrix Group",
      }),
      expect.objectContaining({
        invite: ["@qa-sut:matrix-qa.test"],
        is_direct: true,
        name: "Matrix Driver/SUT DM",
      }),
    ]);
  });
});
