import { Worker, type Job } from 'bullmq';
import { env } from '@/lib/env';

import type { ConnectionOptions } from 'bullmq';
import { logger } from '@/lib/logger';
import { attachWorkerAudit } from '@/lib/queue/audit-helpers';
import { QUEUE_CONFIGS } from '@/lib/queue';
import { safeUrl } from '@/lib/email/shell';

/** HTML-escape user-supplied text so notification.description / .title
 *  can't break out of the surrounding `<p>` tag or smuggle <script>. */
function escapeHtml(s: string): string {
  return s
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#39;');
}

export const notificationsWorker = new Worker(
  'notifications',
  async (job: Job) => {
    logger.info({ jobId: job.id, jobName: job.name }, 'Processing notifications job');
    switch (job.name) {
      case 'invoice-overdue-check': {
        const { detectOverdue } = await import('@/lib/services/invoices');
        const { db } = await import('@/lib/db');
        const { ports } = await import('@/lib/db/schema/ports');
        const allPorts = await db.select({ id: ports.id }).from(ports);
        for (const port of allPorts) {
          try {
            await detectOverdue(port.id);
          } catch (err) {
            logger.error({ err, portId: port.id }, 'Overdue detection failed');
          }
        }
        break;
      }
      case 'reminder-check': {
        // Document signing reminders (EOI)
        const { processDocumentReminders } = await import('@/jobs/processors/document-reminder');
        await processDocumentReminders();
        // CRM follow-up reminders (BR-060)
        const { processFollowUpReminders } = await import('@/lib/services/reminders.service');
        await processFollowUpReminders();
        break;
      }
      case 'reminder-overdue-check': {
        const { processOverdueReminders } = await import('@/lib/services/reminders.service');
        await processOverdueReminders();
        break;
      }
      case 'send-notification-email': {
        const { notificationId } = job.data as { notificationId: string };
        const { db } = await import('@/lib/db');
        const { notifications } = await import('@/lib/db/schema/operations');
        const { user } = await import('@/lib/db/schema/users');
        const { eq } = await import('drizzle-orm');
        const { sendEmail } = await import('@/lib/email/index');

        const [notif] = await db
          .select()
          .from(notifications)
          .where(eq(notifications.id, notificationId))
          .limit(1);
        if (!notif) break;

        // Get user email from the Better Auth user table
        const [authUser] = await db
          .select({ email: user.email, name: user.name })
          .from(user)
          .where(eq(user.id, notif.userId))
          .limit(1);
        if (!authUser?.email) break;

        // Subject is set as plain text (not HTML) so escaping isn't
        // needed there, but the body interpolates `notif.description`
        // and `notif.link` into HTML - both attacker-influenceable via
        // any service that enqueues a notification (e.g. document title
        // copied from user-supplied filename, reminder note text).
        const bodyText = escapeHtml(notif.description ?? notif.title);
        const linkHtml = notif.link
          ? `<p><a href="${safeUrl(`${process.env.APP_URL ?? ''}${notif.link}`)}">View in CRM</a></p>`
          : '';

        // Subject prefix = port branding `appName` so multi-tenant
        // deploys read "[Port Amador]"/"[Other Marina]" instead of
        // a hardcoded "[Port Nimara]".
        const { getPortBrandingConfig } = await import('@/lib/services/port-config');
        const portBrand = notif.portId
          ? await getPortBrandingConfig(notif.portId).catch(() => null)
          : null;
        const prefix = portBrand?.appName?.trim() || 'CRM';
        // L7: pass `portId` (6th positional arg) so `getPortEmailConfig`
        // resolves the notification's per-port send-from identity instead
        // of falling back to the global default From. `from`/`text` stay
        // undefined.
        await sendEmail(
          authUser.email,
          `[${prefix}] ${notif.title}`,
          `<p>${bodyText}</p>${linkHtml}`,
          undefined,
          undefined,
          notif.portId ?? undefined,
        );

        await db
          .update(notifications)
          .set({ emailSent: true })
          .where(eq(notifications.id, notificationId));
        break;
      }
      default:
        logger.warn({ jobName: job.name }, 'Unknown notifications job');
    }
  },
  {
    connection: { url: env.REDIS_URL } as ConnectionOptions,
    concurrency: QUEUE_CONFIGS.notifications.concurrency,
  },
);

notificationsWorker.on('failed', (job, err) => {
  logger.error({ jobId: job?.id, jobName: job?.name, err }, 'Notifications job failed');
});

attachWorkerAudit(notificationsWorker, 'notifications');
