import { Processor, WorkerHost, OnWorkerEvent } from "@nestjs/bullmq";
import { Injectable, Inject } from "@nestjs/common";
import { Logger } from "nestjs-pino";
import { Job } from "bullmq";
import { QUEUE_NAMES } from "@bff/infra/queue/queue.constants.js";
import { SalesforceFacade } from "@bff/integrations/salesforce/facades/salesforce.facade.js";
import { AuditService, AuditAction } from "@bff/infra/audit/audit.service.js";
import { extractErrorMessage } from "@bff/core/utils/error.util.js";
import type { AddressReconcileJobData } from "./address-reconcile.queue.js";

const SERVICE_NAME = "address-reconcile-processor";

/**
 * Processor for address reconciliation jobs.
 *
 * Handles failed Salesforce address updates by retrying with exponential backoff.
 * Logs audit events for tracking reconciliation status.
 */
@Processor(QUEUE_NAMES.RECONCILE)
@Injectable()
export class AddressReconcileProcessor extends WorkerHost {
  constructor(
    private readonly salesforceService: SalesforceFacade,
    private readonly auditService: AuditService,
    @Inject(Logger) private readonly logger: Logger
  ) {
    super();
  }

  async process(job: Job<AddressReconcileJobData>): Promise<void> {
    const jobContext = {
      service: SERVICE_NAME,
      jobId: job.id,
      attempt: job.attemptsMade + 1,
      maxAttempts: job.opts.attempts || 5,
      userId: job.data.userId,
      sfAccountId: job.data.sfAccountId,
      correlationId: job.data.correlationId,
    };

    this.logger.log("Processing address reconciliation job", jobContext);

    try {
      await this.salesforceService.updateContactAddress(job.data.sfAccountId, {
        mailingStreet: job.data.address.mailingStreet,
        mailingCity: job.data.address.mailingCity,
        mailingState: job.data.address.mailingState,
        mailingPostalCode: job.data.address.mailingPostalCode,
        mailingCountry: job.data.address.mailingCountry,
        buildingName: job.data.address.buildingName,
        roomNumber: job.data.address.roomNumber,
      });

      this.logger.log("Address reconciliation succeeded", {
        ...jobContext,
        duration: Date.now() - job.timestamp,
      });

      // Log successful reconciliation
      await this.auditService.log({
        userId: job.data.userId,
        action: AuditAction.ADDRESS_RECONCILE_SUCCESS,
        resource: "address",
        details: {
          sfAccountId: job.data.sfAccountId,
          correlationId: job.data.correlationId,
          attempt: job.attemptsMade + 1,
          originalError: job.data.originalError,
        },
        success: true,
      });
    } catch (error) {
      const errorMessage = extractErrorMessage(error);

      this.logger.error("Address reconciliation attempt failed", {
        ...jobContext,
        error: errorMessage,
        willRetry: jobContext.attempt < jobContext.maxAttempts,
      });

      // Re-throw to let BullMQ handle retry logic
      throw error;
    }
  }

  @OnWorkerEvent("completed")
  onCompleted(job: Job<AddressReconcileJobData>): void {
    this.logger.debug("Address reconciliation job marked complete", {
      service: SERVICE_NAME,
      jobId: job.id,
      userId: job.data.userId,
      sfAccountId: job.data.sfAccountId,
    });
  }

  @OnWorkerEvent("failed")
  async onFailed(job: Job<AddressReconcileJobData> | undefined, error: Error): Promise<void> {
    if (!job) {
      this.logger.error("Address reconciliation job failed (job undefined)", {
        service: SERVICE_NAME,
        error: error.message,
      });
      return;
    }

    const isLastAttempt = job.attemptsMade >= (job.opts.attempts || 5);

    if (isLastAttempt) {
      this.logger.error("Address reconciliation permanently failed - all retries exhausted", {
        service: SERVICE_NAME,
        jobId: job.id,
        userId: job.data.userId,
        sfAccountId: job.data.sfAccountId,
        correlationId: job.data.correlationId,
        totalAttempts: job.attemptsMade,
        error: error.message,
        originalError: job.data.originalError,
      });

      // Log permanent failure for alerting/monitoring
      await this.auditService.log({
        userId: job.data.userId,
        action: AuditAction.ADDRESS_RECONCILE_FAILED,
        resource: "address",
        details: {
          sfAccountId: job.data.sfAccountId,
          correlationId: job.data.correlationId,
          totalAttempts: job.attemptsMade,
          originalError: job.data.originalError,
          finalError: error.message,
        },
        success: false,
        error: error.message,
      });
    } else {
      this.logger.warn("Address reconciliation job failed - will retry", {
        service: SERVICE_NAME,
        jobId: job.id,
        userId: job.data.userId,
        attempt: job.attemptsMade,
        maxAttempts: job.opts.attempts || 5,
        error: error.message,
      });
    }
  }

  @OnWorkerEvent("error")
  onError(error: Error): void {
    this.logger.error("Address reconcile processor worker error", {
      service: SERVICE_NAME,
      error: error.message,
      stack: error.stack,
    });
  }
}
