import { assert, describe, it } from "@effect/vitest";
import * as Effect from "effect/Effect";
import * as Context from "effect/Context";
import * as Layer from "effect/Layer";
import * as Schema from "effect/Schema";
import * as FetchHttpClient from "effect/http/FetchHttpClient";
import * as HttpClient from "effect/http/HttpClient";
import type * as HttpClientRequest from "effect/http/HttpClientRequest";
import * as HttpClientResponse from "effect/http/HttpClientResponse";
import * as HostProcess from "@t3tools/shared/HostProcess";
import * as TestSourceControlHost from "@t3tools/source-control-testing/TestSourceControlHost";

import * as GitCafeApi from "./GitCafeApi.ts";
import * as GitCafeCredentials from "./GitCafeCredentials.ts";
import * as GitCafePullRequestProvider from "./GitCafePullRequestProvider.ts";

const encodeJson = Schema.encodeSync(Schema.fromJsonString(Schema.Unknown));
const decodeJson = Schema.decodeUnknownSync(Schema.fromJsonString(Schema.Unknown));

interface SentRequest {
  readonly method: string;
  readonly path: string;
  readonly query: URLSearchParams;
  readonly host: string;
  readonly body: unknown;
  readonly redirect: string | undefined;
}

function fakeGitCafe(
  routes: Record<string, unknown | ((request: SentRequest) => unknown)>,
  options: { readonly status?: number; readonly env?: NodeJS.ProcessEnv } = {},
) {
  const sent: Array<SentRequest> = [];
  const client = HttpClient.make(
    (request: HttpClientRequest.HttpClientRequest, _url, _signal, fiber) => {
      const url = new URL(request.url);
      assert.isTrue(url.pathname.startsWith("/api/"), url.pathname);
      assert.strictEqual(request.headers.authorization, "Bearer env-token");
      const entry: SentRequest = {
        method: request.method,
        path: url.pathname.slice("/api".length),
        query: url.searchParams,
        host: url.host,
        body:
          request.body._tag === "Uint8Array"
            ? decodeJson(new TextDecoder().decode(request.body.body))
            : undefined,
        redirect: Context.getOrUndefined(fiber.context, FetchHttpClient.RequestInit)?.redirect,
      };
      sent.push(entry);
      const route = routes[`${entry.method} ${entry.path}`];
      const response =
        route === undefined
          ? new Response(encodeJson({ type: "https://cafe.sh/errors/not-found" }), { status: 404 })
          : route instanceof Response
            ? route
            : new Response(encodeJson(typeof route === "function" ? route(entry) : route), {
                status: options.status ?? 200,
              });
      return Effect.succeed(HttpClientResponse.fromWeb(request, response));
    },
  );
  const layer = GitCafeApi.layer.pipe(
    Layer.provide(GitCafeCredentials.layer),
    Layer.provide(TestSourceControlHost.layer()),
    Layer.provide(
      Layer.succeed(HostProcess.Environment, options.env ?? { CAFE_TOKEN: "env-token" }),
    ),
    Layer.provide(Layer.succeed(HostProcess.WorkingDirectory, "/server")),
    Layer.provide(Layer.succeed(HttpClient.HttpClient, client)),
  );
  const writes = () =>
    sent.filter((request) => request.method !== "GET").map((r) => `${r.method} ${r.path}`);
  return { layer, sent, writes };
}

const target = { cwd: "/repo", host: "git.cafe", repository: "owner/repo", number: 7 };
const timestamp = "2026-09-12T12:00:00Z";
const headOid = "0123456789abcdef0123456789abcdef01234567";
const baseOid = "89abcdef0123456789abcdef0123456789abcdef";
const actor = {
  kind: "local",
  actorId: "act_one",
  handle: "alice",
  displayName: null,
  avatarUrl: "/avatars/one",
};
const pull = {
  id: "pr_one",
  number: 7,
  title: "Change",
  state: "open",
  draft: false,
  sourceBranch: "feature",
  targetBranch: "main",
  headOid,
  observedBaseOid: baseOid,
  mergeRoute: "provider",
  author: actor,
  createdAt: timestamp,
  updatedAt: timestamp,
  version: 4,
  description: "Body",
  closedAt: null,
  mergedAt: null,
  sourceRepo: null,
  capabilities: { comment: true, review: true, merge: true, edit: true, moderate: true },
};

import { readFileSync, writeFileSync } from "node:fs";
const evidenceDir = process.env.PROOF_DIR ?? "/tmp/pr24406-evidence";
const cases = JSON.parse(readFileSync(`${evidenceDir}/matrix-fixtures.json`, "utf8"));
const live = JSON.parse(readFileSync(`${evidenceDir}/sanitized-live-response.json`, "utf8"));
it.effect("replays EVERY proof matrix row through production getChangeRequest HTTP boundary", () =>
  Effect.gen(function* () {
    const records = [];
    for (const c of [...cases, { id: "LIVE-cancelled-rerun", mode: "live", items: live.checks.items }]) {
      let calls = 0;
      const liveCase = c.mode === "live";
      const chosenPull = liveCase ? live.pull : { ...pull, headOid: c.mode === "nohead" ? null : headOid };
      const checkPage = () => {
        const index = calls++;
        if (liveCase) return live.checks;
        if (c.mode === "pages") return c.pages[index];
        if (c.mode === "empty") return { items: [], next: "non-null" };
        return { items: c.items, next: c.mode === "limit" ? String(index+1) : c.mode === "repeat" ? "same" : null };
      };
      const server = fakeGitCafe({
        "GET /repos/owner/repo/pulls/7": chosenPull,
        "GET /repos/owner/repo/pulls/7/reviewers": { items: [], next: null },
        "GET /repos/owner/repo/pulls/7/labels": { items: [], next: null },
        "GET /repos/owner/repo/pulls/7/status": liveCase ? live.status : {
          merge: { conflicts: "none", fastForward: null, strategies: [] }, checks: c.counts,
        },
        [`GET /repos/owner/repo/commits/${chosenPull.headOid}/checks`]: checkPage,
        "GET /repos/owner/repo/pulls/7/changes": {
          version: chosenPull.version, headOid: chosenPull.headOid, comparisonBaseOid: baseOid, items: [], next: null,
        },
      });
      const detail = yield* Effect.gen(function* () {
        const provider = yield* GitCafePullRequestProvider.make;
        return yield* provider.getChangeRequest(target);
      }).pipe(Effect.provide(server.layer));
      const checks = detail.checks.map(({name,status}) => [name,status]).sort((a,b)=>JSON.stringify(a).localeCompare(JSON.stringify(b)));
      const record = { id: c.id, checks, ...(Object.hasOwn(detail,"checksState") ? {checksState:detail.checksState} : {}), checksStatePresent:Object.hasOwn(detail,"checksState"), checkRequests:calls, writes:server.writes() };
      records.push(record);
      console.log("PROOF_JSON " + JSON.stringify(record));
      assert.deepStrictEqual(server.writes(), []);
      if (process.env.PROOF_ASSERT === "1" && !liveCase) {
        assert.deepStrictEqual(checks,c.expected);
        assert.strictEqual(detail.checksState,c.state ?? undefined);
        assert.strictEqual(Object.hasOwn(detail,"checksState"),c.state !== null);
        assert.strictEqual(calls,c.mode === "nohead" ? 0 : c.mode === "limit" ? 10 : ["pages","repeat"].includes(c.mode) ? 2 : 1);
      }
      if (process.env.PROOF_ASSERT === "1" && liveCase) {
        assert.strictEqual(detail.checks.filter(x=>x.name === "Native fingerprint diff").length,1);
        assert.strictEqual(detail.checks.find(x=>x.name === "Native fingerprint diff")?.status,"success");
        assert.strictEqual(detail.checksState,"passing");
      }
    }
    writeFileSync(`${evidenceDir}/${process.env.PROOF_VERSION ?? "replay"}-runtime.json`,JSON.stringify(records,null,2)+"\n");
  }),
);
