import { createRequire } from "node:module";
import { appendFileSync, existsSync, mkdirSync, readFileSync, watch } from "node:fs";
import { createInterface } from "node:readline";

const wt = process.env.WT!;
const req = createRequire(`${wt}/apps/server/package.json`);
const load = (name: string) => name.startsWith("@t3tools/shared/")
  ? import(`${wt}/packages/shared/src/${name.slice("@t3tools/shared/".length)}.ts`)
  : import(req.resolve(name));
const [NodeRuntime, NodeServices, NodeCrypto, HostProcess, RelayClient, ConfigProvider, Effect, Layer,
  Deferred, Exit, Fiber, Scope, Stream, ChildProcessSpawner, HttpClient] = await Promise.all([
  "@effect/platform-node/NodeRuntime", "@effect/platform-node/NodeServices", "@effect/platform-node/NodeCrypto",
  "@t3tools/shared/HostProcess", "@t3tools/shared/relayClient", "effect/ConfigProvider", "effect/Effect", "effect/Layer",
  "effect/Deferred", "effect/Exit", "effect/Fiber", "effect/Scope", "effect/Stream",
  "effect/process/ChildProcessSpawner", "effect/http/HttpClient",
].map(load));
const [ManagedEndpointRuntime, ServerSecretStore, ServerConfig, ManagedPolicy] = await Promise.all([
  import(`${wt}/apps/server/src/cloud/ManagedEndpointRuntime.ts`),
  import(`${wt}/apps/server/src/auth/ServerSecretStore.ts`),
  import(`${wt}/apps/server/src/config.ts`),
  import(`${wt}/apps/server/src/policy/ManagedPolicy.ts`),
]);
const row = process.env.ROW!;
console.log(JSON.stringify({ timestamp: new Date().toISOString(), source: "driver", event: "actual-node-version", node: process.version, executable: process.execPath }));
const baseDir = process.env.BASE_DIR!;
const stamp = (event: string, data = {}) => console.log(JSON.stringify({ timestamp: new Date().toISOString(), source: "driver", event, ...data }));
const config = { providerKind: "cloudflare_tunnel", connectorToken: "synthetic-token", tunnelId: "tunnel-1" };
const commandGates = new Map<string, ReturnType<typeof Deferred.makeUnsafe>>();
const gate = (name: string) => {
  if (!commandGates.has(name)) commandGates.set(name, Deferred.makeUnsafe());
  return commandGates.get(name)!;
};
const input = createInterface({ input: process.stdin });
input.on("line", (name) => {
  stamp("command-received", { command: name });
  Deferred.doneUnsafe(gate(name), Exit.void);
});
const waitCommand = (name: string) => Deferred.await(gate(name));
const releaseLock = Deferred.makeUnsafe();
let signals = 0;
for (const signal of ["SIGINT", "SIGTERM"] as const) process.on(signal, () => {
  signals++;
  stamp("host-signal-received", { signal, signals });
  if (row === "B2") {
    stamp("lock-release-gate-opened-after-signal", { signal });
    Deferred.doneUnsafe(releaseLock, Exit.void);
  }
});
const identities = new WeakMap<Function, number>();
let nextIdentity = 1;
const listenerSnapshot = () => Object.fromEntries(["SIGINT", "SIGTERM"].map((signal) => [signal, process.listeners(signal).map((fn) => {
  if (!identities.has(fn)) identities.set(fn, nextIdentity++);
  return identities.get(fn);
})]));
const baseline = listenerSnapshot();
const listeners = (phase: string) => {
  const current = listenerSnapshot();
  stamp("listeners", { phase, identities: current, counts: Object.fromEntries(Object.entries(current).map(([k, v]) => [k, v.length])), baselineRestored: JSON.stringify(current) === JSON.stringify(baseline) });
};
listeners("before-build");

// Forward complete synthetic file lines for event-driven runner gates, preserving the raw files.
const watched = new Set<string>();
const forwardFile = (path: string, source: string) => {
  mkdirSync(path.slice(0, path.lastIndexOf("/")), { recursive: true });
  if (!existsSync(path)) appendFileSync(path, "");
  let offset = 0;
  const drain = () => {
    const contents = readFileSync(path, "utf8");
    const end = contents.lastIndexOf("\n") + 1;
    for (const line of contents.slice(offset, end).split("\n").filter(Boolean)) {
      console.log(JSON.stringify({ timestamp: new Date().toISOString(), source, event: "file-line", line }));
    }
    offset = end;
  };
  const watcher = watch(path, { persistent: false }, drain);
  watched.add(path);
  return () => { drain(); watcher.close(); };
};
const stopStubForwarding = forwardFile(process.env.STUB_LOG!, "stub");
const stopRelayForwarding = forwardFile(`${baseDir}/userdata/logs/relay-client.ndjson`, "relay");

// Observe the real Node handle's exit Effect, preserving its real exit result.
const observedSpawner = Layer.effect(ChildProcessSpawner.ChildProcessSpawner, Effect.gen(function* () {
  const actual = yield* ChildProcessSpawner.ChildProcessSpawner;
  return ChildProcessSpawner.make((command) => actual.spawn(command).pipe(Effect.map((child) => {
    stamp("real-child-spawned", { pid: Number(child.pid) });
    return new Proxy(child, { get(target, key) {
      if (key === "exitCode") return target.exitCode.pipe(Effect.tap((code) => Effect.sync(() => stamp("child-exit-observed", { pid: Number(child.pid), exitCode: Number(code) }))));
      return Reflect.get(target, key);
    } });
  })));
})).pipe(Layer.provide(NodeServices.layer));
const deps = Layer.mergeAll(
  ManagedPolicy.layerTest(),
  Layer.succeed(HttpClient.HttpClient, HttpClient.make(() => Effect.die("unused synthetic HTTP client"))),
  Layer.succeed(RelayClient.RelayClient, RelayClient.RelayClient.of({
    resolve: Effect.succeed({ status: "available", executablePath: process.env.STUB!, source: "path", version: RelayClient.CLOUDFLARED_VERSION }),
    install: Effect.die("unused"), installWithProgress: () => Effect.die("unused"), pruneManagedVersions: Effect.void,
  })),
  Layer.mock(ServerSecretStore.ServerSecretStore)({ get: () => Effect.succeedNone, set: () => Effect.void }),
  ServerConfig.layerTest(wt, baseDir),
).pipe(Layer.provideMerge(Layer.mergeAll(NodeServices.layer, NodeCrypto.layer)));
let recoveryCount = 0;
const observeRecovery = (runtime, scope, phase: string) => Stream.runForEach(runtime.recoveryRequests, (value) => Effect.sync(() => {
  recoveryCount++;
  stamp("recovery-request", { phase, count: recoveryCount, config: value });
})).pipe(Effect.forkIn(scope));
const build = (scope, phase: string) => Effect.gen(function* () {
  const ctx = yield* Layer.buildWithScope(ManagedEndpointRuntime.layer.pipe(Layer.provide(observedSpawner), Layer.provide(deps)), scope);
  const runtime = yield* Effect.service(ManagedEndpointRuntime.CloudManagedEndpointRuntime).pipe(Effect.provide(ctx));
  listeners(`${phase}:after-build`);
  yield* observeRecovery(runtime, scope, phase);
  const status = yield* runtime.applyConfig(config);
  stamp("connector-applied", { phase, status });
  return runtime;
});
const slowMs = Number(process.env.SLOW_MS ?? "1000");
const ordinary = Effect.gen(function* () {
  const scope = yield* Effect.scope;
  const runtime = yield* build(scope, "ordinary");
  yield* Effect.addFinalizer(() => Effect.gen(function* () {
    stamp("predecessor-finalizer-start", { delayMs: slowMs, recoveryCount });
    yield* Effect.sleep(slowMs);
    stamp("predecessor-finalizer-end-relay-next", { recoveryCount });
  }));
  yield* Effect.addFinalizer(() => Effect.sync(() => stamp("shutdown-started", { recoveryCount })));
  if (row === "B2") {
    const status = yield* runtime.inspectConnector;
    const checking = Deferred.makeUnsafe();
    yield* runtime.restartConnector(status!.pid, Effect.gen(function* () {
      stamp("reconcile-lock-held", { pid: status!.pid });
      yield* Deferred.succeed(checking, undefined);
      yield* Deferred.await(releaseLock);
      stamp("stillDead-false-releasing-lock", { pid: status!.pid });
      return false;
    })).pipe(Effect.tap((result) => Effect.sync(() => stamp("lock-probe-complete", { result }))), Effect.forkIn(scope));
    yield* Deferred.await(checking);
  }
  stamp("driver-ready", { row });
  return yield* Effect.never;
}).pipe(Effect.scoped);
const lifecycle = Effect.gen(function* () {
  for (const phase of ["C2-no-signal", "C3-signalled", "C3-fresh"] as const) {
    const scope = yield* Scope.make();
    const runtime = yield* build(scope, phase);
    stamp("phase-ready", { phase });
    yield* waitCommand(`close:${phase}`);
    const started = Date.now();
    stamp("explicit-scope-close-start", { phase, signals, recoveryCount });
    yield* Scope.close(scope, Exit.void);
    stamp("explicit-scope-close-end", { phase, stopMs: Date.now() - started, signals, recoveryCount });
    listeners(`${phase}:after-close`);
    stamp("phase-closed", { phase });
  }
  stamp("lifecycle-complete", { recoveryCount, signals });
});
const env = { PATH: process.env.PATH!, STUB_LOG: process.env.STUB_LOG!, STUB_JS: process.env.STUB_JS!, STUB_STATE: process.env.STUB_STATE! };
const provide = (effect) => effect.pipe(
  Effect.provideService(HostProcess.Environment, env),
  Effect.provide(ConfigProvider.layer(ConfigProvider.fromEnv({ env: {} }))),
);
process.on("exit", (code) => {
  stopStubForwarding(); stopRelayForwarding();
  stamp("process-exit", { code, recoveryCount, signals });
});
if (row === "C2-C3") {
  try { await Effect.runPromise(provide(lifecycle)); } finally { input.close(); }
} else {
  NodeRuntime.runMain(provide(ordinary));
}
