import { queryWithRLS, mutateWithRLS } from "../db.js";
import { APP_CONFIG_MERGED } from "../config/index.js";
import { getActionHandler } from "./registry.js";
import { scheduleAction } from "./scheduler.js";
const DEFAULT_BATCH_SIZE = 10;
const DEFAULT_TIMEOUT = 3e4;
const DEFAULT_CONCURRENCY_LIMIT = 1;
async function processPendingActions(batchSize) {
  var _a, _b;
  const effectiveBatchSize = batchSize ?? ((_a = APP_CONFIG_MERGED.scheduledActions) == null ? void 0 : _a.batchSize) ?? DEFAULT_BATCH_SIZE;
  const concurrencyLimit = ((_b = APP_CONFIG_MERGED.scheduledActions) == null ? void 0 : _b.concurrencyLimit) ?? DEFAULT_CONCURRENCY_LIMIT;
  const result = {
    processed: 0,
    succeeded: 0,
    failed: 0,
    errors: []
  };
  try {
    const actions = await fetchAndLockPendingActions(effectiveBatchSize);
    if (actions.length === 0) {
      console.log("[ScheduledActions] No pending actions to process");
      return result;
    }
    console.log(`[ScheduledActions] Found ${actions.length} pending action(s) to process (concurrency: ${concurrencyLimit})`);
    if (concurrencyLimit === 1) {
      for (const action of actions) {
        result.processed++;
        try {
          await executeAction(action);
          result.succeeded++;
        } catch (error) {
          result.failed++;
          result.errors.push({
            actionId: action.id,
            error: error instanceof Error ? error.message : "Unknown error"
          });
        }
      }
    } else {
      const results = await processWithConcurrencyLimit(actions, concurrencyLimit);
      for (const r of results) {
        result.processed++;
        if (r.status === "fulfilled") {
          result.succeeded++;
        } else {
          result.failed++;
          result.errors.push({
            actionId: r.actionId,
            error: r.reason instanceof Error ? r.reason.message : "Unknown error"
          });
        }
      }
    }
    console.log(`[ScheduledActions] Processing complete: ${result.succeeded} succeeded, ${result.failed} failed`);
  } catch (error) {
    console.error("[ScheduledActions] Error fetching pending actions:", error);
    throw error;
  }
  return result;
}
async function fetchAndLockPendingActions(batchSize) {
  const actions = await queryWithRLS(
    `WITH ranked_actions AS (
      SELECT *,
        ROW_NUMBER() OVER (
          PARTITION BY "lockGroup"
          ORDER BY "scheduledAt" ASC
        ) as rn
      FROM "scheduled_actions"
      WHERE status = 'pending'
        AND "scheduledAt" <= NOW()
    )
    SELECT id, "actionType", status, payload, "teamId", "scheduledAt",
           "startedAt", "completedAt", "errorMessage", attempts,
           "recurringInterval", "recurrenceType", "lockGroup", "maxRetries",
           "createdAt", "updatedAt"
    FROM ranked_actions
    WHERE rn = 1 OR "lockGroup" IS NULL
    ORDER BY "scheduledAt" ASC
    LIMIT $1
    FOR UPDATE SKIP LOCKED`,
    [batchSize],
    null
    // System operation
  );
  return actions;
}
async function processWithConcurrencyLimit(actions, limit) {
  const results = [];
  const executing = /* @__PURE__ */ new Set();
  for (const action of actions) {
    const promise = executeAction(action).then(() => {
      results.push({ actionId: action.id, status: "fulfilled" });
    }).catch((error) => {
      results.push({ actionId: action.id, status: "rejected", reason: error });
    }).finally(() => {
      executing.delete(promise);
    });
    executing.add(promise);
    if (executing.size >= limit) {
      await Promise.race(executing);
    }
  }
  await Promise.all(executing);
  return results;
}
async function executeAction(action) {
  var _a;
  const startTime = Date.now();
  const configDefaultTimeout = ((_a = APP_CONFIG_MERGED.scheduledActions) == null ? void 0 : _a.defaultTimeout) ?? DEFAULT_TIMEOUT;
  console.log(`[ScheduledActions] Executing action ${action.id} (${action.actionType})${action.lockGroup ? ` [lockGroup: ${action.lockGroup}]` : ""}`);
  await markActionRunning(action.id);
  try {
    const actionDef = getActionHandler(action.actionType);
    if (!actionDef) {
      throw new Error(`No handler registered for action type: ${action.actionType}`);
    }
    const timeout = actionDef.timeout ?? configDefaultTimeout;
    await executeWithTimeout(
      actionDef.handler(action.payload, action),
      timeout,
      `Action ${action.actionType} exceeded timeout of ${timeout}ms`
    );
    const executionTime = Date.now() - startTime;
    console.log(`[ScheduledActions] Action ${action.id} completed in ${executionTime}ms`);
    await markActionCompleted(action.id);
    if (action.recurringInterval) {
      await rescheduleRecurringAction(action);
    }
  } catch (error) {
    const executionTime = Date.now() - startTime;
    const errorMessage = error instanceof Error ? error.message : "Unknown error";
    console.error(`[ScheduledActions] Action ${action.id} failed after ${executionTime}ms:`, errorMessage);
    if (action.attempts < action.maxRetries) {
      const retryDelayMinutes = action.attempts * 5;
      console.log(`[ScheduledActions] Will retry action ${action.id} in ${retryDelayMinutes} minutes (attempt ${action.attempts}/${action.maxRetries})`);
      await rescheduleFailedAction(action, errorMessage, retryDelayMinutes);
    } else {
      console.error(`[ScheduledActions] Max retries (${action.maxRetries}) reached for action ${action.id}`);
      await markActionFailed(action.id, errorMessage);
    }
    throw error;
  }
}
async function executeWithTimeout(promise, timeoutMs, timeoutMessage) {
  const timeoutPromise = new Promise((_, reject) => {
    setTimeout(() => reject(new Error(timeoutMessage)), timeoutMs);
  });
  return Promise.race([promise, timeoutPromise]);
}
async function markActionRunning(actionId) {
  await mutateWithRLS(
    `UPDATE "scheduled_actions"
     SET status = 'running',
         "startedAt" = NOW(),
         attempts = attempts + 1,
         "updatedAt" = NOW()
     WHERE id = $1`,
    [actionId],
    null
  );
}
async function markActionCompleted(actionId) {
  await mutateWithRLS(
    `UPDATE "scheduled_actions"
     SET status = 'completed',
         "completedAt" = NOW(),
         "updatedAt" = NOW()
     WHERE id = $1`,
    [actionId],
    null
  );
}
async function markActionFailed(actionId, errorMessage) {
  await mutateWithRLS(
    `UPDATE "scheduled_actions"
     SET status = 'failed',
         "completedAt" = NOW(),
         "errorMessage" = $2,
         "updatedAt" = NOW()
     WHERE id = $1`,
    [actionId, errorMessage],
    null
  );
}
async function rescheduleFailedAction(action, errorMessage, delayMinutes) {
  const nextAttemptTime = new Date(Date.now() + delayMinutes * 60 * 1e3);
  await mutateWithRLS(
    `UPDATE "scheduled_actions"
     SET status = 'pending',
         "scheduledAt" = $2,
         "errorMessage" = $3,
         "startedAt" = NULL,
         "completedAt" = NULL,
         "updatedAt" = NOW()
     WHERE id = $1`,
    [action.id, nextAttemptTime, errorMessage],
    null
  );
  console.log(`[ScheduledActions] Rescheduled action ${action.id} for ${nextAttemptTime.toISOString()}`);
}
async function rescheduleRecurringAction(action) {
  if (!action.recurringInterval) {
    return;
  }
  let nextScheduledAt;
  if (action.recurrenceType === "rolling") {
    nextScheduledAt = calculateNextScheduledTime(action.recurringInterval, /* @__PURE__ */ new Date());
  } else {
    nextScheduledAt = calculateNextScheduledTime(action.recurringInterval, action.scheduledAt);
  }
  await scheduleAction(
    action.actionType,
    action.payload,
    {
      scheduledAt: nextScheduledAt,
      teamId: action.teamId ?? void 0,
      recurringInterval: action.recurringInterval ?? void 0,
      recurrenceType: action.recurrenceType ?? void 0,
      lockGroup: action.lockGroup ?? void 0
    }
  );
  console.log(
    `[ScheduledActions] Rescheduled recurring action ${action.actionType} for ${nextScheduledAt.toISOString()} (type: ${action.recurrenceType || "fixed"})`
  );
}
function calculateNextScheduledTime(interval, baseTime) {
  const base = new Date(baseTime);
  switch (interval) {
    case "every-30-minutes":
      return new Date(base.getTime() + 30 * 60 * 1e3);
    case "hourly":
      return new Date(base.getTime() + 60 * 60 * 1e3);
    case "daily":
      return new Date(base.getTime() + 24 * 60 * 60 * 1e3);
    case "weekly":
      return new Date(base.getTime() + 7 * 24 * 60 * 60 * 1e3);
    default:
      console.warn(`[ScheduledActions] Unknown interval '${interval}', defaulting to 1 hour`);
      return new Date(base.getTime() + 60 * 60 * 1e3);
  }
}
export {
  executeAction,
  processPendingActions
};
