import { Processor, WorkerHost } from '@nestjs/bullmq';
import { Logger } from '@nestjs/common';
import { Job } from 'bullmq';
import * as crypto from 'crypto';
import { PrismaService } from '../../prisma/prisma.service';
import { WEBHOOK_QUEUE, WebhookDeliveryJob } from './webhook.service';
import { assertResolvesToPublicHost } from './webhook-url.util';

const TIMEOUT_MS = 10_000;

@Processor(WEBHOOK_QUEUE)
export class WebhookProcessor extends WorkerHost {
  private readonly logger = new Logger(WebhookProcessor.name);

  constructor(private readonly prisma: PrismaService) {
    super();
  }

  async process(job: Job<WebhookDeliveryJob>): Promise<void> {
    const deliveryId = job.data.delivery_id;
    const attempt = job.attemptsMade + 1;

    const delivery = await this.prisma.webhook_deliveries.findUnique({
      where: { id: deliveryId },
      include: { endpoint: true },
    });
    if (!delivery) {
      this.logger.warn(`Delivery ${deliveryId} no existe, descartando`);
      return;
    }
    if (delivery.estado === 'entregado') {
      this.logger.log(`Delivery ${deliveryId} ya entregado, skip`);
      return;
    }
    if (!delivery.endpoint.activo) {
      await this.prisma.webhook_deliveries.update({
        where: { id: deliveryId },
        data: {
          estado: 'falla_definitiva',
          last_error: 'Endpoint desactivado',
          delivered_at: new Date(),
        },
      });
      return;
    }

    await this.prisma.webhook_deliveries.update({
      where: { id: deliveryId },
      data: { estado: 'entregando', intentos: attempt },
    });

    const bodyStr = JSON.stringify({
      evento: delivery.evento,
      payload: delivery.payload,
      timestamp: new Date().toISOString(),
      empresa_id: delivery.empresa_id,
    });
    const signature = delivery.endpoint.secreto_hmac
      ? crypto
          .createHmac('sha256', delivery.endpoint.secreto_hmac)
          .update(bodyStr)
          .digest('hex')
      : null;

    try {
      // Anti-SSRF: revalidar la URL y su resolución DNS justo antes del fetch
      // (bloquea IPs privadas/metadata y DNS rebinding; cubre URLs almacenadas
      // antes de este control).
      await assertResolvesToPublicHost(delivery.endpoint.url);

      const controller = new AbortController();
      const timeout = setTimeout(() => controller.abort(), TIMEOUT_MS);

      const resp = await fetch(delivery.endpoint.url, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'X-Cartera-Event': delivery.evento,
          'X-Cartera-Delivery': deliveryId,
          ...(signature ? { 'X-Cartera-Signature': `sha256=${signature}` } : {}),
        },
        body: bodyStr,
        signal: controller.signal,
      }).finally(() => clearTimeout(timeout));

      const respBody = await resp.text().catch(() => '');

      if (resp.ok) {
        await this.prisma.webhook_deliveries.update({
          where: { id: deliveryId },
          data: {
            estado: 'entregado',
            response_code: resp.status,
            response_body: respBody.slice(0, 4000),
            last_error: null,
            delivered_at: new Date(),
          },
        });
        this.logger.log(`Delivery ${deliveryId} OK (${resp.status})`);
        return;
      }

      throw new Error(`HTTP ${resp.status}: ${respBody.slice(0, 200)}`);
    } catch (err: any) {
      const esUltimo = attempt >= (delivery.max_intentos ?? 3);
      await this.prisma.webhook_deliveries.update({
        where: { id: deliveryId },
        data: {
          estado: esUltimo ? 'falla_definitiva' : 'falla_temporal',
          last_error: String(err?.message ?? err).slice(0, 1000),
        },
      });
      this.logger.warn(
        `Delivery ${deliveryId} falló (intento ${attempt}/${delivery.max_intentos}): ${err?.message ?? err}`,
      );
      throw err; // BullMQ reintenta según backoff configurado
    }
  }
}
