import { Effect } from "effect";
import { describe, expect, it } from "vitest";

import {
  createBranch,
  createIssue,
  createPullRequest,
  getIssue,
  listIssues,
} from "./git-remote-runtime";
import type { GitRemoteConfig, GitRemoteTransport } from "./git-remote-runtime";

const config: GitRemoteConfig = {
  baseUrl: "https://git.example.com",
  owner: "puter",
  repo: "zopu-code",
  token: "secret",
};

const stubTransport = (
  responder: (method: string, path: string, body: unknown) => unknown,
  status = 200
): GitRemoteTransport => ({
  request: ({ body, method, url }) =>
    Effect.sync(() => {
      const path = url.replace(
        `${config.baseUrl}/api/v1/repos/puter/zopu-code`,
        ""
      );
      const result = responder(method, path, body);
      return {
        json: () => Promise.resolve(result),
        status,
      };
    }),
});

const run = <A, E>(eff: Effect.Effect<A, E>) => Effect.runPromise(eff);

describe("GitRemoteRuntime", () => {
  it("creates an issue and maps Gitea fields", async () => {
    const transport = stubTransport(() => ({
      body: "the body",
      html_url: "https://git.example.com/puter/zopu-code/issues/1",
      number: 1,
      state: "open",
      title: "Add status control",
    }));
    const issue = await run(
      createIssue(transport, config, {
        body: "the body",
        title: "Add status control",
      })
    );
    expect(issue.number).toBe(1);
    expect(issue.url).toContain("/issues/1");
  });

  it("maps 401 to Unauthorized", async () => {
    const transport = stubTransport(() => ({}), 401);
    await expect(run(listIssues(transport, config))).rejects.toMatchObject({
      reason: "Unauthorized",
    });
  });

  it("maps 404 to NotFound", async () => {
    const transport = stubTransport(() => ({}), 404);
    await expect(run(getIssue(transport, config, 7))).rejects.toMatchObject({
      reason: "NotFound",
    });
  });

  it("creates a branch", async () => {
    const transport = stubTransport((_m, _p, body) => ({
      name: (body as { new_branch_name: string }).new_branch_name,
    }));
    const branch = await run(
      createBranch(transport, config, { name: "work/1/fix" })
    );
    expect(branch.name).toBe("work/1/fix");
  });

  it("creates a pull request and reads nested ref fields", async () => {
    const transport = stubTransport(() => ({
      base: { ref: "main" },
      head: { ref: "work/1/fix" },
      html_url: "https://git.example.com/puter/zopu-code/pulls/2",
      number: 2,
      state: "open",
      title: "Fix thing",
    }));
    const pr = await run(
      createPullRequest(transport, config, {
        baseBranch: "main",
        body: "",
        branch: "work/1/fix",
        title: "Fix thing",
      })
    );
    expect(pr.number).toBe(2);
    expect(pr.branch).toBe("work/1/fix");
    expect(pr.baseBranch).toBe("main");
  });

  it("maps merged PR state", async () => {
    const transport = stubTransport(() => ({
      base: { ref: "main" },
      head: { ref: "work/1/fix" },
      merged: true,
      number: 2,
      state: "closed",
      title: "x",
    }));
    const pr = await run(
      createPullRequest(transport, config, {
        baseBranch: "main",
        body: "",
        branch: "work/1/fix",
        title: "x",
      })
    );
    expect(pr.state).toBe("merged");
  });
});
