import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { createMemberContextMenu, createChannelContextMenu } from "@components/AdminActions";
import type { MemberContextMenuOptions, ChannelContextMenuOptions } from "@components/AdminActions";

describe("AdminActions", () => {
  let container: HTMLDivElement;

  beforeEach(() => {
    container = document.createElement("div");
    document.body.appendChild(container);
  });

  afterEach(() => {
    container.remove();
  });

  describe("MemberContextMenu", () => {
    function makeMenu(overrides?: Partial<MemberContextMenuOptions>) {
      const options: MemberContextMenuOptions = {
        userId: 1,
        username: "TestUser",
        currentRole: "member",
        availableRoles: ["admin", "moderator", "member"],
        onKick: overrides?.onKick ?? vi.fn(async () => {}),
        onBan: overrides?.onBan ?? vi.fn(async () => {}),
        onChangeRole: overrides?.onChangeRole ?? vi.fn(async () => {}),
      };
      const result = createMemberContextMenu(options);
      container.appendChild(result.element);
      return { result, options };
    }

    it("creates element with context-menu class", () => {
      const { result } = makeMenu();
      expect(result.element.classList.contains("context-menu")).toBe(true);
      result.destroy();
    });

    it("renders Change Role item", () => {
      const { result } = makeMenu();
      const items = result.element.querySelectorAll(".context-menu__item");
      const texts = Array.from(items).map((i) => i.textContent);
      expect(texts.some((t) => t?.includes("Change Role"))).toBe(true);
      result.destroy();
    });

    it("renders role submenu with available roles", () => {
      const { result } = makeMenu();
      const submenu = result.element.querySelector(".context-menu__submenu");
      expect(submenu).not.toBeNull();

      const roleItems = submenu!.querySelectorAll(".context-menu__item");
      const roleTexts = Array.from(roleItems).map((r) => r.textContent);
      expect(roleTexts).toContain("admin");
      expect(roleTexts).toContain("moderator");
      expect(roleTexts).toContain("member");
      result.destroy();
    });

    it("marks current role as active in submenu", () => {
      const { result } = makeMenu();
      const submenu = result.element.querySelector(".context-menu__submenu");
      const activeRole = submenu!.querySelector(".context-menu__item--active");
      expect(activeRole).not.toBeNull();
      expect(activeRole!.textContent).toBe("member");
      result.destroy();
    });

    it("renders Kick and Ban items with danger class", () => {
      const { result } = makeMenu();
      const dangerItems = result.element.querySelectorAll(".context-menu__item--danger");
      const texts = Array.from(dangerItems).map((i) => i.textContent);
      expect(texts).toContain("Kick");
      expect(texts).toContain("Ban");
      result.destroy();
    });

    it("Kick requires double-click confirmation", () => {
      const onKick = vi.fn(async () => {});
      const { result } = makeMenu({ onKick });

      const dangerItems = result.element.querySelectorAll(".context-menu__item--danger");
      const kickItem = Array.from(dangerItems).find(
        (i) => i.textContent === "Kick",
      ) as HTMLDivElement;

      // First click changes text to confirmation
      kickItem.click();
      expect(kickItem.textContent).toBe("Are you sure?");
      expect(onKick).not.toHaveBeenCalled();

      // Second click confirms
      kickItem.click();
      expect(onKick).toHaveBeenCalledOnce();
      result.destroy();
    });

    it("Ban asks for a reason before it fires", () => {
      const onBan = vi.fn(async () => {});
      const { result } = makeMenu({ onBan });

      const dangerItems = result.element.querySelectorAll(".context-menu__item--danger");
      const banItem = Array.from(dangerItems).find(
        (i) => i.textContent === "Ban",
      ) as HTMLDivElement;

      banItem.click();
      expect(onBan).not.toHaveBeenCalled();

      const reasonInput = result.element.querySelector(
        "[data-testid='ban-reason-input']",
      ) as HTMLInputElement;
      expect(reasonInput).not.toBeNull();
      reasonInput.value = "  spamming  ";

      const confirm = result.element.querySelector("[data-testid='ban-confirm']") as HTMLDivElement;
      confirm.click();

      // The server stores and displays the reason, so it's trimmed, not raw.
      expect(onBan).toHaveBeenCalledWith("spamming");
      result.destroy();
    });

    it("Ban sends an empty reason when none is typed", () => {
      const onBan = vi.fn(async () => {});
      const { result } = makeMenu({ onBan });

      const banItem = Array.from(
        result.element.querySelectorAll(".context-menu__item--danger"),
      ).find((i) => i.textContent === "Ban") as HTMLDivElement;
      banItem.click();
      (result.element.querySelector("[data-testid='ban-confirm']") as HTMLDivElement).click();

      expect(onBan).toHaveBeenCalledWith("");
      result.destroy();
    });

    it("Ban ignores a second click while the request is in flight", () => {
      let release: (() => void) | null = null;
      const onBan = vi.fn(
        () =>
          new Promise<void>((resolve) => {
            release = resolve;
          }),
      );
      const { result } = makeMenu({ onBan });

      const banItem = Array.from(
        result.element.querySelectorAll(".context-menu__item--danger"),
      ).find((i) => i.textContent === "Ban") as HTMLDivElement;
      banItem.click();
      const confirm = result.element.querySelector("[data-testid='ban-confirm']") as HTMLDivElement;
      confirm.click();
      expect(confirm.textContent).toBe("Banning...");
      confirm.click();

      expect(onBan).toHaveBeenCalledTimes(1);
      release!();
      result.destroy();
    });

    it("Kick shows an in-flight state and disarms after a pause", async () => {
      vi.useFakeTimers();
      try {
        let release: (() => void) | null = null;
        const onKick = vi.fn(
          () =>
            new Promise<void>((resolve) => {
              release = resolve;
            }),
        );
        const { result } = makeMenu({ onKick });
        const kickItem = Array.from(
          result.element.querySelectorAll(".context-menu__item--danger"),
        ).find((i) => i.textContent === "Kick") as HTMLDivElement;

        // Armed, then left alone — a stray later click must not kick anyone.
        kickItem.click();
        expect(kickItem.textContent).toBe("Are you sure?");
        vi.advanceTimersByTime(5000);
        expect(kickItem.textContent).toBe("Kick");
        kickItem.click();
        expect(onKick).not.toHaveBeenCalled();

        kickItem.click();
        expect(onKick).toHaveBeenCalledOnce();
        expect(kickItem.textContent).toBe("Kicking...");

        release!();
        await vi.waitFor(() => {
          expect(kickItem.textContent).toBe("Kick");
        });
        result.destroy();
      } finally {
        vi.useRealTimers();
      }
    });

    it("renders separator between role and danger items", () => {
      const { result } = makeMenu();
      const separator = result.element.querySelector(".context-menu__separator");
      expect(separator).not.toBeNull();
      result.destroy();
    });

    it("destroy removes element from DOM", () => {
      const { result } = makeMenu();
      expect(container.querySelector(".context-menu")).not.toBeNull();
      result.destroy();
      expect(container.querySelector(".context-menu")).toBeNull();
    });
  });

  describe("ChannelContextMenu", () => {
    function makeMenu(overrides?: Partial<ChannelContextMenuOptions>) {
      const options: ChannelContextMenuOptions = {
        channelId: 1,
        channelName: "general",
        onEdit: overrides?.onEdit ?? vi.fn(),
        onDelete: overrides?.onDelete ?? vi.fn(async () => {}),
        onCreate: overrides?.onCreate ?? vi.fn(),
      };
      const result = createChannelContextMenu(options);
      container.appendChild(result.element);
      return { result, options };
    }

    it("creates element with context-menu class", () => {
      const { result } = makeMenu();
      expect(result.element.classList.contains("context-menu")).toBe(true);
      result.destroy();
    });

    it("renders Edit Channel, Create Channel, and Delete Channel items", () => {
      const { result } = makeMenu();
      const items = result.element.querySelectorAll(".context-menu__item");
      const texts = Array.from(items).map((i) => i.textContent);

      expect(texts).toContain("Edit Channel");
      expect(texts).toContain("Create Channel");
      expect(texts).toContain("Delete Channel");
      result.destroy();
    });

    it("clicking Edit Channel calls onEdit", () => {
      const onEdit = vi.fn();
      const { result } = makeMenu({ onEdit });

      const items = result.element.querySelectorAll(".context-menu__item");
      const editItem = Array.from(items).find(
        (i) => i.textContent === "Edit Channel",
      ) as HTMLDivElement;
      editItem.click();

      expect(onEdit).toHaveBeenCalledOnce();
      result.destroy();
    });

    it("clicking Create Channel calls onCreate", () => {
      const onCreate = vi.fn();
      const { result } = makeMenu({ onCreate });

      const items = result.element.querySelectorAll(".context-menu__item");
      const createItem = Array.from(items).find(
        (i) => i.textContent === "Create Channel",
      ) as HTMLDivElement;
      createItem.click();

      expect(onCreate).toHaveBeenCalledOnce();
      result.destroy();
    });

    it("Delete Channel requires double-click confirmation", () => {
      const onDelete = vi.fn(async () => {});
      const { result } = makeMenu({ onDelete });

      const dangerItems = result.element.querySelectorAll(".context-menu__item--danger");
      const deleteItem = dangerItems[0] as HTMLDivElement;

      deleteItem.click();
      expect(deleteItem.textContent).toBe("Are you sure?");
      expect(onDelete).not.toHaveBeenCalled();

      deleteItem.click();
      expect(onDelete).toHaveBeenCalledOnce();
      result.destroy();
    });

    it("destroy removes element from DOM", () => {
      const { result } = makeMenu();
      expect(container.querySelector(".context-menu")).not.toBeNull();
      result.destroy();
      expect(container.querySelector(".context-menu")).toBeNull();
    });
  });
});
