import { randomUUID } from "node:crypto";

export type ApiHandler = (request: Request) => Response | Promise<Response>;
export type ReadinessResult = { readonly ready: true } | { readonly ready: false; readonly reason?: string };
export type ReadinessCheck = () => ReadinessResult | Promise<ReadinessResult>;

export interface ApiFeatureRegistrar {
  route(method: string, path: `/${string}`, handler: ApiHandler): void;
  readiness(check: ReadinessCheck): void;
}

export interface ApiFeature {
  readonly name: string;
  register(registrar: ApiFeatureRegistrar): void;
}

export interface SafeLogEvent {
  readonly level: "error";
  readonly event: "request.failed";
  readonly requestId: string;
  readonly method: string;
  readonly pathname: string;
  readonly errorName: string;
}

export type SafeLogger = (event: SafeLogEvent) => void;

export interface ApiCompositionOptions {
  readonly features?: readonly ApiFeature[];
  readonly logger?: SafeLogger;
  readonly requestId?: () => string;
}

export interface ApiComposition {
  readonly fetch: ApiHandler;
  readonly featureNames: readonly string[];
}

const RESERVED_PATHS = new Set(["/health/live", "/health/ready"]);
const jsonHeaders = { "content-type": "application/json; charset=utf-8" } as const;

function json(status: number, body: unknown): Response {
  return Response.json(body, { status, headers: jsonHeaders });
}

function routeKey(method: string, path: string): string {
  return `${method.toUpperCase()} ${path}`;
}

function validatePath(path: string): asserts path is `/${string}` {
  if (!path.startsWith("/") || path.includes("?") || path.includes("#")) {
    throw new TypeError(`Feature route must be an absolute pathname: ${path}`);
  }
}

export function createApiComposition(options: ApiCompositionOptions = {}): ApiComposition {
  const routes = new Map<string, ApiHandler>();
  const checks: Array<{ readonly feature: string; readonly check: ReadinessCheck }> = [];
  const featureNames = new Set<string>();

  for (const feature of options.features ?? []) {
    if (!/^[a-z][a-z0-9-]*$/u.test(feature.name)) {
      throw new TypeError(`Invalid feature name: ${feature.name}`);
    }
    if (featureNames.has(feature.name)) throw new TypeError(`Duplicate feature name: ${feature.name}`);
    featureNames.add(feature.name);

    feature.register({
      route(method, path, handler) {
        validatePath(path);
        const normalizedMethod = method.toUpperCase();
        if (!/^[A-Z]+$/u.test(normalizedMethod)) throw new TypeError(`Invalid HTTP method: ${method}`);
        if (RESERVED_PATHS.has(path)) throw new TypeError(`Feature cannot replace reserved route: ${path}`);
        const key = routeKey(normalizedMethod, path);
        if (routes.has(key)) throw new TypeError(`Duplicate API route: ${key}`);
        routes.set(key, handler);
      },
      readiness(check) {
        checks.push({ feature: feature.name, check });
      },
    });
  }

  const logger = options.logger ?? ((event) => console.error(JSON.stringify(event)));
  const nextRequestId = options.requestId ?? randomUUID;

  return Object.freeze({
    featureNames: Object.freeze([...featureNames]),
    async fetch(request: Request): Promise<Response> {
      const url = new URL(request.url);
      const requestId = nextRequestId();
      try {
        if (request.method === "GET" && url.pathname === "/health/live") {
          return json(200, { status: "alive" });
        }
        if (request.method === "GET" && url.pathname === "/health/ready") {
          const results = await Promise.all(
            checks.map(async ({ feature, check }) => {
              try {
                return { feature, ...(await check()) };
              } catch {
                return { feature, ready: false as const };
              }
            }),
          );
          const ready = results.every((result) => result.ready);
          return json(ready ? 200 : 503, {
            status: ready ? "ready" : "not_ready",
            checks: results.map(({ feature, ready: checkReady }) => ({ feature, ready: checkReady })),
          });
        }

        const handler = routes.get(routeKey(request.method, url.pathname));
        if (handler === undefined) return json(404, { code: "NOT_FOUND", requestId });
        return await handler(request);
      } catch (error) {
        logger({
          level: "error",
          event: "request.failed",
          requestId,
          method: request.method,
          pathname: url.pathname,
          errorName: error instanceof Error ? error.name : "UnknownError",
        });
        return json(500, { code: "INTERNAL_ERROR", requestId });
      }
    },
  });
}
