import * as NodeCrypto from "@effect/platform-node/NodeCrypto";
import * as NodeServices from "@effect/platform-node/NodeServices";
import { it } from "@effect/vitest";
import { EnvironmentId, ProviderInstanceId, ThreadId } from "@t3tools/contracts";
import * as Effect from "effect/Effect";
import * as NodeHttp from "node:http";
import * as Fiber from "effect/Fiber";
import * as Layer from "effect/Layer";
import { FetchHttpClient } from "effect/http";
import * as ServerConfig from "../config.ts";
import * as ServerEnvironment from "../environment/ServerEnvironment.ts";
import * as Broker from "../mcp/PreviewAutomationBroker.ts";
import * as DesktopChannel from "./DesktopBrowserChannel.ts";
import * as Manager from "./Manager.ts";
import * as PreviewAgentActivity from "./PreviewAgentActivity.ts";
import * as ServerBrowser from "./ServerBrowser.ts";
import * as PreviewBrowser from "./PreviewBrowser.ts";

const environmentId = EnvironmentId.make("env-16921");
const scopeFor = (session: string, thread: string) => ({
  environmentId,
  thread: {
    threadId: ThreadId.make(thread),
    providerSessionId: session,
    providerInstanceId: ProviderInstanceId.make("codex"),
  },
  client: undefined,
  requestNamespace: session,
  capabilities: new Set(["preview"] as const),
  issuedAt: 1,
});
const deps = Layer.mergeAll(
  Broker.layer,
  Manager.layer,
  Layer.succeed(ServerEnvironment.ServerEnvironment, {
    getEnvironmentId: Effect.succeed(environmentId),
    getDescriptor: Effect.die("unused"),
  }),
  PreviewBrowser.layer,
  DesktopChannel.layer,
  PreviewAgentActivity.layer,
).pipe(
  Layer.provideMerge(ServerConfig.layerTest(process.cwd(), { prefix: "t3-16921-" })),
  Layer.provideMerge(Layer.mergeAll(NodeServices.layer, NodeCrypto.layer, FetchHttpClient.layer)),
);
const layer = ServerBrowser.layer.pipe(Layer.provideMerge(deps));
const t0 = Date.now();
const log = (...a: unknown[]) => console.log(`[+${Date.now() - t0}ms]`, ...a);

it.live(
  "real chromium: stalled tab in thread A vs open in thread B",
  () =>
    Effect.scoped(
      Effect.gen(function* () {
        const srv = NodeHttp.createServer((req, res) => {
          res.setHeader("content-type", "text/html");
          res.end(`<title>${req.url}</title><h1>${req.url}</h1><a href="/x">x</a>`);
        });
        yield* Effect.promise(() => new Promise<void>((r) => srv.listen(0, "127.0.0.1", () => r())));
        yield* Effect.addFinalizer(() => Effect.sync(() => srv.close()));
        const port = (srv.address() as { port: number }).port;
        yield* ServerBrowser.ServerBrowser;
        const broker = yield* Broker.PreviewAutomationBroker;
        yield* Effect.sleep("2 seconds");
        const a = scopeFor("agent-a", "thread-a");
        const b = scopeFor("agent-b", "thread-b");
        const opened = yield* broker.invoke<{ tabId: string }>({
          scope: a,
          operation: "open",
          input: { url: `http://127.0.0.1:${port}/a`, reuseExistingTab: false, show: false },
        });
        log("A opened", opened.tabId);
        const tabA = opened.tabId;
        const openedB = yield* broker.invoke<{ tabId: string }>({
          scope: b,
          operation: "open",
          input: { url: `http://127.0.0.1:${port}/b`, reuseExistingTab: false, show: false },
        });
        log("B opened", openedB.tabId);
        const track = (label: string, eff: Effect.Effect<unknown, unknown>) =>
          eff.pipe(
            Effect.exit,
            Effect.tap((e) => Effect.sync(() => log(label, JSON.stringify(e).slice(0, 1400)))),
            Effect.forkScoped,
          );
        // thread A: a wait whose browser-side timeout outlasts the broker's 15s budget
        const waitA = yield* track(
          "A waitFor(25s) exit",
          broker.invoke({ scope: a, operation: "waitFor", input: { text: "never-there", timeoutMs: 25_000 } }),
        );
        yield* Effect.sleep("5 seconds");
        // thread B: an unrelated, healthy call on its own tab, still in flight when A's deadline passes
        const clickB = yield* track(
          "B click(15s) exit",
          broker.invoke({ scope: b, operation: "click", input: { selector: "#appears-later", timeoutMs: 15_000 } }),
        );
        yield* Fiber.join(waitA);
        yield* Fiber.join(clickB);
        const statusLater = yield* broker
          .invoke({ scope: b, operation: "status", input: {} })
          .pipe(Effect.exit);
        log("B status (after both)", JSON.stringify(statusLater).slice(0, 300));
        yield* Effect.sleep("2 seconds");
        const snap = yield* track("B status (+2s)", broker.invoke({ scope: b, operation: "status", input: {} }));
        yield* Fiber.join(snap);
      }),
    ).pipe(Effect.provide(layer)),
  120_000,
);
