/**
 * MCP Proxy — bridges stdio (JSON-RPC) ↔ HTTP Streamable transport.
 *
 * Reads newline-delimited JSON-RPC from stdin, POSTs to the MCP server with
 * a Bearer token, and writes the JSON response to stdout.
 */

import { createInterface } from 'node:readline';

const log = (msg) => process.stderr.write(`[mcp-proxy] ${msg}\n`);

export class McpProxy {
  #mcpUrl;
  #tokenManager;

  /**
   * @param {object} opts
   * @param {string} opts.mcpUrl — MCP server endpoint (e.g. "https://mcp.ccmqa.com/mcp")
   * @param {import('./token-manager.js').TokenManager} opts.tokenManager
   */
  constructor({ mcpUrl, tokenManager }) {
    this.#mcpUrl = mcpUrl;
    this.#tokenManager = tokenManager;
  }

  /** Starts reading from stdin and forwarding to the MCP server. */
  start() {
    const rl = createInterface({ input: process.stdin });
    let pending = 0;
    let stdinClosed = false;

    const maybeExit = () => {
      if (stdinClosed && pending === 0) {
        log('All requests complete, shutting down');
        process.exit(0);
      }
    };

    rl.on('line', async (line) => {
      const trimmed = line.trim();
      if (!trimmed) return;

      try {
        JSON.parse(trimmed); // validate JSON
      } catch {
        log(`Ignoring non-JSON line: ${trimmed.substring(0, 80)}`);
        return;
      }

      pending++;
      try {
        await this.#forward(trimmed);
      } catch (err) {
        log(`Error forwarding request: ${err.message}`);
        this.#writeError(trimmed, err.message);
      } finally {
        pending--;
        maybeExit();
      }
    });

    rl.on('close', () => {
      stdinClosed = true;
      log('stdin closed');
      maybeExit();
    });
  }

  /**
   * Forwards a JSON-RPC message to the MCP server.
   * Retries once on 401 after forcing a token refresh.
   */
  async #forward(jsonBody, isRetry = false) {
    const token = await this.#tokenManager.getToken();

    const controller = new AbortController();
    const timeout = setTimeout(() => controller.abort(), 300_000);
    let res;
    try {
      res = await fetch(this.#mcpUrl, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Accept': 'application/json, text/event-stream',
          'Authorization': `Bearer ${token}`,
        },
        body: jsonBody,
        signal: controller.signal,
      });
    } catch (err) {
      throw new Error(err.name === 'AbortError' ? 'MCP request timed out after 5m' : `MCP request failed: ${err.message}`);
    } finally {
      clearTimeout(timeout);
    }

    if (res.status === 401 && !isRetry) {
      log('Received 401 — refreshing token and retrying...');
      await this.#tokenManager.forceRefresh();
      return this.#forward(jsonBody, true);
    }

    if (!res.ok) {
      const text = await res.text().catch(() => '');
      throw new Error(`HTTP ${res.status}: ${text.substring(0, 200)}`);
    }

    const contentType = res.headers.get('content-type') || '';

    if (contentType.includes('text/event-stream')) {
      await this.#handleSse(res);
    } else {
      // Stateless JSON response — validate and write to stdout
      const body = await res.text();
      const trimmed = body.trim();
      if (trimmed) {
        try {
          JSON.parse(trimmed);
          process.stdout.write(trimmed + '\n');
        } catch {
          throw new Error(`MCP server returned non-JSON response: ${trimmed.substring(0, 100)}`);
        }
      }
    }
  }

  /** Handles SSE responses (in case the server switches to streaming in the future). */
  async #handleSse(res) {
    const text = await res.text();
    for (const line of text.split('\n')) {
      if (line.startsWith('data:')) {
        const data = line.slice(5).trim();
        if (data) {
          process.stdout.write(data + '\n');
        }
      }
    }
  }

  /** Writes a JSON-RPC error response to stdout for a failed request. */
  #writeError(requestJson, message) {
    try {
      const request = JSON.parse(requestJson);
      if (request.id != null) {
        const errorResponse = {
          jsonrpc: '2.0',
          id: request.id,
          error: { code: -32000, message: `Proxy error: ${message}` },
        };
        process.stdout.write(JSON.stringify(errorResponse) + '\n');
      }
    } catch {
      // If we can't parse the original request, we can't send a proper error response
    }
  }
}
