import { describe, it, expect, vi, beforeEach } from "vitest";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { BatchMode } from "@/components/BatchMode";
import { useBatchStore } from "@/stores/batch-store";
import { makeFocusItem } from "../helpers/fixtures";

describe("BatchMode", () => {
  const onOpenUrl = vi.fn();
  const onExit = vi.fn();

  beforeEach(() => {
    vi.clearAllMocks();
    useBatchStore.getState().exitBatch();
  });

  function startBatchWith(count: number) {
    const items = Array.from({ length: count }, (_, i) =>
      makeFocusItem({
        id: `r${i + 1}`,
        type: "mr_review",
        title: `Review MR !${900 + i}`,
        iid: 900 + i,
      })
    );
    useBatchStore.getState().startBatch(items, "CODE REVIEWS");
  }

  describe("rendering", () => {
    it("shows the batch label", () => {
      startBatchWith(3);
      render(<BatchMode onOpenUrl={onOpenUrl} onExit={onExit} />);
      expect(screen.getByText(/CODE REVIEWS/)).toBeInTheDocument();
    });

    it("shows progress (1 of N)", () => {
      startBatchWith(4);
      render(<BatchMode onOpenUrl={onOpenUrl} onExit={onExit} />);
      expect(screen.getByText(/1 of 4/)).toBeInTheDocument();
    });

    it("shows the current item title", () => {
      startBatchWith(3);
      render(<BatchMode onOpenUrl={onOpenUrl} onExit={onExit} />);
      expect(screen.getByText("Review MR !900")).toBeInTheDocument();
    });

    it("shows item metadata", () => {
      startBatchWith(2);
      render(<BatchMode onOpenUrl={onOpenUrl} onExit={onExit} />);
      // Metadata line contains IID and project
      const metaLine = screen.getByText((_content, el) => {
        return (
          el?.tagName === "P" &&
          Boolean(el.textContent?.includes("!900")) &&
          Boolean(el.textContent?.includes("platform/core"))
        );
      });
      expect(metaLine).toBeInTheDocument();
    });

    it("shows action buttons", () => {
      startBatchWith(2);
      render(<BatchMode onOpenUrl={onOpenUrl} onExit={onExit} />);
      expect(
        screen.getByRole("button", { name: /open in gl/i })
      ).toBeInTheDocument();
      expect(
        screen.getByRole("button", { name: /done/i })
      ).toBeInTheDocument();
      expect(
        screen.getByRole("button", { name: /skip/i })
      ).toBeInTheDocument();
    });

    it("shows ESC to exit hint", () => {
      startBatchWith(2);
      render(<BatchMode onOpenUrl={onOpenUrl} onExit={onExit} />);
      expect(screen.getByText(/ESC to exit/i)).toBeInTheDocument();
    });
  });

  describe("actions", () => {
    it("Open in GL calls onOpenUrl with current item URL", async () => {
      const user = userEvent.setup();
      startBatchWith(2);
      render(<BatchMode onOpenUrl={onOpenUrl} onExit={onExit} />);

      await user.click(
        screen.getByRole("button", { name: /open in gl/i })
      );
      expect(onOpenUrl).toHaveBeenCalledWith(
        "https://gitlab.com/platform/core/-/merge_requests/847"
      );
    });

    it("Done marks item and advances to next", async () => {
      const user = userEvent.setup();
      startBatchWith(3);
      render(<BatchMode onOpenUrl={onOpenUrl} onExit={onExit} />);

      await user.click(screen.getByRole("button", { name: /done/i }));

      expect(screen.getByText(/2 of 3/)).toBeInTheDocument();
      // Wait for AnimatePresence to swap items
      expect(await screen.findByText("Review MR !901")).toBeInTheDocument();
    });

    it("Skip marks item and advances to next", async () => {
      const user = userEvent.setup();
      startBatchWith(3);
      render(<BatchMode onOpenUrl={onOpenUrl} onExit={onExit} />);

      await user.click(screen.getByRole("button", { name: /skip/i }));

      expect(screen.getByText(/2 of 3/)).toBeInTheDocument();
    });
  });

  describe("keyboard shortcuts", () => {
    it("Cmd+D triggers Done", async () => {
      const user = userEvent.setup();
      startBatchWith(2);
      render(<BatchMode onOpenUrl={onOpenUrl} onExit={onExit} />);

      await user.keyboard("{Meta>}d{/Meta}");

      expect(screen.getByText(/2 of 2/)).toBeInTheDocument();
    });

    it("Cmd+S triggers Skip", async () => {
      const user = userEvent.setup();
      startBatchWith(2);
      render(<BatchMode onOpenUrl={onOpenUrl} onExit={onExit} />);

      await user.keyboard("{Meta>}s{/Meta}");

      expect(screen.getByText(/2 of 2/)).toBeInTheDocument();
    });

    it("Escape exits batch mode", async () => {
      const user = userEvent.setup();
      startBatchWith(2);
      render(<BatchMode onOpenUrl={onOpenUrl} onExit={onExit} />);

      await user.keyboard("{Escape}");
      expect(onExit).toHaveBeenCalledOnce();
    });
  });

  describe("completion", () => {
    it("shows celebration when all items are processed", async () => {
      const user = userEvent.setup();
      startBatchWith(2);
      render(<BatchMode onOpenUrl={onOpenUrl} onExit={onExit} />);

      await user.click(screen.getByRole("button", { name: /done/i }));
      await user.click(screen.getByRole("button", { name: /done/i }));

      expect(screen.getByText(/all done/i)).toBeInTheDocument();
      expect(screen.getByText(/2.*completed/i)).toBeInTheDocument();
    });

    it("shows completed and skipped counts in celebration", async () => {
      const user = userEvent.setup();
      startBatchWith(3);
      render(<BatchMode onOpenUrl={onOpenUrl} onExit={onExit} />);

      await user.click(screen.getByRole("button", { name: /done/i }));
      await user.click(screen.getByRole("button", { name: /skip/i }));
      await user.click(screen.getByRole("button", { name: /done/i }));

      expect(screen.getByText(/2.*completed/i)).toBeInTheDocument();
      expect(screen.getByText(/1.*skipped/i)).toBeInTheDocument();
    });

    it("celebration has a button to exit", async () => {
      const user = userEvent.setup();
      startBatchWith(1);
      render(<BatchMode onOpenUrl={onOpenUrl} onExit={onExit} />);

      await user.click(screen.getByRole("button", { name: /done/i }));

      const exitBtn = screen.getByRole("button", { name: /back to focus/i });
      await user.click(exitBtn);
      expect(onExit).toHaveBeenCalledOnce();
    });
  });
});
