/**
 * B6 — Tool egress logging.
 *
 * wrapFetch(toolName, agentId, fetchFn) returns a wrapped fetch-compatible
 * function. Every call records a row in tool_egress with:
 *   - host (parsed from URL)
 *   - method
 *   - status_code (null on network error)
 *   - bytes_out (request body byte length)
 *   - bytes_in (response body byte length after buffering)
 *   - duration_ms
 *   - error (message if the call threw)
 *
 * IMPORTANT: egress logging is observation-only. A failed DB write is logged
 * as a warning but never propagates to the caller. The wrapped fetch always
 * returns whatever the underlying fetch returned (or re-throws if it threw).
 *
 * No request/response bodies are stored — only byte counts.
 */

import { getDb, nowIso, newId } from './db.js';

/**
 * Parse the host from a URL string.
 * Returns the raw URL (capped at 256 chars) if parsing fails.
 */
function parseHost(url) {
  try {
    return new URL(String(url)).host;
  } catch {
    return String(url).slice(0, 256);
  }
}

/**
 * Measure the byte length of a fetch init body without consuming it.
 * Handles string, Buffer/Uint8Array, ArrayBuffer, URLSearchParams, null/undefined.
 * Returns 0 for anything else (ReadableStream, FormData — not measurable without reading).
 */
function measureBodyBytes(body) {
  if (body == null) return 0;
  if (typeof body === 'string') return Buffer.byteLength(body, 'utf8');
  if (Buffer.isBuffer(body)) return body.byteLength;
  if (body instanceof Uint8Array) return body.byteLength;
  if (body instanceof ArrayBuffer) return body.byteLength;
  if (body instanceof URLSearchParams) return Buffer.byteLength(body.toString(), 'utf8');
  return 0;
}

/**
 * Write a row to tool_egress. Never throws — logs a warning on failure.
 */
function insertEgressRow({ id, tool_name, agent_id, host, method, status_code, bytes_out, bytes_in, duration_ms, error, at }) {
  try {
    const db = getDb();
    db.prepare(`
      INSERT INTO tool_egress
        (id, tool_name, agent_id, host, method, status_code, bytes_out, bytes_in, duration_ms, error, at)
      VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
    `).run(
      id,
      tool_name,
      agent_id ?? null,
      host,
      method,
      status_code ?? null,
      bytes_out ?? 0,
      bytes_in ?? 0,
      duration_ms ?? null,
      error ?? null,
      at,
    );
  } catch (dbErr) {
    // Observation-only: never block the caller.
    console.warn('[egress] Failed to insert tool_egress row:', dbErr?.message || dbErr);
  }
}

/**
 * Wrap a fetch-compatible function to record egress metadata.
 *
 * @param {string} toolName   - Name of the marketplace tool (e.g. 'web_fetch')
 * @param {string|null} agentId - Agent ID, or null for the main agent
 * @param {Function} fetchFn  - The actual fetch function to wrap (default: global fetch)
 * @returns {Function} A drop-in fetch replacement that also logs to tool_egress
 */
export function wrapFetch(toolName, agentId, fetchFn = fetch) {
  return async function wrappedFetch(url, init = {}) {
    const host = parseHost(url);
    const method = String(init?.method || 'GET').toUpperCase();
    const bytesOut = measureBodyBytes(init?.body);
    const startMs = performance.now();
    const at = nowIso();
    const id = newId('egr');

    let response;
    let bytesIn = 0;
    let statusCode = null;
    let errorMsg = null;

    try {
      response = await fetchFn(url, init);
      statusCode = response.status;

      // Buffer the body so we can measure its size, then return a Response
      // with a fresh body from the buffered bytes so the caller can still read it.
      const buffer = await response.arrayBuffer();
      bytesIn = buffer.byteLength;

      const durationMs = Math.round(performance.now() - startMs);
      insertEgressRow({ id, tool_name: toolName, agent_id: agentId, host, method, status_code: statusCode, bytes_out: bytesOut, bytes_in: bytesIn, duration_ms: durationMs, error: null, at });

      // Reconstruct a Response so the caller can still call .json(), .text(), etc.
      return new Response(buffer, {
        status: response.status,
        statusText: response.statusText,
        headers: response.headers,
      });
    } catch (err) {
      const durationMs = Math.round(performance.now() - startMs);
      errorMsg = String(err?.message || err).slice(0, 512);
      insertEgressRow({ id, tool_name: toolName, agent_id: agentId, host, method, status_code: statusCode, bytes_out: bytesOut, bytes_in: bytesIn, duration_ms: durationMs, error: errorMsg, at });
      throw err;
    }
  };
}
