/**
 * Post-Booking Action Queue (BullMQ)
 *
 * Handles delayed post-booking actions like reminders, review requests,
 * pre-appointment instructions, and document delivery.
 *
 * The WhatsApp AI engine (`lib/services/whatsapp-ai-engine.service.ts`)
 * already handles immediate (`delayMinutes === 0`) actions inline. Anything
 * with a non-zero delay is queued here.
 *
 * Wired to the existing BullMQ Redis connection from `./bullmq.ts` so we
 * inherit graceful degradation when Redis is not configured.
 *
 * Usage:
 *   import { schedulePostBookingAction } from '@/lib/queue/post-booking-queue';
 *   await schedulePostBookingAction({ ... }, delayMinutes);
 *
 * Worker startup:
 *   import { initPostBookingWorker } from '@/lib/queue/post-booking-queue';
 *   initPostBookingWorker();
 */

import 'server-only';
import { Queue, Worker, type Job } from 'bullmq';
import IORedis from 'ioredis';
import { logger } from '@/lib/logger';

// ── Redis Connection ────────────────────────────────────────────────────
//
// We mirror the connection logic from ./bullmq.ts rather than importing
// `getQueue()` from there because that file uses a private map of queues
// keyed by a fixed `QueueName` enum. Keeping our own queue lets us scope
// post-booking jobs separately without polluting the shared registry.

let redisConnection: IORedis | null = null;

function getRedisConnection(): IORedis | null {
  if (redisConnection) return redisConnection;

  const redisUrl = process.env.REDIS_URL || 'redis://localhost:6379';

  try {
    const url = new URL(redisUrl);
    redisConnection = new IORedis({
      host: url.hostname || 'localhost',
      port: parseInt(url.port) || 6379,
      password: url.password || undefined,
      maxRetriesPerRequest: null, // BullMQ requirement
      enableReadyCheck: false,
      retryStrategy(times) {
        const delay = Math.min(times * 50, 2000);
        return delay;
      },
    });

    redisConnection.on('error', (err) => {
      logger.error('[PostBookingQueue] Redis connection error', err);
    });

    return redisConnection;
  } catch (error) {
    logger.error('[PostBookingQueue] Failed to parse REDIS_URL', { error });
    return null;
  }
}

// ── Job Definition ──────────────────────────────────────────────────────

export type PostBookingActionType =
  | 'send_confirmation'
  | 'request_review'
  | 'send_reminder'
  | 'send_instructions'
  | 'send_documents';

export interface PostBookingJob {
  organizationId: string;
  bookingId: string;
  customerPhone: string;
  language: 'en' | 'ar';
  actionType: PostBookingActionType;
  templateKey?: string;
}

const QUEUE_NAME = 'post-booking-actions';

// ── Queue Instance (lazy) ───────────────────────────────────────────────

let queueInstance: Queue<PostBookingJob> | null = null;

function getPostBookingQueue(): Queue<PostBookingJob> | null {
  if (queueInstance) return queueInstance;
  const connection = getRedisConnection();
  if (!connection) return null;

  try {
    queueInstance = new Queue<PostBookingJob>(QUEUE_NAME, {
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      connection: connection as any,
      defaultJobOptions: {
        attempts: 3,
        backoff: { type: 'exponential', delay: 60_000 },
        removeOnComplete: { age: 24 * 3600, count: 200 },
        removeOnFail: { age: 7 * 24 * 3600, count: 500 },
      },
    });
    return queueInstance;
  } catch (error) {
    logger.warn('[PostBookingQueue] Failed to create queue', { error });
    return null;
  }
}

// ── Public API: Schedule a job ──────────────────────────────────────────

/**
 * Schedule a post-booking action with a delay.
 *
 * @param job          The action payload.
 * @param delayMinutes Minutes to wait before processing.
 *                     Must be > 0 — immediate actions are handled inline by
 *                     the AI engine and do not need queueing.
 *
 * Note on negative delays: the sector config allows `delayMinutes < 0` to
 * mean "schedule relative to the appointment time" (e.g. -1440 = 24h before
 * appointment). Computing absolute trigger time requires the appointment
 * timestamp, which the caller must resolve. For now, callers should pass
 * `Math.abs(delayMinutes)` and we treat the value as "wait this many
 * minutes from now". A future iteration will plumb the appointment time
 * through and compute the proper schedule.
 */
export async function schedulePostBookingAction(
  job: PostBookingJob,
  delayMinutes: number,
): Promise<void> {
  if (delayMinutes <= 0) {
    // Immediate or non-positive delay — caller handles inline.
    return;
  }

  const queue = getPostBookingQueue();
  if (!queue) {
    logger.warn(
      '[PostBookingQueue] Redis unavailable — action will not be queued',
      { actionType: job.actionType, organizationId: job.organizationId },
    );
    return;
  }

  try {
    await queue.add(job.actionType, job, {
      delay: delayMinutes * 60 * 1000,
      attempts: 3,
      backoff: { type: 'exponential', delay: 60_000 },
    });
    logger.info('[PostBookingQueue] Job scheduled', {
      actionType: job.actionType,
      organizationId: job.organizationId,
      bookingId: job.bookingId,
      delayMinutes,
    });
  } catch (error) {
    logger.error('[PostBookingQueue] Failed to enqueue job', {
      actionType: job.actionType,
      organizationId: job.organizationId,
      error,
    });
  }
}

// ── Worker ──────────────────────────────────────────────────────────────

let workerInstance: Worker<PostBookingJob> | null = null;

/**
 * Initialize the post-booking worker. Idempotent — calling twice returns
 * the existing instance. Safe to call from worker bootstrap or tests.
 */
export function initPostBookingWorker(): Worker<PostBookingJob> | null {
  if (workerInstance) return workerInstance;
  const connection = getRedisConnection();
  if (!connection) {
    logger.warn(
      '[PostBookingWorker] Redis unavailable — worker not started',
    );
    return null;
  }

  try {
    workerInstance = new Worker<PostBookingJob>(
      QUEUE_NAME,
      processPostBookingJob,
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      { connection: connection as any, concurrency: 5 },
    );

    workerInstance.on('failed', (job, err) => {
      logger.error('[PostBookingWorker] Job failed', {
        jobId: job?.id,
        actionType: job?.data?.actionType,
        error: err.message,
      });
    });

    workerInstance.on('completed', (job) => {
      logger.debug('[PostBookingWorker] Job completed', {
        jobId: job.id,
        actionType: job.data.actionType,
      });
    });

    logger.info('[PostBookingWorker] Worker started');
    return workerInstance;
  } catch (error) {
    logger.warn('[PostBookingWorker] Failed to initialize', { error });
    return null;
  }
}

// ── Job Processor ───────────────────────────────────────────────────────

async function processPostBookingJob(job: Job<PostBookingJob>): Promise<void> {
  const {
    organizationId,
    bookingId,
    customerPhone,
    language,
    actionType,
    templateKey,
  } = job.data;

  logger.info('[PostBookingWorker] Processing', {
    actionType,
    organizationId,
    bookingId,
  });

  switch (actionType) {
    case 'send_confirmation':
    case 'send_reminder':
    case 'send_instructions':
    case 'send_documents': {
      // Send a WhatsApp message via the existing service. We import
      // dynamically so the queue module doesn't pull the entire WhatsApp
      // stack into worker bootstrap.
      const { orgWhatsAppKeysService } = await import(
        '@/lib/services/org-whatsapp-keys.service'
      );
      const credentials =
        await orgWhatsAppKeysService.getDecryptedCredentials(organizationId);
      if (!credentials) {
        throw new Error(
          `No WhatsApp credentials for org ${organizationId}`,
        );
      }
      const message = buildMessageForAction(actionType, language, templateKey);
      await sendMetaMessage(
        credentials.phoneNumberId,
        credentials.accessToken,
        customerPhone,
        message,
      );
      break;
    }
    case 'request_review': {
      const { createReviewRequest, sendReviewRequest } = await import(
        '@/lib/services/reviews.service'
      );
      const reviewReq = await createReviewRequest(organizationId, {
        customerName: 'Customer',
        customerPhone,
        channel: 'whatsapp',
        language,
        bookingId,
        source: 'auto_booking',
      });
      if (reviewReq?.id) {
        await sendReviewRequest(organizationId, reviewReq.id);
      }
      break;
    }
  }
}

// ── Helpers ─────────────────────────────────────────────────────────────

function buildMessageForAction(
  actionType: PostBookingActionType,
  language: 'en' | 'ar',
  // templateKey is reserved for future template-driven messages; the
  // current implementation uses simple bilingual fallbacks.
  _templateKey?: string,
): string {
  const isAr = language === 'ar';
  switch (actionType) {
    case 'send_reminder':
      return isAr
        ? 'تذكير بموعدك القادم. نتطلع إلى رؤيتك قريباً!'
        : 'This is a reminder of your upcoming appointment. We look forward to seeing you!';
    case 'send_instructions':
      return isAr
        ? 'تعليمات قبل موعدك ستصلك قريباً.'
        : 'Pre-appointment instructions will follow shortly.';
    case 'send_documents':
      return isAr
        ? 'سنرسل لك المستندات ذات الصلة قريباً.'
        : 'We will send you relevant documents shortly.';
    case 'send_confirmation':
    default:
      return isAr ? 'تم تأكيد حجزك!' : 'Your booking has been confirmed!';
  }
}

async function sendMetaMessage(
  phoneNumberId: string,
  accessToken: string,
  to: string,
  body: string,
): Promise<void> {
  // Strip the "whatsapp:" prefix the AI engine uses internally so the Meta
  // Graph API receives a plain E.164 number.
  const cleanTo = to.replace(/^whatsapp:/, '');
  const url = `https://graph.facebook.com/v19.0/${phoneNumberId}/messages`;
  const res = await fetch(url, {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${accessToken}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      messaging_product: 'whatsapp',
      to: cleanTo,
      type: 'text',
      text: { body },
    }),
  });
  if (!res.ok) {
    const errorText = await res.text().catch(() => '');
    throw new Error(
      `Meta send failed (${res.status}): ${errorText.slice(0, 300)}`,
    );
  }
}
