import { Injectable, Inject, InternalServerErrorException } from "@nestjs/common";
import { InjectQueue } from "@nestjs/bullmq";
import { Queue } from "bullmq";
import { Logger } from "nestjs-pino";
import { QUEUE_NAMES } from "@bff/infra/queue/queue.constants.js";

/**
 * Job data for address reconciliation between WHMCS and Salesforce
 */
export interface AddressReconcileJobData {
  /** Portal user ID */
  userId: string;
  /** Salesforce Account ID */
  sfAccountId: string;
  /** Address fields to sync to Salesforce */
  address: {
    mailingStreet: string;
    mailingCity: string;
    mailingState: string;
    mailingPostalCode: string;
    mailingCountry: string;
    buildingName: string | null;
    roomNumber: string | null;
  };
  /** Error message from original failure */
  originalError: string;
  /** Correlation ID for tracing */
  correlationId: string;
}

export interface AddressReconcileJobResult {
  jobId: string;
  queued: boolean;
}

/** Queue configuration for address reconciliation */
const QUEUE_CONFIG = {
  /** Keep last N completed jobs for debugging */
  REMOVE_ON_COMPLETE: 50,
  /** Keep last N failed jobs for investigation */
  REMOVE_ON_FAIL: 100,
  /** Max retry attempts - more aggressive than email since this is reconciliation */
  MAX_ATTEMPTS: 5,
  /** Initial backoff delay in ms (1 minute) */
  BACKOFF_DELAY: 60_000,
} as const;

/**
 * Queue service for address reconciliation jobs.
 *
 * When Salesforce address update fails during dual-write,
 * this queue ensures eventual consistency through retries.
 */
@Injectable()
export class AddressReconcileQueueService {
  constructor(
    @InjectQueue(QUEUE_NAMES.RECONCILE) private readonly queue: Queue<AddressReconcileJobData>,
    @Inject(Logger) private readonly logger: Logger
  ) {}

  /**
   * Enqueue an address reconciliation job.
   *
   * Called when the initial Salesforce address update fails.
   * Job will be retried with exponential backoff.
   */
  async enqueueReconciliation(data: AddressReconcileJobData): Promise<AddressReconcileJobResult> {
    const jobContext = {
      service: "address-reconcile-queue",
      queue: QUEUE_NAMES.RECONCILE,
      userId: data.userId,
      sfAccountId: data.sfAccountId,
      correlationId: data.correlationId,
    };

    try {
      const job = await this.queue.add("address-reconcile", data, {
        removeOnComplete: QUEUE_CONFIG.REMOVE_ON_COMPLETE,
        removeOnFail: QUEUE_CONFIG.REMOVE_ON_FAIL,
        attempts: QUEUE_CONFIG.MAX_ATTEMPTS,
        backoff: {
          type: "exponential",
          delay: QUEUE_CONFIG.BACKOFF_DELAY,
        },
      });

      this.logger.log("Address reconciliation job queued", {
        ...jobContext,
        jobId: job.id,
        maxAttempts: QUEUE_CONFIG.MAX_ATTEMPTS,
        initialDelay: QUEUE_CONFIG.BACKOFF_DELAY,
      });

      return { jobId: job.id || "unknown", queued: true };
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : String(error);

      this.logger.error("Failed to queue address reconciliation job", {
        ...jobContext,
        error: errorMessage,
      });

      throw new InternalServerErrorException(
        `Failed to queue address reconciliation: ${errorMessage}`
      );
    }
  }

  /**
   * Get queue health metrics for monitoring
   */
  async getQueueHealth(): Promise<{
    waiting: number;
    active: number;
    completed: number;
    failed: number;
    delayed: number;
  }> {
    const [waiting, active, completed, failed, delayed] = await Promise.all([
      this.queue.getWaitingCount(),
      this.queue.getActiveCount(),
      this.queue.getCompletedCount(),
      this.queue.getFailedCount(),
      this.queue.getDelayedCount(),
    ]);

    return { waiting, active, completed, failed, delayed };
  }
}
