import { describe, test, expect, mock } from "bun:test";
import { SandboxGitHubClient } from "@/github/client.js";
import { mockLogger } from "../_helpers/mock-logger.js";

const logger = mockLogger();

const VALID_REPO_URL = "https://github.com/acme/my-repo";
const TOKEN = "ghp_test_token";
const JIRA_DOMAIN = "acme";

function makeClient(): SandboxGitHubClient {
  return new SandboxGitHubClient(TOKEN, VALID_REPO_URL, JIRA_DOMAIN, logger);
}

/** Access internal Octokit namespaces as plain record for mutation. */
function octNamespace(client: SandboxGitHubClient, ns: string): Record<string, unknown> {
  const oct = (client as unknown as { octokit: Record<string, Record<string, unknown>> }).octokit;
  return oct[ns]!;
}

function stubOctokit(
  client: SandboxGitHubClient,
  ns: string,
  method: string,
  impl: (...args: unknown[]) => unknown,
): ReturnType<typeof mock> {
  const fn = mock(impl);
  octNamespace(client, ns)[method] = fn;
  return fn;
}

function stubResolve(
  client: SandboxGitHubClient,
  ns: string,
  method: string,
  value: unknown,
): ReturnType<typeof mock> {
  return stubOctokit(client, ns, method, async () => value);
}

// ── SandboxGitHubClient ──────────────────────────────────────────────────────

describe("SandboxGitHubClient – createPr always sends draft: true", () => {
  test("passes draft=true to pulls.create even when called without draft arg", async () => {
    const client = makeClient();
    // No existing PR
    stubResolve(client, "pulls", "list", { data: [] });

    const capturedParams: Record<string, unknown>[] = [];
    octNamespace(client, "pulls")["create"] = mock(async (p: Record<string, unknown>) => {
      capturedParams.push(p);
      return { data: { number: 7, html_url: "https://github.com/acme/my-repo/pull/7" } };
    });

    const result = await client.createPr("feat/my-branch", "PR title", "PR body");
    expect(capturedParams[0]!["draft"]).toBe(true);
    expect(result).toEqual({ number: 7, url: "https://github.com/acme/my-repo/pull/7" });
  });

  test("passes draft=true even when a base branch is explicitly provided", async () => {
    const client = makeClient();
    stubResolve(client, "pulls", "list", { data: [] });

    const capturedParams: Record<string, unknown>[] = [];
    octNamespace(client, "pulls")["create"] = mock(async (p: Record<string, unknown>) => {
      capturedParams.push(p);
      return { data: { number: 8, html_url: "https://github.com/acme/my-repo/pull/8" } };
    });

    await client.createPr("feat/my-branch", "PR title", "PR body", "main");
    expect(capturedParams[0]!["draft"]).toBe(true);
    expect(capturedParams[0]!["base"]).toBe("main");
  });
});

describe("SandboxGitHubClient – addLabels is a no-op", () => {
  test("does NOT call octokit.issues.addLabels", async () => {
    const client = makeClient();
    const addLabelsMock = stubResolve(client, "issues", "addLabels", { data: [] });

    await client.addLabels(42, ["bug", "squad/frontend"]);
    expect(addLabelsMock.mock.calls).toHaveLength(0);
  });

  test("resolves without throwing even when labels are provided", async () => {
    const client = makeClient();
    stubResolve(client, "issues", "addLabels", { data: [] });

    await expect(client.addLabels(1, ["label-a"])).resolves.toBeUndefined();
  });
});
