'use strict';

/**
 * sys-call dispatcher.
 *
 * Routes incoming sys-call messages from VPS to channel handlers.
 *
 * Supported actions (incoming from VPS):
 *   request  — start a new task
 *   cancel   — abort a running task
 *
 * Handler interface:
 *   handler[method](params) → { abort } | function
 *     abort()  — called on cancel
 */

const log = require('./logger');

// ── channel handlers ──────────────────────────────────────────────────────────
const handlers = {
  'channel.weixin': require('./channel/weixin'),
};

// ── running tasks: callId → { abort, onReply? } ───────────────────────────────
const running = new Map();

/**
 * Handle an incoming sys-call message from VPS.
 *
 * @param {object}   msg  - parsed message object
 * @param {function} send - send(replyPayload) → forwarded to VPS over WS
 *                          caller prepends { type:'sys-call' }
 */
function handle(msg, send) {
  const { id: callId, api, method, action } = msg;

  if (!callId) {
    log.warn('sys-call', 'missing id field, ignoring');
    return;
  }

  // ── cancel ────────────────────────────────────────────────────────────────
  if (action === 'cancel') {
    const task = running.get(callId);
    if (task) {
      log.info('sys-call', `cancel callId=${callId}`);
      try { task.abort(); } catch (e) { log.warn('sys-call', `abort error: ${e.message}`); }
      running.delete(callId);
    } else {
      log.debug('sys-call', `cancel for unknown/finished callId=${callId}`);
    }
    return;
  }

  // ── request ───────────────────────────────────────────────────────────────
  if (action !== 'request') {
    log.warn('sys-call', `unexpected action=${action} callId=${callId}`);
    return;
  }

  const handler = handlers[api];
  if (!handler || typeof handler[method] !== 'function') {
    log.warn('sys-call', `unknown api=${api} method=${method}`);
    send({
      id: callId, api, method,
      action: 'finish', event: 'failed',
      code: 404, message: `unknown api: ${api}.${method}`,
    });
    return;
  }

  // emit: merges envelope fields, cleans up running map on finish
  const emit = (payload) => {
    send({ id: callId, api, method, event: '', code: 0, message: '', ...payload });
    if (payload.action === 'finish') {
      running.delete(callId);
      log.info('sys-call', `finished callId=${callId}`);
    }
  };

  log.info('sys-call', `start api=${api} method=${method} callId=${callId}`);

  let task;
  try {
    task = handler[method]({ callId, ...(msg.data || {}), emit });
  } catch (e) {
    log.error('sys-call', `handler threw: ${e.message}`);
    send({
      id: callId, api, method,
      action: 'finish', event: 'failed',
      code: 500, message: e.message,
    });
    return;
  }

  // Normalise: handler may return a function (abort only) or { abort }
  if (typeof task === 'function') {
    task = { abort: task };
  }

  if (task && typeof task.abort === 'function') {
    running.set(callId, task);
  }
}

module.exports = { handle };
