import { HttpApiBuilder, HttpApiSwagger } from "effect/unstable/httpapi";
import { HttpRouter, HttpServer } from "effect/unstable/http";
import { Context, Effect, Layer, ManagedRuntime } from "effect";

import { observabilityMiddleware } from "@executor-js/api";
import {
  CoreHandlers,
  ExecutorService,
  ExecutionEngineService,
  composePluginApi,
  composePluginHandlers,
} from "@executor-js/api/server";
import { createExecutionEngine } from "@executor-js/execution";
import { makeQuickJsExecutor } from "@executor-js/runtime-quickjs";
import { getExecutorBundle } from "./executor";
import { createMcpRequestHandler, type McpRequestHandler } from "./mcp";
import { ErrorCaptureLive } from "./observability";

// ---------------------------------------------------------------------------
// Local server API.
//
// Every plugin contributes its `HttpApiGroup` and handler `Layer` through
// the spec (`routes()` / `handlers(self)` on `PluginSpec`); the host folds
// the group list into a single `HttpApi` and merges the handler layers
// into the runtime. The plugin set is the union of `executor.config.ts`
// (static, typed) and `executor.jsonc#plugins` (dynamic, jiti-loaded),
// so `LocalApi` can't be constructed until the executor bundle resolves
// — composition happens inside `createServerHandlers` instead of at
// module-eval time.
// ---------------------------------------------------------------------------

// ---------------------------------------------------------------------------
// Server handlers
// ---------------------------------------------------------------------------

export type ServerHandlers = {
  readonly api: {
    readonly handler: (request: Request) => Promise<Response>;
    readonly dispose: () => Promise<void>;
  };
  readonly mcp: McpRequestHandler;
};

const closeServerHandlers = async (handlers: ServerHandlers): Promise<void> => {
  await Effect.runPromise(
    Effect.all(
      [
        Effect.tryPromise({
          try: () => handlers.api.dispose(),
          catch: (cause) => cause,
        }).pipe(Effect.ignore),
        Effect.tryPromise({
          try: () => handlers.mcp.close(),
          catch: (cause) => cause,
        }).pipe(Effect.ignore),
      ],
      { concurrency: "unbounded" },
    ),
  );
};

export const createServerHandlers = async (): Promise<ServerHandlers> => {
  const { executor, plugins } = await getExecutorBundle();
  const engine = createExecutionEngine({ executor, codeExecutor: makeQuickJsExecutor() });

  const LocalApi = composePluginApi(plugins);
  // `ErrorCaptureLive` logs causes to the console and returns a short
  // correlation id. Provided above the handler + middleware layers so
  // both the `withCapture` typed-channel translation AND the
  // `observabilityMiddleware` defect catchall see the same
  // implementation.
  const LocalObservability = observabilityMiddleware(LocalApi);
  const LocalApiBase = HttpApiBuilder.layer(LocalApi).pipe(
    Layer.provide(CoreHandlers),
    Layer.provide(LocalObservability),
    Layer.provide(ErrorCaptureLive),
  );

  // Spec-based plugin handlers — each plugin's `handlers(self)` Layer is
  // built against its own bundled HttpApi for full type safety inside the
  // plugin, and merges into the runtime `LocalApi` by group identity.
  // Each plugin's handler bodies that yield its `*ExtensionService` are
  // satisfied because `composePluginHandlers` provides `executor[id]` to
  // the plugin's own `Layer.succeed(*ExtensionService)(self)` wiring.
  const SpecPluginHandlers = composePluginHandlers(plugins, executor);

  const localApiLayer = LocalApiBase.pipe(
    Layer.provideMerge(HttpApiSwagger.layer(LocalApi, { path: "/docs" })),
    Layer.provideMerge(SpecPluginHandlers),
    Layer.provideMerge(Layer.succeed(ExecutorService)(executor)),
    Layer.provideMerge(Layer.succeed(ExecutionEngineService)(engine)),
    Layer.provideMerge(HttpServer.layerServices),
    Layer.provideMerge(Layer.succeed(HttpRouter.RouterConfig)({ maxParamLength: 1000 })),
  );
  const api = HttpRouter.toWebHandler(localApiLayer);
  const apiHandler: ServerHandlers["api"] = {
    handler: (request) => api.handler(request),
    dispose: api.dispose,
  };

  const mcp = createMcpRequestHandler({ engine });

  return { api: apiHandler, mcp };
};

export class ServerHandlersService extends Context.Service<ServerHandlersService, ServerHandlers>()(
  "@executor-js/local/ServerHandlersService",
) {}

const ServerHandlersLive = Layer.effect(ServerHandlersService)(
  Effect.acquireRelease(
    Effect.promise(() => createServerHandlers()),
    (handlers) => Effect.promise(() => closeServerHandlers(handlers)),
  ),
);

const serverHandlersRuntime = ManagedRuntime.make(ServerHandlersLive);

export const getServerHandlers = (): Promise<ServerHandlers> =>
  serverHandlersRuntime.runPromise(ServerHandlersService.asEffect());

export const disposeServerHandlers = async (): Promise<void> => {
  await Effect.runPromise(
    Effect.tryPromise({
      try: () => serverHandlersRuntime.dispose(),
      catch: (cause) => cause,
    }).pipe(Effect.ignore),
  );
};
