#!/usr/bin/env node
'use strict';

const http = require('node:http');
const readline = require('node:readline');

let localBrain = null;
let localHandleMcpMessage = null;

const PROTOCOL_VERSION = '2025-03-26';

const DEGRADED_TOOLS = Object.freeze([
  {
    name: 'walle_memory_status',
    description: 'Report Wall-E MCP daemon availability. In degraded stdio startup mode this returns why the daemon is not ready instead of failing MCP initialization.',
    inputSchema: { type: 'object', properties: {} },
    annotations: {
      title: 'Walle Memory Status',
      readOnlyHint: true,
      destructiveHint: false,
      idempotentHint: true,
      openWorldHint: false,
    },
    outputSchema: { type: 'object', additionalProperties: true },
  },
]);

function log(message) {
  process.stderr.write(`[wall-e-mcp-stdio] ${message}\n`);
}

function writeMessage(message) {
  process.stdout.write(`${JSON.stringify(message)}\n`);
}

function _wallePort() {
  const raw = Number(process.env.WALLE_PORT || process.env.PORT || 3457);
  return Number.isFinite(raw) && raw > 0 ? raw : 3457;
}

function _ctmPort() {
  const direct = Number(process.env.CTM_PORT || '');
  if (Number.isFinite(direct) && direct > 0) return direct;
  const inferred = _wallePort() - 1;
  return Number.isFinite(inferred) && inferred > 0 ? inferred : 3456;
}

function _selfHealEnabled() {
  if (/^(1|true|yes|on)$/i.test(String(process.env.WALLE_DISABLED || process.env.WALLE_SUPERVISOR_DISABLED || '').trim())) {
    return false;
  }
  return !/^(0|false|no|off)$/i.test(String(process.env.WALLE_MCP_SELF_HEAL || '').trim());
}

function _embeddedDbFallbackEnabled() {
  return /^(1|true|yes|on)$/i.test(String(
    process.env.WALLE_MCP_EMBEDDED_DB
    || process.env.WALLE_MCP_DIRECT_DB
    || ''
  ).trim());
}

function _jsonRpcError(id, code, message) {
  return {
    jsonrpc: '2.0',
    id: id === undefined ? null : id,
    error: { code, message },
  };
}

function _jsonRpcResult(id, result) {
  return {
    jsonrpc: '2.0',
    id: id === undefined ? null : id,
    result,
  };
}

function _extractId(message) {
  if (Array.isArray(message)) return null;
  return Object.prototype.hasOwnProperty.call(message || {}, 'id') ? message.id : null;
}

function _hasRequestId(message) {
  return !Array.isArray(message) && Object.prototype.hasOwnProperty.call(message || {}, 'id');
}

function _proxyUnavailableResponse(message, err) {
  const messageText = `Wall-E daemon MCP is unavailable and embedded DB fallback is disabled: ${err.message}`;
  if (Array.isArray(message)) {
    const responses = message
      .filter(_hasRequestId)
      .map(item => _jsonRpcError(item.id, -32098, messageText));
    return responses.length ? responses : null;
  }
  if (!_hasRequestId(message)) return null;
  return _jsonRpcError(_extractId(message), -32098, messageText);
}

function _degradedStatusPayload(err) {
  return {
    ok: false,
    mode: 'stdio-degraded',
    daemon_ready: false,
    code: 'walle_daemon_unavailable',
    error: String(err && err.message || err || 'Wall-E daemon is unavailable'),
    transport: {
      stdio: true,
      daemon_host: process.env.WALLE_HOST || '127.0.0.1',
      daemon_port: _wallePort(),
      ctm_host: process.env.WALLE_MCP_CTM_HOST || '127.0.0.1',
      ctm_port: _ctmPort(),
      embedded_db_fallback: false,
    },
    guidance: 'The Wall-E MCP stdio transport initialized in degraded mode so the coding client keeps the MCP server mounted. Start or repair the Wall-E daemon, then retry the tool call.',
  };
}

function _degradedResponseOne(message, err) {
  if (!_hasRequestId(message)) return null;
  const id = _extractId(message);
  const method = message && message.method;
  if (method === 'initialize') {
    return _jsonRpcResult(id, {
      protocolVersion: PROTOCOL_VERSION,
      capabilities: { tools: {}, resources: {}, prompts: {} },
      serverInfo: { name: 'wall-e', version: '0.1.0', degraded: true },
    });
  }
  if (method === 'tools/list') {
    return _jsonRpcResult(id, { tools: DEGRADED_TOOLS });
  }
  if (method === 'resources/list') {
    return _jsonRpcResult(id, { resources: [] });
  }
  if (method === 'resources/templates/list') {
    return _jsonRpcResult(id, { resourceTemplates: [] });
  }
  if (method === 'prompts/list') {
    return _jsonRpcResult(id, { prompts: [] });
  }
  if (method === 'tools/call') {
    const toolName = message?.params?.name;
    const payload = _degradedStatusPayload(err);
    if (toolName === 'walle_memory_status') {
      return _jsonRpcResult(id, {
        content: [{ type: 'text', text: JSON.stringify(payload) }],
        structuredContent: payload,
      });
    }
    return _jsonRpcResult(id, {
      content: [{ type: 'text', text: `Wall-E daemon is not ready; cannot run ${toolName || 'requested tool'}: ${payload.error}` }],
      structuredContent: payload,
      isError: true,
    });
  }
  return null;
}

function _degradedResponse(message, err) {
  if (Array.isArray(message)) {
    const responses = message
      .map(item => _degradedResponseOne(item, err))
      .filter(Boolean);
    return responses.length ? responses : null;
  }
  return _degradedResponseOne(message, err);
}

function _sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

function _parseJson(raw) {
  try { return raw ? JSON.parse(raw) : null; } catch { return null; }
}

function _formatCtmStartFailure(statusCode, raw) {
  const parsed = _parseJson(raw);
  const code = parsed && (parsed.code || parsed.error?.code || parsed.walle?.code);
  const detail = parsed && (parsed.message || parsed.error || parsed.walle?.error);
  const prefix = `CTM returned HTTP ${statusCode}`;
  if (code && detail) return `${prefix} (${code}): ${detail}`;
  if (detail) return `${prefix}: ${detail}`;
  if (raw && raw.trim()) return `${prefix}: ${raw.slice(0, 500)}`;
  return prefix;
}

function proxyMcpMessage(message) {
  const body = JSON.stringify(message);
  const headers = {
    'Content-Type': 'application/json',
    'Content-Length': Buffer.byteLength(body),
    'MCP-Protocol-Version': process.env.WALLE_MCP_PROTOCOL_VERSION || '2025-06-18',
  };

  return new Promise((resolve, reject) => {
    const req = http.request({
      hostname: process.env.WALLE_HOST || '127.0.0.1',
      port: _wallePort(),
      path: '/mcp',
      method: 'POST',
      headers,
      timeout: Number(process.env.WALLE_MCP_PROXY_TIMEOUT_MS || 2500),
    }, (res) => {
      let raw = '';
      res.setEncoding('utf8');
      res.on('data', chunk => { raw += chunk; });
      res.on('end', () => {
        if (res.statusCode === 202 && !raw.trim()) {
          resolve(null);
          return;
        }
        if (res.statusCode < 200 || res.statusCode >= 300) {
          reject(new Error(`Wall-E daemon MCP returned HTTP ${res.statusCode}: ${raw.slice(0, 300)}`));
          return;
        }
        try {
          resolve(raw.trim() ? JSON.parse(raw) : null);
        } catch (err) {
          reject(new Error(`Wall-E daemon MCP returned invalid JSON: ${err.message}`));
        }
      });
    });
    req.on('timeout', () => {
      req.destroy(new Error('Wall-E daemon MCP request timed out'));
    });
    req.on('error', reject);
    req.end(body);
  });
}

function postCtmStartWalle() {
  const body = '{}';
  return new Promise((resolve, reject) => {
    const req = http.request({
      hostname: process.env.WALLE_MCP_CTM_HOST || '127.0.0.1',
      port: _ctmPort(),
      path: '/api/start/walle',
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Content-Length': Buffer.byteLength(body),
      },
      timeout: Number(process.env.WALLE_MCP_CTM_START_TIMEOUT_MS || 20000),
    }, (res) => {
      let raw = '';
      res.setEncoding('utf8');
      res.on('data', chunk => { raw += chunk; });
      res.on('end', () => {
        if ((res.statusCode >= 200 && res.statusCode < 300) || res.statusCode === 409) resolve(true);
        else reject(new Error(_formatCtmStartFailure(res.statusCode, raw)));
      });
    });
    req.on('timeout', () => req.destroy(new Error('CTM start request timed out')));
    req.on('error', reject);
    req.end(body);
  });
}

function shouldTrySelfHeal(err) {
  const msg = String(err && err.message || err || '');
  return /ECONNREFUSED|ECONNRESET|EPIPE|socket hang up|timed out/i.test(msg);
}

async function proxyWithSelfHeal(message, firstErr) {
  if (!_selfHealEnabled() || !shouldTrySelfHeal(firstErr)) throw firstErr;

  const timeoutMs = Number(process.env.WALLE_MCP_SELF_HEAL_TIMEOUT_MS || 20000);
  const retryMs = Number(process.env.WALLE_MCP_SELF_HEAL_RETRY_MS || 200);
  const deadline = Date.now() + Math.max(0, timeoutMs);
  const startPort = _ctmPort();
  log(`daemon proxy failed (${firstErr.message}); asking CTM on ${startPort} to start Wall-E`);

  try {
    await postCtmStartWalle();
  } catch (err) {
    log(`CTM Wall-E start request failed: ${err.message}`);
    throw new Error(`CTM could not start Wall-E: ${err.message}`);
  }

  let lastErr = firstErr;
  let firstRetry = true;
  while (firstRetry || Date.now() <= deadline) {
    if (!firstRetry) await _sleep(Math.max(25, retryMs));
    firstRetry = false;
    try {
      const result = await proxyMcpMessage(message);
      log('daemon proxy recovered after CTM start');
      return result;
    } catch (err) {
      lastErr = err;
      if (!shouldTrySelfHeal(err)) throw err;
    }
  }
  log(`daemon proxy still unavailable after CTM start: ${lastErr.message}`);
  throw new Error(`Wall-E daemon still unavailable after CTM start: ${lastErr.message}`);
}

function initializeLocalBrain() {
  if (localHandleMcpMessage) return;
  try {
    process.env.WALL_E_PROCESS_ROLE = process.env.WALL_E_PROCESS_ROLE || 'walle-mcp-stdio';
    localBrain = require('../brain');
    localBrain.initDb();
    localBrain.setDaemonOwned();
    localHandleMcpMessage = require('../mcp-server').handleMcpMessage;
    log('using embedded DB fallback; this should only be used for standalone repair or tests');
  } catch (err) {
    log(`failed to initialize embedded brain: ${err.message}`);
    throw err;
  }
}

async function handleLocal(message) {
  initializeLocalBrain();
  if (Array.isArray(message)) {
    const responses = [];
    for (const item of message) {
      const result = await localHandleMcpMessage(item, { transport: 'stdio-embedded' });
      if (result.response) responses.push(result.response);
    }
    return responses.length ? responses : null;
  }
  const result = await localHandleMcpMessage(message, { transport: 'stdio-embedded' });
  return result.response || null;
}

async function handleOne(message) {
  try {
    const proxied = await proxyMcpMessage(message);
    if (proxied) writeMessage(proxied);
    return;
  } catch (err) {
    if (!_embeddedDbFallbackEnabled()) {
      try {
        const recovered = await proxyWithSelfHeal(message, err);
        if (recovered) writeMessage(recovered);
        return;
      } catch (healErr) {
        err = healErr;
      }
    }
    if (!_embeddedDbFallbackEnabled()) {
      const degraded = _degradedResponse(message, err);
      if (degraded) {
        if (Array.isArray(degraded)) log(`serving degraded MCP response after daemon startup failure: ${err.message}`);
        else log(`serving degraded MCP ${message?.method || 'request'} response after daemon startup failure: ${err.message}`);
        writeMessage(degraded);
        return;
      }
      const response = _proxyUnavailableResponse(message, err);
      if (response) writeMessage(response);
      return;
    }
    log(`daemon proxy failed; falling back to embedded DB: ${err.message}`);
  }

  const local = await handleLocal(message);
  if (local) writeMessage(local);
}

async function handleLine(line) {
  if (!line.trim()) return;
  let message;
  try {
    message = JSON.parse(line);
  } catch {
    writeMessage({
      jsonrpc: '2.0',
      id: null,
      error: { code: -32700, message: 'Parse error' },
    });
    return;
  }

  if (Array.isArray(message)) {
    await handleOne(message);
    return;
  }

  await handleOne(message);
}

function shutdown() {
  try { if (localBrain) localBrain.closeDb(true); } catch {}
}

async function main() {
  const rl = readline.createInterface({
    input: process.stdin,
    crlfDelay: Infinity,
  });

  rl.on('line', (line) => {
    handleLine(line).catch((err) => {
      log(err.stack || err.message);
      writeMessage({
        jsonrpc: '2.0',
        id: null,
        error: { code: -32603, message: 'Internal error' },
      });
    });
  });

  rl.on('close', () => {
    shutdown();
    process.exit(0);
  });
}

process.on('SIGINT', () => {
  shutdown();
  process.exit(130);
});
process.on('SIGTERM', () => {
  shutdown();
  process.exit(143);
});

main().catch((err) => {
  log(err.stack || err.message);
  shutdown();
  process.exit(1);
});
