import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
import { CommunicationChannel } from "../data/entities.js";
import {
  COMMUNICATION_CHANNELS_INGEST_INBOUND_COMMAND_ID
} from "../commands/ingest-inbound-message.js";
import { COMMUNICATION_CHANNELS_QUEUES } from "../lib/queue.js";
import { preservePushState } from "../lib/push-state.js";
import { refreshCredentialsIfNeeded } from "../lib/credential-refresh.js";
import { classifyOutboundError } from "../lib/error-classification.js";
import { writeIngestDeadLetter } from "../lib/dead-letter.js";
import { createLogger } from "@open-mercato/shared/lib/logger";
const logger = createLogger("communication_channels").child({ component: "gmail-history-sync" });
const MAX_DRAIN_PAGES = 100;
const metadata = {
  queue: COMMUNICATION_CHANNELS_QUEUES.gmailHistorySync,
  id: "communication_channels:gmail-history-sync",
  concurrency: 5
};
async function handle(job, ctx) {
  const { channelId, scope } = job.payload;
  const em = ctx.resolve("em").fork();
  const adapterRegistry = ctx.resolve("channelAdapterRegistry");
  const channel = await findOneWithDecryption(
    em,
    CommunicationChannel,
    {
      id: channelId,
      tenantId: scope.tenantId,
      organizationId: scope.organizationId ?? null,
      deletedAt: null
    },
    void 0,
    scope
  );
  if (!channel) {
    logger.warn("channel not found (skip)", { channelId });
    return;
  }
  if (!channel.isActive || channel.status !== "connected") return;
  const adapter = adapterRegistry?.get(channel.providerKey);
  if (!adapter || typeof adapter.applyPushNotification !== "function") {
    logger.warn("adapter does not support applyPushNotification", { providerKey: channel.providerKey });
    return;
  }
  let credentialsService = null;
  try {
    credentialsService = ctx.resolve("integrationCredentialsService");
  } catch {
    credentialsService = null;
  }
  const credentialsScope = {
    tenantId: scope.tenantId,
    organizationId: scope.organizationId ?? scope.tenantId,
    userId: channel.userId ?? null
  };
  let credentials = {};
  if (channel.credentialsRef && credentialsService) {
    try {
      credentials = await credentialsService.resolve(`channel_${channel.providerKey}`, credentialsScope) ?? {};
    } catch {
      credentials = {};
    }
  }
  const refreshed = await refreshCredentialsIfNeeded(
    { adapter, channelId: channel.id, credentials, scope: credentialsScope },
    { credentialsService }
  );
  credentials = refreshed.credentials;
  let page;
  try {
    page = await adapter.applyPushNotification({
      credentials,
      scope: { tenantId: scope.tenantId, organizationId: scope.organizationId ?? scope.tenantId },
      channelState: channel.channelState ?? {},
      notification: job.payload.notification
    });
  } catch (err) {
    const classification = classifyOutboundError(err);
    if (classification.transient) throw err;
    logger.warn("permanent failure applying push for channel", { channelId: channel.id, reason: classification.message });
    return;
  }
  const messages = Array.isArray(page?.messages) ? page.messages : [];
  if (messages.length > 0) {
    const commandBus = ctx.resolve("commandBus");
    const containerProxy = { resolve: ctx.resolve.bind(ctx) };
    const commandCtx = {
      container: containerProxy,
      auth: null,
      organizationScope: null,
      selectedOrganizationId: scope.organizationId ?? null,
      organizationIds: scope.organizationId ? [scope.organizationId] : null
    };
    for (const message of messages) {
      try {
        const input = {
          channelId: channel.id,
          providerKey: channel.providerKey,
          channelType: channel.channelType,
          scope: { tenantId: scope.tenantId, organizationId: scope.organizationId ?? null },
          message
        };
        await commandBus.execute(COMMUNICATION_CHANNELS_INGEST_INBOUND_COMMAND_ID, {
          input,
          ctx: commandCtx
        });
      } catch (err) {
        const classification = classifyOutboundError(err);
        if (classification.transient) {
          throw err;
        }
        logger.warn("permanent ingest failure for channel", { channelId: channel.id, reason: classification.message });
        await writeIngestDeadLetter({ em, scope, channel, message, err, errorMessage: classification.message });
      }
    }
  }
  if (page?.nextCursor) {
    try {
      const decoded = JSON.parse(Buffer.from(page.nextCursor, "base64").toString("utf-8"));
      channel.channelState = preservePushState(channel.channelState, decoded);
      channel.lastPolledAt = /* @__PURE__ */ new Date();
      await em.flush();
    } catch (err) {
      logger.warn("failed to persist next cursor for channel", { channelId: channel.id, err });
    }
  }
  if (page?.hasMore && page?.nextCursor) {
    const drainPage = job.payload.drainPage ?? 0;
    if (drainPage < MAX_DRAIN_PAGES) {
      const queue = (await import("../lib/queue.js")).getCommunicationChannelsQueue(
        COMMUNICATION_CHANNELS_QUEUES.gmailHistorySync
      );
      await queue.enqueue(
        { ...job.payload, drainPage: drainPage + 1 },
        { delayMs: 250 }
      );
    } else {
      logger.warn("drain page cap reached; stopping re-enqueue", { maxDrainPages: MAX_DRAIN_PAGES, channelId });
    }
  }
}
export {
  handle as default,
  metadata
};
//# sourceMappingURL=gmail-history-sync.js.map
