'use strict';
/**
 * Scheduler failure alerting.
 *
 * Borrowed from OpenClaw's resolveFailureAlert + emitFailureAlert + applyJobResult
 * (src/cron/service/timer.ts:300-465). After N consecutive errors on a single
 * scheduler job, write a Service Alert that the CTM Wall-E tab will surface
 * to the operator. Cooldown prevents spam.
 *
 * Defaults match OpenClaw:
 *   DEFAULT_FAILURE_ALERT_AFTER       = 2
 *   DEFAULT_FAILURE_ALERT_COOLDOWN_MS = 1 hour
 *
 * Wall-E already has a service-alerts surface (kv_store key 'service_alerts',
 * a capped JSON array) used by skill-planner.js for auth failures. This
 * module reuses that surface so scheduler alerts appear in the same UI list,
 * keyed by `service: 'scheduler'`, `type: 'job_failure:<jobName>'`.
 *
 * The CTM Wall-E tab polls `/api/wall-e/service-alerts`; no UI change needed.
 */

const FAILURE_ALERT_AFTER = 2;
const FAILURE_ALERT_COOLDOWN_MS = 60 * 60_000; // 1h
const MAX_ERROR_PREVIEW_CHARS = 200;
const MAX_ALERTS = 20;

function _getAlerts(brain) {
  try {
    const raw = brain.getKv('service_alerts');
    return raw ? JSON.parse(raw) : [];
  } catch {
    return [];
  }
}

function _putAlerts(brain, alerts) {
  while (alerts.length > MAX_ALERTS) alerts.shift();
  brain.setKv('service_alerts', JSON.stringify(alerts));
}

/**
 * Add or refresh a scheduler-job-failure alert. Dedupes by service+type
 * (matches skill-planner.addServiceAlert semantics).
 */
function _addAlert(brain, { jobName, message, actionUrl }) {
  const alerts = _getAlerts(brain);
  const service = 'scheduler';
  const type = `job_failure:${jobName}`;
  const existing = alerts.findIndex((a) => a.service === service && a.type === type);
  if (existing >= 0) alerts.splice(existing, 1);
  alerts.push({
    id: `${service}:${type}:${Date.now()}`,
    service,
    type,
    message,
    action_url: actionUrl || null,
    created_at: new Date().toISOString(),
  });
  _putAlerts(brain, alerts);
}

/**
 * Record an error for a scheduler job. Mutates `state` to track
 * consecutive errors and last-alert timestamp. Emits a Service Alert when
 * the threshold is reached and we're outside the cooldown window.
 *
 * @param {Object} params
 * @param {string} params.jobName
 * @param {Error|string} params.err
 * @param {Object} params.state - The scheduler's per-job state object.
 * @param {Object} [params.brain] - Brain module (defaults to require('../brain')).
 * @param {Object} [params.opts] - Override thresholds for tests.
 * @param {number} [params.opts.alertAfter]
 * @param {number} [params.opts.cooldownMs]
 */
function recordJobError({ jobName, err, state, brain, opts }) {
  if (!brain) brain = require('../brain');
  const alertAfter = opts?.alertAfter ?? FAILURE_ALERT_AFTER;
  const cooldownMs = opts?.cooldownMs ?? FAILURE_ALERT_COOLDOWN_MS;

  state.consecutiveErrors = (state.consecutiveErrors || 0) + 1;
  if (state.consecutiveErrors < alertAfter) return { alerted: false };

  const now = Date.now();
  const lastAlertAt = state.lastFailureAlertAt || 0;
  if (now - lastAlertAt < cooldownMs) return { alerted: false, reason: 'cooldown' };

  const errMsg = (err && err.message) || String(err) || 'unknown error';
  const truncated = errMsg.slice(0, MAX_ERROR_PREVIEW_CHARS);
  const message = `Wall-E loop "${jobName}" failed ${state.consecutiveErrors} times. Last error: ${truncated}`;
  try {
    _addAlert(brain, { jobName, message, actionUrl: '/walle' });
    state.lastFailureAlertAt = now;
    return { alerted: true, consecutiveErrors: state.consecutiveErrors };
  } catch (alertErr) {
    return { alerted: false, reason: 'kv-write-failed', error: alertErr.message };
  }
}

/**
 * Reset error tracking on a successful run. Also clears any active
 * scheduler:job_failure:<jobName> alert in kv_store so the operator
 * sees recovery as the alert disappearing.
 */
function recordJobSuccess({ jobName, state, brain }) {
  if (!brain) brain = require('../brain');
  state.consecutiveErrors = 0;
  state.lastFailureAlertAt = 0;
  try {
    const alerts = _getAlerts(brain);
    const service = 'scheduler';
    const type = `job_failure:${jobName}`;
    const filtered = alerts.filter((a) => !(a.service === service && a.type === type));
    if (filtered.length !== alerts.length) {
      brain.setKv('service_alerts', JSON.stringify(filtered));
      return { cleared: true };
    }
  } catch {}
  return { cleared: false };
}

module.exports = {
  recordJobError,
  recordJobSuccess,
  FAILURE_ALERT_AFTER,
  FAILURE_ALERT_COOLDOWN_MS,
};
