/**
 * @file AddLaneModal.test.tsx
 * @description Pins the "+ Add lane" flow after it was rebuilt around a source
 * repo instead of an existing folder: picking or typing a repo path triggers a
 * branch lookup, the base-branch picker only appears once that lookup resolves,
 * confirm submits through the provisioning endpoint (not the adopt/ensure one),
 * an unresolvable path degrades to a quiet hint instead of blocking the form,
 * and a server error surfaces instead of closing the modal.
 * @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
 */

import { describe, it, expect, vi, beforeEach } from "vitest";
import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { AddLaneModal } from "../AddLaneModal";
import { api } from "../../../lib/api";
import type { CwdSuggestion } from "../../../lib/api";
import type { Lane } from "../../../lib/types";

vi.mock("../../../lib/api", () => ({
  api: { lanes: { branches: vi.fn(), worktree: vi.fn() } },
}));

function laneFixture(over: Partial<Lane> = {}): Lane {
  return {
    id: 9,
    title: "",
    cwd: "/lanes/repo__feature",
    branch: "feat/feature",
    kind: "managed",
    pipeline: "default",
    session_id: null,
    run_id: null,
    stage: "idle",
    stage_since: null,
    status: "provisioning",
    gate_decision: null,
    ci_status: null,
    needs_action: null,
    links: {},
    stages: {},
    notes: null,
    pipeline_name: "Default",
    pipeline_nodes: [],
    progress: 0,
    stage_seconds: null,
    last_event_seconds: null,
    liveness: "idle",
    detected_stage: null,
    detected_signal: null,
    slot: null,
    ports: {},
    ...over,
  };
}

const SUGGESTIONS: CwdSuggestion[] = [
  { kind: "home", path: "/Users/tester", label: "Home" },
  { kind: "recent", path: "/Users/tester/projects/repo", label: "repo" },
];

function renderModal(over: Partial<React.ComponentProps<typeof AddLaneModal>> = {}) {
  return render(
    <AddLaneModal open onClose={vi.fn()} onAdded={vi.fn()} cwdSuggestions={SUGGESTIONS} {...over} />
  );
}

/** ConfirmModal focuses its Cancel button on a 0ms timer after mount, which
 *  races userEvent.type() and can eat the first keystroke. Let that timer fire,
 *  then click the field to reclaim focus before typing. */
async function focusField(user: ReturnType<typeof userEvent.setup>, el: HTMLElement) {
  await new Promise((r) => setTimeout(r, 0));
  await user.click(el);
}

beforeEach(() => {
  vi.mocked(api.lanes.branches).mockReset();
  vi.mocked(api.lanes.worktree).mockReset();
});

describe("AddLaneModal", () => {
  it("disables confirm until a repo, a title, and a resolved branch list are all present", () => {
    renderModal();
    expect(screen.getByRole("button", { name: "Add lane" })).toBeDisabled();
  });

  it("looks up branches once the repo path settles, and shows them as a picker", async () => {
    vi.mocked(api.lanes.branches).mockResolvedValue({
      branches: ["main", "feat/other"],
      current: "main",
    });
    renderModal();
    const user = userEvent.setup();

    const repoField = screen.getByLabelText("Source repository");
    await focusField(user, repoField);
    await user.type(repoField, "/Users/tester/projects/repo");

    await waitFor(() =>
      expect(api.lanes.branches).toHaveBeenCalledWith("/Users/tester/projects/repo")
    );
    const base = await screen.findByLabelText("Branch to fork from");
    expect(base).toHaveValue("main"); // the repo's current branch is preselected
    expect(screen.getByRole("option", { name: "feat/other" })).toBeInTheDocument();
  });

  it("stays disabled and shows a quiet hint when the path is not a resolvable repo", async () => {
    vi.mocked(api.lanes.branches).mockRejectedValue(new Error("EBADSOURCEREPO"));
    renderModal();
    const user = userEvent.setup();

    const repoField = screen.getByLabelText("Source repository");
    await focusField(user, repoField);
    await user.type(repoField, "/not/a/repo");

    await waitFor(() => expect(api.lanes.branches).toHaveBeenCalled());
    expect(await screen.findByText(/Not a git repository/)).toBeInTheDocument();
    expect(screen.queryByLabelText("Branch to fork from")).toBeNull();
    expect(screen.getByRole("button", { name: "Add lane" })).toBeDisabled();
  });

  it("submits through the worktree provisioning endpoint, not ensure", async () => {
    vi.mocked(api.lanes.branches).mockResolvedValue({ branches: ["main"], current: "main" });
    vi.mocked(api.lanes.worktree).mockResolvedValue({ lane: laneFixture({ id: 9 }) });
    const onAdded = vi.fn();
    const onClose = vi.fn();
    renderModal({ onClose, onAdded });
    const user = userEvent.setup();

    const repoField = screen.getByLabelText("Source repository");
    await focusField(user, repoField);
    await user.type(repoField, "/Users/tester/projects/repo");
    await screen.findByLabelText("Branch to fork from");
    await user.type(screen.getByLabelText("Title"), "New feature");
    await user.click(screen.getByRole("button", { name: "Add lane" }));

    await waitFor(() => {
      expect(api.lanes.worktree).toHaveBeenCalledWith({
        sourceRepo: "/Users/tester/projects/repo",
        title: "New feature",
        base: "main",
      });
    });
    expect(onAdded).toHaveBeenCalledWith(
      expect.objectContaining({ id: 9, status: "provisioning" })
    );
    expect(onClose).toHaveBeenCalled();
  });

  it("shows a server error and leaves the modal open instead of closing silently", async () => {
    vi.mocked(api.lanes.branches).mockResolvedValue({ branches: ["main"], current: "main" });
    vi.mocked(api.lanes.worktree).mockRejectedValue(new Error("EWORKTREEDIRCOLLISION"));
    const onClose = vi.fn();
    renderModal({ onClose });
    const user = userEvent.setup();

    const repoField = screen.getByLabelText("Source repository");
    await focusField(user, repoField);
    await user.type(repoField, "/Users/tester/projects/repo");
    await screen.findByLabelText("Branch to fork from");
    await user.type(screen.getByLabelText("Title"), "New feature");
    await user.click(screen.getByRole("button", { name: "Add lane" }));

    expect(await screen.findByText("EWORKTREEDIRCOLLISION")).toBeInTheDocument();
    expect(onClose).not.toHaveBeenCalled();
  });

  it("renders nothing when closed", () => {
    renderModal({ open: false });
    expect(screen.queryByRole("dialog")).toBeNull();
  });
});
