// Copyright 2022 - 2024 Gnuxie <Gnuxie@protonmail.com>
// Copyright 2021 The Matrix.org Foundation C.I.C.
//
// SPDX-License-Identifier: Apache-2.0
//
// SPDX-FileAttributionText: <text>
// This modified file incorporates work from mjolnir
// https://github.com/matrix-org/mjolnir
// </text>

import { strict as assert } from "assert";
import { newTestUser } from "../clientHelper";
import { getMessagesByUserIn } from "../../../src/utils";
import { LogService } from "@vector-im/matrix-bot-sdk";
import { getFirstReaction } from "./commandUtils";
import {
  draupnirClient,
  draupnirSafeEmitter,
  DraupnirTestContext,
} from "../mjolnirSetupUtils";
import { MatrixClient } from "@vector-im/matrix-bot-sdk";
import { RoomEvent } from "matrix-protection-suite";
import { extractRawRoomEvent } from "matrix-protection-suite-for-matrix-bot-sdk";

interface RedactionTestContext extends DraupnirTestContext {
  moderator?: MatrixClient;
}

describe("Test: The redaction command", function () {
  // If a test has a timeout while awaitng on a promise then we never get given control back.
  afterEach(function (this: RedactionTestContext) {
    this.moderator?.stop();
  });

  it(
    "Draupnir redacts all of the events sent by a spammer when instructed to by giving their id and a room id.",
    async function (this: RedactionTestContext) {
      this.timeout(60000);
      // Create a few users and a room.
      const badUser = await newTestUser(this.config.homeserverUrl, {
        name: { contains: "spammer-needs-redacting" },
      });
      const badUserId = await badUser.getUserId();
      const draupnirMatrixClient = draupnirClient();
      const draupnir = this.draupnir;
      if (draupnirMatrixClient === null || draupnir === undefined) {
        throw new TypeError(`Test isn't setup correctly`);
      }
      const mjolnirUserId = await draupnirMatrixClient.getUserId();
      const moderator = await newTestUser(this.config.homeserverUrl, {
        name: { contains: "moderator" },
      });
      this.moderator = moderator;
      await moderator.joinRoom(draupnir.managementRoomID);
      const targetRoom = await moderator.createRoom({
        invite: [await badUser.getUserId(), mjolnirUserId],
      });
      await moderator.setUserPowerLevel(mjolnirUserId, targetRoom, 100);
      await badUser.joinRoom(targetRoom);
      await getFirstReaction(
        draupnirSafeEmitter(),
        draupnir.managementRoomID,
        "✅",
        async () => {
          return moderator.sendMessage(draupnir.managementRoomID, {
            msgtype: "m.text",
            body: `!draupnir rooms add ${targetRoom}`,
          });
        }
      );
      LogService.debug(
        "redactionTest",
        `targetRoom: ${targetRoom}, managementRoom: ${this.config.managementRoom}`
      );
      // Sandwich irrelevant messages in bad messages.
      await badUser.sendMessage(targetRoom, {
        msgtype: "m.text",
        body: "Very Bad Stuff",
      });
      await Promise.all(
        [...Array(50).keys()].map((i) =>
          moderator.sendMessage(targetRoom, {
            msgtype: "m.text",
            body: `Irrelevant Message #${i}`,
          })
        )
      );
      for (let i = 0; i < 5; i++) {
        await badUser.sendMessage(targetRoom, {
          msgtype: "m.text",
          body: "Very Bad Stuff",
        });
      }
      await Promise.all(
        [...Array(50).keys()].map((i) =>
          moderator.sendMessage(targetRoom, {
            msgtype: "m.text",
            body: `Irrelevant Message #${i}`,
          })
        )
      );
      await badUser.sendMessage(targetRoom, {
        msgtype: "m.text",
        body: "Very Bad Stuff",
      });

      await getFirstReaction(
        draupnirSafeEmitter(),
        draupnir.managementRoomID,
        "✅",
        async () => {
          return await moderator.sendMessage(draupnir.managementRoomID, {
            msgtype: "m.text",
            body: `!draupnir redact ${badUserId} --room ${targetRoom}`,
          });
        }
      );

      await getMessagesByUserIn(
        moderator,
        badUserId,
        targetRoom,
        1000,
        function (events) {
          events.map((e) => {
            if (e.type === "m.room.member") {
              assert.equal(
                Object.keys(e.content).length,
                1,
                "Only membership should be left on the membership even when it has been redacted."
              );
            } else if (Object.keys(e.content).length !== 0) {
              throw new Error(
                `This event should have been redacted: ${JSON.stringify(e, null, 2)}`
              );
            }
          });
        }
      );
    } as unknown as Mocha.AsyncFunc
  );

  it(
    "Draupnir redacts all of the events sent by a spammer when instructed to by giving their id in multiple rooms.",
    async function (this: RedactionTestContext) {
      this.timeout(60000);
      // Create a few users and a room.
      const badUser = await newTestUser(this.config.homeserverUrl, {
        name: { contains: "spammer-needs-redacting" },
      });
      const badUserId = await badUser.getUserId();
      const draupnir = this.draupnir;
      if (draupnir === undefined) {
        throw new TypeError(`Test isn't setup correctly`);
      }
      const mjolnirUserId = await draupnir.client.getUserId();
      const moderator = await newTestUser(this.config.homeserverUrl, {
        name: { contains: "moderator" },
      });
      this.moderator = moderator;
      await moderator.joinRoom(draupnir.managementRoomID);
      const targetRooms: string[] = [];
      for (let i = 0; i < 5; i++) {
        const targetRoom = await moderator.createRoom({
          invite: [await badUser.getUserId(), mjolnirUserId],
        });
        await moderator.setUserPowerLevel(mjolnirUserId, targetRoom, 100);
        await badUser.joinRoom(targetRoom);
        await moderator.sendMessage(draupnir.managementRoomID, {
          msgtype: "m.text",
          body: `!draupnir rooms add ${targetRoom}`,
        });
        targetRooms.push(targetRoom);

        // Sandwich irrelevant messages in bad messages.
        await badUser.sendMessage(targetRoom, {
          msgtype: "m.text",
          body: "Very Bad Stuff",
        });
        await Promise.all(
          [...Array(50).keys()].map((j) =>
            moderator.sendMessage(targetRoom, {
              msgtype: "m.text",
              body: `Irrelevant Message #${j}`,
            })
          )
        );
        for (let j = 0; j < 5; j++) {
          await badUser.sendMessage(targetRoom, {
            msgtype: "m.text",
            body: "Very Bad Stuff",
          });
        }
        await Promise.all(
          [...Array(50).keys()].map((j) =>
            moderator.sendMessage(targetRoom, {
              msgtype: "m.text",
              body: `Irrelevant Message #${j}`,
            })
          )
        );
        await badUser.sendMessage(targetRoom, {
          msgtype: "m.text",
          body: "Very Bad Stuff",
        });
      }

      await getFirstReaction(
        draupnirSafeEmitter(),
        draupnir.managementRoomID,
        "✅",
        async () => {
          return await moderator.sendMessage(draupnir.managementRoomID, {
            msgtype: "m.text",
            body: `!draupnir redact ${badUserId}`,
          });
        }
      );

      await Promise.all(
        targetRooms.map(async (targetRoom) => {
          await getMessagesByUserIn(
            moderator,
            badUserId,
            targetRoom,
            1000,
            function (events) {
              events.map((e) => {
                if (e.type === "m.room.member") {
                  assert.equal(
                    Object.keys(e.content).length,
                    1,
                    "Only membership should be left on the membership even when it has been redacted."
                  );
                } else if (Object.keys(e.content).length !== 0) {
                  throw new Error(
                    `This event should have been redacted: ${JSON.stringify(e, null, 2)}`
                  );
                }
              });
            }
          );
        })
      );
    } as unknown as Mocha.AsyncFunc
  );
  it("Redacts a single event when instructed to.", async function (
    this: RedactionTestContext
  ) {
    this.timeout(60000);
    // Create a few users and a room.
    const badUser = await newTestUser(this.config.homeserverUrl, {
      name: { contains: "spammer-needs-redacting" },
    });
    const draupnir = this.draupnir;
    if (draupnir === undefined) {
      throw new TypeError(`Test isn't setup correctly`);
    }
    const moderator = await newTestUser(this.config.homeserverUrl, {
      name: { contains: "moderator" },
    });
    this.moderator = moderator;
    await moderator.joinRoom(draupnir.managementRoomID);
    const targetRoom = await moderator.createRoom({
      invite: [await badUser.getUserId(), draupnir.clientUserID],
    });
    await moderator.setUserPowerLevel(draupnir.clientUserID, targetRoom, 100);
    await badUser.joinRoom(targetRoom);
    await moderator.sendMessage(draupnir.managementRoomID, {
      msgtype: "m.text",
      body: `!draupnir rooms add ${targetRoom}`,
    });
    const eventToRedact = await badUser.sendMessage(targetRoom, {
      msgtype: "m.text",
      body: "Very Bad Stuff",
    });

    await getFirstReaction(
      draupnirSafeEmitter(),
      draupnir.managementRoomID,
      "✅",
      async () => {
        return await moderator.sendMessage(draupnir.managementRoomID, {
          msgtype: "m.text",
          body: `!draupnir redact https://matrix.to/#/${encodeURIComponent(targetRoom)}/${encodeURIComponent(eventToRedact)}`,
        });
      }
    );

    const redactedEvent = extractRawRoomEvent(
      await moderator.getEvent(targetRoom, eventToRedact)
    ) as RoomEvent;
    assert.equal(
      Object.keys(redactedEvent.content).length,
      0,
      "This event should have been redacted"
    );
  } as unknown as Mocha.AsyncFunc);
});
