import type { PluginInfo } from "./utils";

const corsHeaders = {
  "Access-Control-Allow-Origin": "*",
  "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS",
  "Access-Control-Allow-Headers": "X-Requested-With, content-type, Authorization",
};

const applyCorsHeaders = (res: any) => {
  Object.entries(corsHeaders).forEach(([key, value]) => {
    res.setHeader(key, value);
  });
};

const normalizePrefix = (prefix?: string): string => {
  if (!prefix) return "";
  const cleaned = prefix.replace(/^\/+|\/+$/g, "");
  return cleaned ? `/${cleaned}` : "";
};

export function setupPluginMiddleware(
  devServer: any,
  pluginInfo: PluginInfo,
  devConfig: any,
  port: number,
) {
  const rpcPrefix = normalizePrefix(devConfig?.prefix);
  const handlers: { rpc: any; api: any } = { rpc: null, api: null };
  let cleanup: (() => Promise<void>) | null = null;

  const performCleanup = async () => {
    if (cleanup) {
      await cleanup();
      cleanup = null;
    }
  };

  (async () => {
    await performCleanup();

    try {
      const { createPluginRuntime } = await import("every-plugin");
      const { RPCHandler } = await import("@orpc/server/fetch");
      const { OpenAPIHandler } = await import("@orpc/openapi/fetch");
      const { OpenAPIReferencePlugin } = await import("@orpc/openapi/plugins");
      const { ZodToJsonSchemaConverter } = await import("@orpc/zod/zod4");
      const { onError } = await import("every-plugin/orpc");
      const { formatORPCError } = await import("every-plugin/errors");

      const pluginId = devConfig?.pluginId || pluginInfo.normalizedName;

      const runtime = createPluginRuntime({
        registry: {
          [pluginId]: {
            remote: `http://localhost:${port}/remoteEntry.js`,
          },
        },
      });

      const defaultConfig = { variables: {}, secrets: {} };

      // @ts-expect-error we don't know the plugin id
      const loaded = await runtime.usePlugin(pluginId, (devConfig?.config ?? defaultConfig) as any);

      cleanup = async () => {
        if (runtime) await runtime.shutdown();
        handlers.rpc = null;
        handlers.api = null;
        if (devServer.app.locals.handlers) {
          devServer.app.locals.handlers = null;
        }
      };

      handlers.rpc = new RPCHandler(loaded.router, {
        interceptors: [
          onError((error: any) => {
            formatORPCError(error);
          }),
        ],
      });

      handlers.api = new OpenAPIHandler(loaded.router, {
        plugins: [
          new OpenAPIReferencePlugin({
            schemaConverters: [new ZodToJsonSchemaConverter()],
          }),
        ],
        interceptors: [
          onError((error: any) => {
            formatORPCError(error);
          }),
        ],
      });

      console.log(`╭─────────────────────────────────────────────`);
      console.log(`│  ✅ Plugin dev server ready: `);
      console.log(`├─────────────────────────────────────────────`);
      console.log(`│  📡 RPC:    http://localhost:${port}/api/rpc${rpcPrefix}`);
      console.log(`│  📖 Docs:   http://localhost:${port}/api`);
      console.log(`│  💚 Health: http://localhost:${port}/`);
      console.log(`╰─────────────────────────────────────────────`);

      devServer.app.locals.handlers = handlers;

      if (devServer.server) {
        devServer.server.once("close", async () => {
          await performCleanup();
        });
      }
    } catch (error) {
      console.error("❌ Failed to load plugin:", error);
      await performCleanup();
    }
  })().catch((err) => {
    console.error("❌ Plugin dev server fatal error:", err);
  });

  process.once("SIGINT", async () => {
    const timeout = setTimeout(() => process.exit(0), 3000);
    await performCleanup();
    clearTimeout(timeout);
  });
  process.once("SIGTERM", async () => {
    const timeout = setTimeout(() => process.exit(0), 3000);
    await performCleanup();
    clearTimeout(timeout);
  });

  devServer.app.options("*", (_req: any, res: any) => {
    applyCorsHeaders(res);
    res.status(200).end();
  });

  devServer.app.get("/", (_req: any, res: any) => {
    applyCorsHeaders(res);
    res.json({
      ok: true,
      plugin: pluginInfo.normalizedName,
      version: pluginInfo.version,
      status: devServer.app.locals.handlers?.rpc ? "ready" : "loading",
      endpoints: {
        health: "/",
        docs: "/api",
        rpc: `/api/rpc${rpcPrefix}`,
      },
    });
  });

  devServer.app.get("/health", (_req: any, res: any) => {
    applyCorsHeaders(res);
    res.status(200).send("OK");
  });

  const buildDevContext = (_req: any, webRequest: Request) => {
    const rawClone =
      webRequest.method === "GET" || webRequest.method === "HEAD" ? null : webRequest.clone();
    let cachedRawBody: string | null = null;
    return {
      reqHeaders: webRequest.headers,
      getRawBody: async (): Promise<string> => {
        if (cachedRawBody !== null) return cachedRawBody;
        if (!rawClone) {
          cachedRawBody = "";
          return cachedRawBody;
        }
        cachedRawBody = await rawClone.text();
        return cachedRawBody;
      },
    };
  };

  const handleApiRequest = async (req: any, res: any) => {
    applyCorsHeaders(res);
    const apiHandler = devServer.app.locals.handlers?.api;
    if (!apiHandler) {
      return res.status(503).json({ error: "Plugin still loading..." });
    }

    try {
      const url = `http://${req.headers.host}${req.url}`;
      const webRequest = new Request(url, {
        method: req.method,
        headers: req.headers,
        body: req.method !== "GET" && req.method !== "HEAD" ? req : undefined,
        duplex: req.method !== "GET" && req.method !== "HEAD" ? "half" : undefined,
      } as RequestInit);

      const result = await apiHandler.handle(webRequest, {
        prefix: "/api",
        context: buildDevContext(req, webRequest),
      });

      if (result.response) {
        res.status(result.response.status);
        result.response.headers.forEach((value: string, key: string) => {
          res.setHeader(key, value);
        });
        const text = await result.response.text();
        res.send(text);
      } else {
        res.status(404).send("Not Found");
      }
    } catch (error) {
      console.error("OpenAPI error:", error);
      res.status(500).json({ error: (error as Error).message });
    }
  };

  devServer.app.all(`/api/rpc${rpcPrefix}/*`, async (req: any, res: any) => {
    applyCorsHeaders(res);
    const rpcHandler = devServer.app.locals.handlers?.rpc;
    if (!rpcHandler) {
      return res.status(503).json({ error: "Plugin still loading..." });
    }

    try {
      const url = `http://${req.headers.host}${req.url}`;
      const webRequest = new Request(url, {
        method: req.method,
        headers: req.headers,
        body: req.method !== "GET" && req.method !== "HEAD" ? req : undefined,
        duplex: req.method !== "GET" && req.method !== "HEAD" ? "half" : undefined,
      } as RequestInit);

      const result = await rpcHandler.handle(webRequest, {
        prefix: `/api/rpc${rpcPrefix}`,
        context: buildDevContext(req, webRequest),
      });

      if (result.response) {
        res.status(result.response.status);
        result.response.headers.forEach((value: string, key: string) => {
          res.setHeader(key, value);
        });
        const text = await result.response.text();
        res.send(text);
      } else {
        res.status(404).send("Not Found");
      }
    } catch (error) {
      console.error("RPC error:", error);
      res.status(500).json({ error: (error as Error).message });
    }
  });

  devServer.app.all("/api", handleApiRequest);
  devServer.app.all("/api/*", handleApiRequest);
}
