/**
 * Integration tests for `GitGraphView` via `MockGitGateway`.
 *
 * Covers:
 * - The commits from the demo DAG are displayed.
 * - Branch refs are shown with primary colour class.
 * - Tag refs are shown with warning colour class.
 * - Clicking a commit row opens the detail panel.
 * - The detail panel shows full hash, author, and parents.
 * - Clicking close hides the detail panel.
 */

import { describe, it, expect } from "vitest";
import { render, screen, waitFor, fireEvent } from "@testing-library/react";

import { MockGitGateway } from "@/adapters/mock";
import type { Gateways } from "@/ports";
import { DIProvider } from "@/app/di";
import { GitGraphView } from "./GitGraphView";

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

function renderView(git: MockGitGateway = new MockGitGateway()) {
  const gateways = { git } as unknown as Gateways;
  return render(
    <DIProvider gateways={gateways}>
      <GitGraphView projectId="proj-graph-test" />
    </DIProvider>,
  );
}

/** Waits until at least one commit row is rendered. */
async function waitForCommits() {
  await waitFor(() => {
    const list = screen.getByRole("list", { name: "commits" });
    expect(list.children.length).toBeGreaterThan(0);
  });
}

// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------

describe("GitGraphView", () => {
  it("renders commits from the mock graph", async () => {
    renderView();
    await waitForCommits();

    // The demo DAG has "Merge feature into main" as the newest commit.
    expect(screen.getByText("Merge feature into main")).toBeTruthy();
    // And "Initial commit" as the oldest.
    expect(screen.getByText("Initial commit")).toBeTruthy();
  });

  it("shows branch ref badge for 'main'", async () => {
    renderView();
    await waitForCommits();

    // The "main" badge should appear (it's in the refs of the merge commit).
    const mainBadges = screen.getAllByText("main");
    expect(mainBadges.length).toBeGreaterThan(0);
  });

  it("shows tag ref badge for 'tag: v1.0'", async () => {
    renderView();
    await waitForCommits();

    const tagBadge = screen.getByText("tag: v1.0");
    expect(tagBadge).toBeTruthy();
    // Tag badge should have the warning colour class
    expect(tagBadge.className).toMatch(/warning/);
  });

  it("shows 'feature' branch badge", async () => {
    renderView();
    await waitForCommits();

    const featureBadge = screen.getByText("feature");
    expect(featureBadge).toBeTruthy();
    expect(featureBadge.className).toMatch(/primary/);
  });

  it("clicking a commit row opens the detail panel", async () => {
    renderView();
    await waitForCommits();

    // The detail panel should not be visible yet.
    expect(screen.queryByRole("complementary", { name: "commit detail" })).toBeNull();

    // Click the first commit row.
    const items = screen.getAllByRole("listitem");
    fireEvent.click(items[0]);

    await waitFor(() => {
      expect(
        screen.getByRole("complementary", { name: "commit detail" }),
      ).toBeTruthy();
    });
  });

  it("detail panel shows full commit hash and author", async () => {
    renderView();
    await waitForCommits();

    // Click the merge commit row.
    const mergeRow = screen.getByRole("listitem", {
      name: /commit eeeeeeee/,
    });
    fireEvent.click(mergeRow);

    await waitFor(() => {
      // Full hash should appear in the detail panel (code element).
      const fullHash = screen.getByText(
        "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
      );
      expect(fullHash.tagName.toLowerCase()).toBe("code");
    });

    // Author should appear in a <dd> (detail panel's definition list).
    await waitFor(() => {
      const detail = screen.getByRole("complementary", { name: "commit detail" });
      expect(detail.textContent).toContain("Alice");
    });
  });

  it("detail panel lists parents for a merge commit", async () => {
    renderView();
    await waitForCommits();

    const mergeRow = screen.getByRole("listitem", {
      name: /commit eeeeeeee/,
    });
    fireEvent.click(mergeRow);

    await waitFor(() => {
      const detail = screen.getByRole("complementary", { name: "commit detail" });
      // The merge commit has two parents displayed as short hashes.
      expect(detail.textContent).toContain("bbbbbbbb");
      expect(detail.textContent).toContain("dddddddd");
    });
  });

  it("closing the detail panel hides it", async () => {
    renderView();
    await waitForCommits();

    const items = screen.getAllByRole("listitem");
    fireEvent.click(items[0]);

    await waitFor(() => {
      expect(
        screen.getByRole("complementary", { name: "commit detail" }),
      ).toBeTruthy();
    });

    // Click the close button.
    fireEvent.click(screen.getByRole("button", { name: "close commit detail" }));

    await waitFor(() => {
      expect(
        screen.queryByRole("complementary", { name: "commit detail" }),
      ).toBeNull();
    });
  });

  it("clicking the same row again deselects and hides detail", async () => {
    renderView();
    await waitForCommits();

    const items = screen.getAllByRole("listitem");
    fireEvent.click(items[0]);

    await waitFor(() => {
      expect(
        screen.getByRole("complementary", { name: "commit detail" }),
      ).toBeTruthy();
    });

    // Click the same row again to deselect.
    fireEvent.click(items[0]);

    await waitFor(() => {
      expect(
        screen.queryByRole("complementary", { name: "commit detail" }),
      ).toBeNull();
    });
  });
});
