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

import {
  decideGitLifecycle,
  inspectGitWorkspace,
  makeCommitMessage,
  validatePullRequestMetadata,
} from "./git";

const inspectionInput = {
  branch: "work/issue-5",
  diff: "diff --git a/file.ts b/file.ts",
  status: " M file.ts",
};

const inspection = await Effect.runPromise(
  inspectGitWorkspace(inspectionInput)
);

describe("Git lifecycle primitives", () => {
  test("parses workspace status and treats a diff as publishable changes", () => {
    expect(inspection).toMatchObject({
      branch: "work/issue-5",
      hasChanges: true,
      status: [{ index: " ", path: "file.ts", worktree: "M" }],
    });
  });

  test("requires verification before any publish action", async () => {
    const result = await Effect.runPromiseExit(
      decideGitLifecycle({
        baseBranch: "main",
        inspection,
        pushed: false,
        verification: "not-run",
      })
    );

    expect(Exit.isFailure(result)).toBe(true);
    if (Exit.isFailure(result)) {
      const failure = result.cause as unknown as {
        readonly reasons: readonly [
          { readonly error: { readonly reason: string } },
        ];
      };
      expect(failure.reasons[0]?.error.reason).toBe("VerificationRequired");
    }
  });

  test("never publishes the default branch", async () => {
    const result = await Effect.runPromiseExit(
      decideGitLifecycle({
        baseBranch: "work/issue-5",
        inspection,
        pushed: false,
        verification: "passed",
      })
    );

    expect(Exit.isFailure(result)).toBe(true);
    if (Exit.isFailure(result)) {
      const failure = result.cause as unknown as {
        readonly reasons: readonly [
          { readonly error: { readonly reason: string } },
        ];
      };
      expect(failure.reasons[0]?.error.reason).toBe("InvalidBranch");
    }
  });

  test("moves verified changes through commit, push, PR, then manual merge", async () => {
    const commit = await Effect.runPromise(
      decideGitLifecycle({
        baseBranch: "main",
        inspection,
        pushed: false,
        verification: "passed",
      })
    );
    expect(commit).toEqual({ decision: "commit", status: "committed" });

    const push = await Effect.runPromise(
      decideGitLifecycle({
        baseBranch: "main",
        commitSha: "abc123",
        inspection: { ...inspection, hasChanges: false, status: [] },
        pushed: false,
        verification: "passed",
      })
    );
    expect(push).toEqual({ decision: "push", status: "pushed" });

    const pr = await Effect.runPromise(
      decideGitLifecycle({
        baseBranch: "main",
        commitSha: "abc123",
        inspection: { ...inspection, hasChanges: false, status: [] },
        pushed: true,
        verification: "passed",
      })
    );
    expect(pr).toEqual({
      decision: "create_pull_request",
      status: "pull_request_open",
    });

    const merge = await Effect.runPromise(
      decideGitLifecycle({
        baseBranch: "main",
        commitSha: "abc123",
        inspection: { ...inspection, hasChanges: false, status: [] },
        pullRequest: {
          baseBranch: "main",
          branch: "work/issue-5",
          number: 5,
          status: "open",
          url: "https://git.openputer.com/puter/zopu-code/pulls/5",
        },
        pushed: true,
        verification: "passed",
      })
    );
    expect(merge).toEqual({
      decision: "manual_merge",
      status: "pull_request_open",
    });
  });

  test("validates PR metadata and keeps merge manual", async () => {
    const metadata = await Effect.runPromise(
      validatePullRequestMetadata({
        baseBranch: "main",
        branch: "work/issue-5",
        number: 5,
        status: "open",
        url: "https://git.openputer.com/puter/zopu-code/pulls/5",
      })
    );
    expect(metadata.status).toBe("open");
    expect(makeCommitMessage(5, "Ship Git lifecycle")).toBe(
      "feat(issue-5): Ship Git lifecycle"
    );
  });
});
