import type { Logger } from "pino";
import { METRIC } from "@/metrics/names.js";
import type { Metrics } from "@/metrics/types.js";
import type { IJobQueue, JobKind } from "@/types/index.js";

export type SkipReason = "reentrant" | "busy" | "other_pipeline";

export interface PollRunnerDeps {
  poller: string;
  queue: IJobQueue;
  metrics: Metrics;
  logger: Logger;
  contendingKinds: JobKind[];
  skipLogThrottleMs: number;
}

/** Wraps a poll body with the guard preamble + uniform metrics/logging. */
export class PollRunner {
  private polling = false;
  private lastSkipLogAt = 0;

  constructor(private readonly deps: PollRunnerDeps) {}

  async run(body: () => Promise<number>): Promise<number> {
    const { poller, queue, metrics } = this.deps;

    if (this.polling) {
      metrics.increment(METRIC.pollSkipped, { poller, reason: "reentrant" });
      return 0;
    }
    if (queue.isBusy()) {
      metrics.increment(METRIC.pollSkipped, { poller, reason: "busy" });
      this.maybeLogSkip("busy");
      return 0;
    }
    const kind = queue.getCurrentJobKind();
    if (kind && this.deps.contendingKinds.includes(kind)) {
      metrics.increment(METRIC.pollSkipped, { poller, reason: "other_pipeline" });
      this.maybeLogSkip("other_pipeline");
      return 0;
    }

    this.polling = true;
    const start = Date.now();
    try {
      const enqueued = await body();
      metrics.increment(METRIC.pollExecuted, { poller });
      if (enqueued > 0) metrics.count(METRIC.pollEnqueued, enqueued, { poller });
      metrics.timing(METRIC.pollDuration, Date.now() - start, { poller });
      return enqueued;
    } catch (err) {
      metrics.increment(METRIC.pollError, { poller });
      this.deps.logger.error(
        { error: err instanceof Error ? err.message : String(err) },
        "poll_error",
      );
      return 0;
    } finally {
      this.polling = false;
    }
  }

  private maybeLogSkip(reason: SkipReason): void {
    const now = Date.now();
    if (now - this.lastSkipLogAt < this.deps.skipLogThrottleMs) return;
    this.lastSkipLogAt = now;
    this.deps.logger.info(
      {
        poller: this.deps.poller,
        reason,
        currentJobKey: this.deps.queue.getCurrentJobKey(),
        currentJobKind: this.deps.queue.getCurrentJobKind(),
        pendingJobs: this.deps.queue.pendingCount(),
      },
      "poll_skipped",
    );
  }
}
