import { afterEach, describe, expect, test } from "bun:test";
import { withGhDefaultRendered } from "./gh/default-render";
import { ghScopedHelp } from "./gh/help";
import { prReady } from "./gh/pr-ready";
import type { GitHubPullRequest } from "./gh/types";

const originalFetch = globalThis.fetch;

afterEach(() => {
  globalThis.fetch = originalFetch;
});

function pullRequest(overrides: Partial<GitHubPullRequest> = {}): GitHubPullRequest {
  return {
    id: 1748,
    node_id: "PR_kwDO_ready_1748",
    number: 1748,
    title: "PaC source artifact",
    body: "body",
    state: "open",
    html_url: "https://github.com/pikasTech/unidesk/pull/1748",
    draft: true,
    head: { ref: "fix/1746-pac-source-artifact", sha: "a".repeat(40) },
    base: { ref: "master", sha: "b".repeat(40) },
    ...overrides,
  };
}

function installFetch(handler: (url: URL, init: RequestInit | undefined) => Response | Promise<Response>): string[] {
  const calls: string[] = [];
  globalThis.fetch = (async (input: string | URL | Request, init?: RequestInit) => {
    const url = new URL(typeof input === "string" || input instanceof URL ? input.toString() : input.url);
    calls.push(`${init?.method ?? "GET"} ${url.pathname}`);
    return handler(url, init);
  }) as typeof fetch;
  return calls;
}

describe("gh pr ready", () => {
  test("marks an open draft PR ready through the shared GraphQL client", async () => {
    const calls = installFetch(async (url, init) => {
      if (url.pathname.endsWith("/pulls/1748")) return Response.json(pullRequest());
      expect(url.pathname).toBe("/graphql");
      expect(new Headers(init?.headers).get("authorization")).toBe("Bearer test-token");
      const payload = JSON.parse(String(init?.body)) as { query: string; variables: { pullRequestId: string } };
      expect(payload.query).toContain("markPullRequestReadyForReview");
      expect(payload.variables.pullRequestId).toBe("PR_kwDO_ready_1748");
      return Response.json({
        data: {
          markPullRequestReadyForReview: {
            pullRequest: {
              id: "PR_kwDO_ready_1748",
              number: 1748,
              title: "PaC source artifact",
              state: "OPEN",
              isDraft: false,
              url: "https://github.com/pikasTech/unidesk/pull/1748",
              headRefName: "fix/1746-pac-source-artifact",
              baseRefName: "master",
              updatedAt: "2026-07-11T04:30:00Z",
            },
          },
        },
      });
    });

    const result = await prReady("pikasTech/unidesk", "test-token", 1748, false);

    expect(result.ok).toBe(true);
    expect(result.action).toBe("marked-ready");
    expect(result.changed).toBe(true);
    expect(result.graphQl).toBe(true);
    expect((result.pullRequest as { draft?: boolean }).draft).toBe(false);
    expect(calls).toEqual(["GET /repos/pikasTech/unidesk/pulls/1748", "POST /graphql"]);
  });

  test("is idempotent when the open PR is already ready", async () => {
    const calls = installFetch(() => Response.json(pullRequest({ draft: false })));

    const result = await prReady("pikasTech/unidesk", "test-token", 1748, false);

    expect(result.ok).toBe(true);
    expect(result.action).toBe("already-ready");
    expect(result.changed).toBe(false);
    expect(result.graphQl).toBe(false);
    expect(calls).toEqual(["GET /repos/pikasTech/unidesk/pulls/1748"]);
    const rendered = withGhDefaultRendered(["pr", "ready", "1748"], result) as { renderedText: string };
    expect(rendered.renderedText).toContain("gh pr ready (already-ready)");
  });

  test("dry-run reads the PR and exposes a bounded mutation plan without writing", async () => {
    const calls = installFetch(() => Response.json(pullRequest()));

    const result = await prReady("pikasTech/unidesk", "test-token", 1748, true);

    expect(result.ok).toBe(true);
    expect(result.action).toBe("would-mark-ready");
    expect(result.planned).toBe(true);
    expect(result.changed).toBe(false);
    expect(result.request).toEqual({
      method: "POST",
      path: "/graphql",
      mutation: "markPullRequestReadyForReview",
      variables: { pullRequestId: "present" },
    });
    expect(calls).toEqual(["GET /repos/pikasTech/unidesk/pulls/1748"]);
  });

  test("rejects a closed PR before mutation with a typed validation error", async () => {
    const calls = installFetch(() => Response.json(pullRequest({ state: "closed" })));

    const result = await prReady("pikasTech/unidesk", "test-token", 1748, false);

    expect(result.ok).toBe(false);
    expect(result.degradedReason).toBe("validation-failed");
    expect(result.runnerDisposition).toBe("business-failed");
    expect(result.phase).toBe("validate-pr-state");
    expect(calls).toEqual(["GET /repos/pikasTech/unidesk/pulls/1748"]);
  });

  test("preserves the typed pr-not-found REST failure", async () => {
    installFetch(() => Response.json({ message: "Not Found" }, { status: 404 }));

    const result = await prReady("pikasTech/unidesk", "test-token", 404, false);

    expect(result.ok).toBe(false);
    expect(result.degradedReason).toBe("pr-not-found");
    expect(result.runnerDisposition).toBe("business-failed");
    expect(result.phase).toBe("fetch-pr");
  });

  test("preserves typed mutation permission failures", async () => {
    installFetch((url) => {
      if (url.pathname.endsWith("/pulls/1748")) return Response.json(pullRequest());
      return Response.json(
        { message: "Resource not accessible by integration" },
        { status: 403, headers: { "x-accepted-oauth-scopes": "repo" } },
      );
    });

    const result = await prReady("pikasTech/unidesk", "test-token", 1748, false);

    expect(result.ok).toBe(false);
    expect(result.degradedReason).toBe("scope-insufficient");
    expect(result.runnerDisposition).toBe("infra-blocked");
    expect(result.phase).toBe("mark-ready");
  });

  test("publishes bounded scoped help for the command", () => {
    const help = ghScopedHelp(["pr", "ready", "--help"]) as { usage: string[]; notes: string[] };
    expect(help.usage).toEqual([
      "bun scripts/cli.ts gh pr ready <number|url|owner/repo#number> [--repo owner/name] [--number N compat] [--dry-run]",
    ]);
    expect(help.notes.join("\n")).toContain("markPullRequestReadyForReview");
    expect(help.notes.join("\n")).toContain("不改变 GitHub 状态");
  });
});
