import { newId } from './db.js';
import { competitorReport, heartbeatTick, marketingPlan } from './runtime.js';
import { runAgentTick } from './agent-runner.js';
import { executeRegistryTool } from './tools/registry.js';
import { markStaleHermesJobs } from './hermes-jobs.js';
import { gcOldEvents as gcRateLimitEvents } from './tool-rate-limits.js';
import { markMissionFromTask } from './missions.js';
import { runStageExecution } from './process/engine.js';
import { minesDocumentExtract } from './tools/adapters/mines/document-extract.js';
import { minesTabularLoad } from './tools/adapters/mines/tabular-load.js';
import { minesSpatialIndex } from './tools/adapters/mines/spatial-index.js';
import {
  claimHeartbeatTask,
  completeHeartbeatTask,
  createHeartbeatTask,
  failHeartbeatTask,
  markHeartbeatTaskRunning,
  pauseHeartbeatTaskForApproval,
  recoverStaleHeartbeatTasks,
} from './heartbeat-queue.js';

async function executeTask(task) {
  const payload = task.payload || {};
  const type = payload.type || task.type;

  // Service-principal identity — propagated from schedules.created_by_user_id
  // via the cron daemon. Falls back to 'system' for internal/non-user tasks.
  const actingUserId = task.acting_on_behalf_of_user_id || 'system';

  if (type === 'noop') return { ok: true };
  if (type === 'tool_rate_limit_gc') {
    const result = gcRateLimitEvents();
    // Re-schedule the next GC run 24 h from now so this is a recurring daily task.
    const nextRunAfter = new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString();
    createHeartbeatTask({
      type: 'tool_rate_limit_gc',
      payload: { type: 'tool_rate_limit_gc' },
      max_attempts: 99,
      run_after: nextRunAfter,
    });
    return { ok: true, deleted: result.deleted };
  }
  if (type === 'update_check') {
    const { runUpdateCheck, shouldAutoApply } = await import('./update-check.js');
    const { resolveHome } = await import('./cli/home.js');
    const { getConfig } = await import('./config.js');
    const check = await runUpdateCheck({ file: resolveHome().updateCheckFile });
    let applied = false;
    // Hands-off apply ONLY when the operator opted in at setup (auto_update_apply=auto).
    // This is system-authorized policy, not an LLM tool call, so it does not pass
    // through the irreversible_external gate — the human consented once, at setup.
    if (shouldAutoApply({ behind: check.behind, mode: getConfig('auto_update_mode', 'off'), apply: getConfig('auto_update_apply', 'notify') })) {
      const { applyUpdate } = await import('./cli/update.js');
      const res = await applyUpdate({ initiatedBy: 'weekly_auto_update' });
      applied = res.ok;
    }
    // Re-schedule the next check 7 days out (recurring weekly task).
    createHeartbeatTask({
      type: 'update_check',
      payload: { type: 'update_check' },
      max_attempts: 99,
      run_after: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(),
    });
    return { ok: true, behind: check.behind, latest: check.latest, applied };
  }
  if (type === 'competitor-report') return competitorReport({ triggered_by: 'heartbeat-worker' });
  if (type === 'marketing-plan') return marketingPlan({ triggered_by: 'heartbeat-worker' });
  if (type === 'heartbeat') return heartbeatTick({ triggered_by: 'heartbeat-worker' });
  if (type === 'agent-task') {
    return runAgentTick(payload.agent_id || task.agent_id, {
      triggered_by: 'heartbeat-worker',
      task_id: payload.task_id || null,
      agent_run_id: payload.run_id || null,
      wake_reason: payload.reason || 'task_assigned',
      acting_on_behalf_of_user_id: actingUserId,
      heartbeat_task_id: task.id,
    });
  }
  if (type === 'mission') {
    try {
      const result = await runAgentTick(payload.agent_id || task.agent_id, {
        triggered_by: 'heartbeat-worker',
        mission_id: payload.mission_id || null,
        wake_reason: `mission:${payload.slug || task.type}`,
        acting_on_behalf_of_user_id: actingUserId,
        heartbeat_task_id: task.id,
      });
      markMissionFromTask(task.id, 'complete');
      return result;
    } catch (error) {
      markMissionFromTask(task.id, 'error', error?.message || String(error));
      throw error;
    }
  }
  if (type === 'process_stage') {
    return runStageExecution({
      case_id: payload.case_id,
      stage_id: payload.stage_id,
      execution_id: payload.execution_id,
      heartbeat_task_id: task.id,
    });
  }
  // Mines ingestion tasks enqueued by mines_data_import. Each was created under
  // the already-approved bulk import, so they dispatch directly to the adapter
  // (no second approval gate). The adapters own their own extraction_status
  // transitions and are idempotent / safe to retry under the heartbeat lease.
  if (type === 'mines.extract_document') {
    return minesDocumentExtract(
      { source_document_id: payload.source_document_id, force_ocr: payload.force_ocr },
      { acting_on_behalf_of_user_id: actingUserId, heartbeat_task_id: task.id }
    );
  }
  if (type === 'mines.load_tabular') {
    return minesTabularLoad(
      { source_document_id: payload.source_document_id },
      { acting_on_behalf_of_user_id: actingUserId, heartbeat_task_id: task.id }
    );
  }
  if (type === 'mines.index_spatial') {
    return minesSpatialIndex(
      { project_id: payload.project_id, source_document_id: payload.source_document_id },
      { acting_on_behalf_of_user_id: actingUserId, heartbeat_task_id: task.id }
    );
  }
  if (type === 'tool_call') {
    const input = { ...(payload.input || {}) };
    if (task.approval_request_id && !input.approval_id) input.approval_id = task.approval_request_id;
    return executeRegistryTool(payload.tool_name, input, {
      agent_id: payload.agent_id || task.agent_id,
      acting_on_behalf_of_user_id: actingUserId,
      heartbeat_task_id: task.id,
    });
  }
  return runAgentTick(payload.agent_id || task.agent_id, {
    triggered_by: 'heartbeat-worker',
    acting_on_behalf_of_user_id: actingUserId,
    heartbeat_task_id: task.id,
  });
}

export async function runHeartbeatWorkerOnce({ workerId = newId('worker'), leaseMs } = {}) {
  recoverStaleHeartbeatTasks();
  try { markStaleHermesJobs(); } catch {}
  const task = claimHeartbeatTask({ workerId, leaseMs });
  if (!task) return { ok: true, worker_id: workerId, worked: false };
  markHeartbeatTaskRunning(task.id, workerId);
  try {
    const result = await executeTask(task);
    if (result?.status === 'approval_required') {
      pauseHeartbeatTaskForApproval(task.id, result.approval_request_id, result.reason);
      return { ok: true, worker_id: workerId, worked: true, status: 'approval_required', task_id: task.id };
    }
    completeHeartbeatTask(task.id, result || {});
    return { ok: true, worker_id: workerId, worked: true, status: 'complete', task_id: task.id };
  } catch (error) {
    const failed = failHeartbeatTask(task.id, error);
    return { ok: false, worker_id: workerId, worked: true, status: failed.status, task_id: task.id, error: error.message };
  }
}

export async function runHeartbeatWorkerLoop({ intervalMs = 30000, workerId = newId('worker') } = {}) {
  let stopped = false;
  const stop = () => { stopped = true; };
  process.once('SIGINT', stop);
  process.once('SIGTERM', stop);
  while (!stopped) {
    await runHeartbeatWorkerOnce({ workerId });
    await new Promise(resolve => setTimeout(resolve, intervalMs));
  }
}
