import { readFile, symlink, writeFile } from "node:fs/promises";
import { expect } from "e2e";
import * as Effect from "effect/Effect";
import * as Layer from "effect/Layer";
import * as Option from "effect/Option";
import * as References from "effect/References";
import * as NodeCrypto from "@effect/platform-node/NodeCrypto";
import * as NetService from "@t3tools/shared/Net";
import * as EnvironmentAuth from "../../apps/server/src/auth/EnvironmentAuth.ts";
import * as ServerConfig from "../../apps/server/src/config.ts";
import { resolveCliAuthConfig } from "../../apps/server/src/cli/config.ts";
import { mcpToolSession } from "../../apps/server/src/mcp/AcpMcpStdioBridge.ts";
import { APP_DIR, T3_HOME } from "../e2e.config.ts";
import { run } from "./shell.ts";
import { test, WEB, runPromise } from "./t3.ts";

const forge = "http://127.0.0.1:3931";
const scratch = process.env.PR_WATCH_FIXTURE_DIR!;
const badge = '<a href="https://app.coderabbit.ai/change-stack/alice/demo/pull/1?cs_source=review_comment"><picture><source media="(prefers-color-scheme: dark)" srcset="https://coderabbit.ai/badges/severity-major-dark.svg"><img src="https://coderabbit.ai/badges/severity-major.svg" alt="Major severity badge"></picture></a>';
const api = async (path: string, body: unknown, user = "alice") => {
  const response = await fetch(`${forge}/api/v1/${path}`, {
    method: "POST", headers: { "Content-Type": "application/json", Authorization: `Basic ${Buffer.from(`${user}:Evidence${user === "alice" ? "Alice" : "Bob"}31530`).toString("base64")}` },
    body: JSON.stringify(body),
  });
  if (!response.ok) throw new Error(`Forgejo ${path}: ${response.status} ${await response.text()}`);
  return response.json();
};

const watch = async (threadId: string, url: string) => runPromise(Effect.gen(function* () {
  const config = yield* resolveCliAuthConfig({ baseDir: Option.some(T3_HOME) }, Option.none(), { quietLogs: true });
  return yield* Effect.gen(function* () {
    const auth = yield* EnvironmentAuth.EnvironmentAuth;
    const issued = yield* Effect.acquireRelease(auth.issueMcpClientSession({ label: "PR watch scenario", access: "full-access", peerLink: false, client: { deviceType: "bot" } }), session => auth.revokeSession(session.sessionId).pipe(Effect.ignore));
    const runtime = JSON.parse(yield* Effect.promise(() => readFile(`${T3_HOME}/userdata/server-runtime.json`, "utf8")));
    const session = yield* mcpToolSession({ endpoint: `${runtime.origin}/mcp`, authorization: `Bearer ${issued.token}`, clientName: "pr-watch-scenario" });
    const result = yield* session.call("watch_pull_request", { threadId, url });
    if ((result as any).isError) throw new Error(JSON.stringify(result));
    return result;
  }).pipe(Effect.scoped, Effect.provide(EnvironmentAuth.layerRuntime.pipe(Layer.provide(ServerConfig.layer(config)), Layer.provide(Layer.succeed(References.MinimumLogLevel, "Error")))));
}).pipe(Effect.provide([NodeCrypto.layer, NetService.layer])));

test("PR watch quotes the readable review after a decorative HTML badge", WEB, async ({ t3, app, screen }) => {
  await symlink(`${scratch}/tea`, `${T3_HOME}/bin/tea`);
  const replayPath = `${T3_HOME}/bin/pr-watch-replay.ts`;
  const replay = (await readFile(`${APP_DIR}/apps/server/scripts/dev-replay-agent.ts`, "utf8"))
    .replace('new URL(scenario.file, import.meta.url)', `new URL(scenario.file, ${JSON.stringify(`file://${APP_DIR}/apps/server/scripts/dev-replay-agent.ts`)})`)
    .replace('const turns = loadTurns(session.model);', `
        const received = JSON.stringify(params.prompt ?? []);
        const blocks = (params.prompt as Array<{ text?: string }> ?? []).map(p => p.text ?? "").join("\\n");
        const start = blocks.indexOf("Update on pull request #");
        if (start >= 0) {
          const actual = blocks.slice(start).split("\\n\\n")[0];
          send({ method: "session/update", params: { sessionId, update: { sessionUpdate: "agent_message_chunk", content: { type: "text", text: "Actual PR-watch wake received by replay agent:\\n\\n\\x60\\x60\\x60text\\n" + actual + "\\n\\x60\\x60\\x60" } } } });
          return { stopReason: "end_turn" };
        }
        const turns = loadTurns(session.model);`);
  await writeFile(replayPath, replay);
  const settingsPath = `${T3_HOME}/userdata/settings.json`;
  const settings = JSON.parse(await readFile(settingsPath, "utf8"));
  settings.providerInstances.devReplay.config.commandArgs = [replayPath];
  await writeFile(settingsPath, JSON.stringify(settings));
  const repo = `demo-${Date.now()}`;
  await api("user/repos", { name: repo, default_branch: "main", private: false });
  const project = await t3.project("PR watch demo", { "README.md": "# PR watch example\n" });
  const git = (args: string[]) => runPromise(run("git", args, { cwd: project }));
  await git(["remote", "add", "origin", `${forge}/alice/${repo}.git`]);
  const credentials = ["-c", `http.extraHeader=Authorization: Basic ${Buffer.from("alice:EvidenceAlice31530").toString("base64")}`];
  await git([...credentials, "push", "-u", "origin", "main"]);
  await git(["checkout", "-b", "feature"]);
  await runPromise(run("mkdir", ["-p", `${project}/src`]));
  await writeFile(`${project}/src/index.ts`, "export const first = <T>(items: T[]) => items[0];\n");
  await git(["add", "."]);
  await git(["-c", "user.name=Scenario", "-c", "user.email=scenario@example.invalid", "commit", "-m", "Add first item helper"]);
  await git([...credentials, "push", "-u", "origin", "feature"]);
  const collaborator = await fetch(`${forge}/api/v1/repos/alice/${repo}/collaborators/bob`, { method: "PUT", headers: { "Content-Type": "application/json", Authorization: `Basic ${Buffer.from("alice:EvidenceAlice31530").toString("base64")}` }, body: JSON.stringify({ permission: "write" }) });
  if (!collaborator.ok) throw new Error(`Collaborator: ${collaborator.status}`);
  const thread = await t3.thread({ project, title: "Review bot findings", messages: ["Watch the pull request for review findings."], model: "multi-turn" });
  const pr = await api(`repos/alice/${repo}/pulls`, { title: "Guard the empty list", head: "feature", base: "main" });
  await watch(thread.threadId, pr.html_url);
  await t3.open(thread);
  await api(`repos/alice/${repo}/pulls/1/reviews`, { event: "COMMENT", body: "", comments: [{ path: "src/index.ts", new_position: 1, body: `${badge}\n\n_⚠️ Potential issue_ | _🟠 Major_\n\nGuard the empty list before indexing it.` }] }, "bob");
  await api(`repos/alice/${repo}/issues/1/comments`, { body: "Replace `<div>` with `<span>` in the wrapper." }, "bob");
  await api(`repos/alice/${repo}/issues/1/comments`, { body: "Return Promise<void> here, not Promise<number>." }, "bob");
  const wake = screen.getByText("new comments", { exact: false }).first();
  await wake.waitFor({ state: "visible", timeout: 180_000 });
  await screen.getByText("Actual PR-watch wake received by replay agent:", { exact: false }).first().waitFor({ state: "visible", timeout: 30_000 });
  await screen.getByRole("button", "Toggle thread details panel").tap();
  await app.screenshot("pr-watch-comment-excerpt");
  await expect(screen.getByText("Guard the empty list before indexing it.", { exact: false }).first()).toBeVisible();
});
