import type { FastifyInstance } from 'fastify';
import WebSocket from 'ws';

function boocoderWsUrl(origin: string, path: string): string {
  const u = new URL(origin);
  u.protocol = u.protocol === 'https:' ? 'wss:' : 'ws:';
  u.pathname = path;
  u.search = '';
  return u.toString();
}

/**
 * Reverse-proxy BooCoder HTTP + WebSocket through BooChat's single origin.
 * WS must be registered before the HTTP catch-all — fetch() cannot upgrade.
 *
 * Keep-in-sync: routes/control-proxy.ts mirrors this pattern (deliberate
 * clone, Rule of Three unmet). Proxy-layer changes go in BOTH files.
 */
export function registerCoderProxy(app: FastifyInstance, boocoderOrigin: string): void {
  app.get<{ Params: { sessionId: string } }>(
    '/api/coder/ws/sessions/:sessionId',
    { websocket: true },
    (clientSocket, req) => {
      const sessionId = req.params.sessionId;
      const target = boocoderWsUrl(boocoderOrigin, `/api/ws/sessions/${sessionId}`);
      const upstream = new WebSocket(target);

      upstream.on('open', () => {
        app.log.debug({ sessionId }, 'coder ws proxy: upstream connected');
      });

      upstream.on('message', (data, isBinary) => {
        if (clientSocket.readyState !== clientSocket.OPEN) return;
        clientSocket.send(data, { binary: isBinary });
      });

      upstream.on('close', (code, reason) => {
        if (clientSocket.readyState === clientSocket.OPEN) {
          clientSocket.close(code, reason.toString());
        }
      });

      upstream.on('error', (err) => {
        app.log.warn({ err, sessionId, target }, 'coder ws proxy: upstream error');
        if (clientSocket.readyState === clientSocket.OPEN) {
          clientSocket.close(1011, 'upstream error');
        }
      });

      clientSocket.on('message', (data, isBinary) => {
        if (upstream.readyState !== WebSocket.OPEN) return;
        upstream.send(data, { binary: isBinary });
      });

      clientSocket.on('close', () => {
        if (upstream.readyState === WebSocket.OPEN || upstream.readyState === WebSocket.CONNECTING) {
          upstream.close();
        }
      });

      clientSocket.on('error', () => {
        if (upstream.readyState === WebSocket.OPEN || upstream.readyState === WebSocket.CONNECTING) {
          upstream.close();
        }
      });
    },
  );

  app.all('/api/coder/*', async (req, reply) => {
    const targetPath = req.url.replace('/api/coder', '/api');
    const targetUrl = `${boocoderOrigin}${targetPath}`;
    const headers: Record<string, string> = {};
    if (req.headers['content-type']) headers['content-type'] = req.headers['content-type'] as string;
    if (req.headers['authorization']) headers['authorization'] = req.headers['authorization'] as string;

    try {
      const res = await fetch(targetUrl, {
        method: req.method as string,
        headers,
        body: req.method !== 'GET' && req.method !== 'HEAD' ? JSON.stringify(req.body) : undefined,
      });
      reply.code(res.status);
      for (const [key, value] of res.headers) {
        if (key === 'transfer-encoding') continue;
        reply.header(key, value);
      }
      const body = await res.text();
      return reply.send(body);
    } catch (err) {
      app.log.error({ err, targetUrl }, 'coder proxy error');
      reply.code(502).send({ error: 'boocoder backend unavailable' });
    }
  });
}
