import { Injectable, Logger, OnModuleDestroy } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { randomUUID } from 'crypto';
import { Queue } from 'bullmq';
import {
  AUTOMATION_QUEUE_NAME,
  type AutomationJobData,
} from './automation-job-data';
import type { QueuePort } from './queue-port';
import { redisConnection } from './redis-connection';

@Injectable()
export class BullMqAutomationQueuePort implements QueuePort, OnModuleDestroy {
  private readonly logger = new Logger(BullMqAutomationQueuePort.name);
  private readonly queue?: Queue<AutomationJobData>;

  constructor(configService: ConfigService) {
    const redisUrl = configService.get<string>('REDIS_URL');
    if (!redisUrl) {
      return;
    }

    this.queue = new Queue<AutomationJobData>(AUTOMATION_QUEUE_NAME, {
      connection: redisConnection(redisUrl),
      prefix:
        configService.get<string>('AUTOMATION_QUEUE_PREFIX') ||
        'planfi:automations',
    });
  }

  async enqueue(
    data: AutomationJobData,
    delayMs = 0,
  ): Promise<{ id: string | null; degraded: boolean }> {
    if (!this.queue) {
      this.logger.warn(
        `automation.queue.degraded runId=${data.runId} automationId=${data.automationId} nodeKey=${data.nodeKey}`,
      );
      return { id: null, degraded: true };
    }

    const jobName =
      data.kind === 'resume-atlas' ? 'resume-atlas' : 'execute-node';

    // BullMQ proíbe ':' em jobId customizado (usa como separador no Redis) → '__'.
    //
    // Para 'resume-atlas': o jobId precisa de um token de unicidade por ENTRADA de
    // Espera. Usar só nodeKey+attempt colide quando o mesmo nó é re-entrado (loop)
    // ou quando waitKey é omitido (todos caem em 'resume-atlas__runId__resume-atlas__1').
    // waitSeq deve ser o id do wait-log no Atlas (determinístico por entrada → POST
    // duplicado retorna jobId igual → BullMQ desduplicando); quando ausente, fallback
    // para UUID local — perde idempotência no POST duplicado mas NUNCA colide.
    //
    // Para 'execute-node': fórmula inalterada (kind__runId__nodeKey__attempt).
    const jobId =
      data.kind === 'resume-atlas'
        ? `resume-atlas__${data.runId}__${data.nodeKey}__${data.waitSeq ?? randomUUID()}`
        : `${data.kind ?? 'execute-node'}__${data.runId}__${data.nodeKey}__${data.attempt}`;

    const job = await this.queue.add(jobName, data, {
      delay: Math.max(delayMs, 0),
      attempts: 3,
      backoff: { type: 'exponential', delay: 60_000 },
      removeOnComplete: 250,
      removeOnFail: 500,
      jobId,
    });

    return { id: String(job.id), degraded: false };
  }

  isAvailable(): boolean {
    return Boolean(this.queue);
  }

  async onModuleDestroy() {
    await this.queue?.close();
  }
}
