import type { Request, Response, NextFunction } from 'express';
import * as mpService from '../services/payment/mercadopago.service.js';
import * as subscriptionService from '../services/payment/subscription.service.js';
import * as invoicingService from '../services/payment/invoicing.service.js';
import * as facturapiService from '../services/facturapi.service.js';
import { handleAddonPayment } from '../services/payment/addon.service.js';
import { prisma } from '../config/database.js';
import { isDespachoPaidPlan } from '@horux/shared';
import { despachoPlanTieneDualidadDb } from '../services/plan-catalogo.service.js';
import { emailService } from '../services/email/email.service.js';
import { getTenantOwnerEmail } from '../utils/memberships.js';

/**
 * Calcula la siguiente fecha de fin de período según la frecuencia.
 * Usa el mismo algoritmo que Mercado Pago: mismo día del mes siguiente,
 * ajustando al último día si el mes destino tiene menos días.
 */
function computeNextPeriodEnd(date: Date, frequency: string): Date {
  const d = new Date(date);
  if (frequency === 'monthly') {
    d.setMonth(d.getMonth() + 1);
  } else if (frequency === 'annual' || frequency === 'yearly') {
    d.setFullYear(d.getFullYear() + 1);
  }
  return d;
}

export async function handleMercadoPagoWebhook(req: Request, res: Response, next: NextFunction) {
  try {
    const { type, data } = req.body;
    const xSignature = req.headers['x-signature'] as string;
    const xRequestId = req.headers['x-request-id'] as string;

    // Verify webhook signature (mandatory)
    if (!xSignature || !xRequestId || !data?.id) {
      console.warn('[WEBHOOK] Missing signature headers');
      return res.status(401).json({ message: 'Missing signature headers' });
    }

    const isValid = mpService.verifyWebhookSignature(xSignature, xRequestId, String(data.id));
    if (!isValid) {
      console.warn('[WEBHOOK] Invalid MercadoPago signature');
      return res.status(401).json({ message: 'Invalid signature' });
    }

    if (type === 'payment') {
      await handlePaymentNotification(String(data.id));
    } else if (type === 'subscription_preapproval') {
      await handlePreapprovalNotification(String(data.id));
    }

    // Always respond 200 to acknowledge receipt
    res.status(200).json({ received: true });
  } catch (error) {
    console.error('[WEBHOOK] Error processing MercadoPago webhook:', error);
    // Still respond 200 to prevent retries for processing errors
    res.status(200).json({ received: true, error: 'processing_error' });
  }
}

async function handlePaymentNotification(paymentId: string) {
  const payment = await mpService.getPaymentDetails(paymentId);

  if (!payment.externalReference) {
    console.warn('[WEBHOOK] Payment without external_reference:', paymentId);
    return;
  }

  // Detecta compras de paquete de timbres. external_reference = `timbres-pack:{paymentId}`
  if (payment.externalReference.startsWith('timbres-pack:')) {
    const localPaymentId = payment.externalReference.split(':')[1];
    if (!localPaymentId) {
      console.warn('[WEBHOOK] external_reference timbres-pack malformado:', payment.externalReference);
      return;
    }

    // Capturar estado previo para detectar transición (idempotencia: MP puede
    // mandar el mismo webhook múltiples veces — solo notificamos en cambio real).
    const before = await prisma.payment.findUnique({
      where: { id: localPaymentId },
      select: { status: true, tenantId: true, amount: true },
    });
    const previousStatus = before?.status ?? null;

    await prisma.payment.update({
      where: { id: localPaymentId },
      data: {
        status: payment.status || 'unknown',
        mpPaymentId: paymentId,
        paidAt: payment.status === 'approved' ? new Date() : null,
      },
    });

    if (payment.status === 'approved') {
      try {
        await facturapiService.activarPaqueteTrasPago(localPaymentId);
      } catch (error: any) {
        console.error('[WEBHOOK] Error activando paquete de timbres:', error.message);
        throw error; // que MP reintente
      }
      // Auto-emisión de factura (fail-soft)
      await invoicingService.emitInvoiceIfApplicable(localPaymentId);
    } else if (
      (payment.status === 'rejected' || payment.status === 'cancelled') &&
      previousStatus !== payment.status &&
      before
    ) {
      // Compra de paquete de timbres falló — el owner pagó y MP rechazó. Aviso fail-soft.
      const tenant = await prisma.tenant.findUnique({ where: { id: before.tenantId }, select: { nombre: true } });
      const ownerEmail = await getTenantOwnerEmail(before.tenantId);
      if (tenant && ownerEmail) {
        emailService.sendPaymentFailed(ownerEmail, {
          nombre: tenant.nombre,
          amount: Number(before.amount),
          plan: 'Paquete de timbres',
        }).catch(err => console.error('[EMAIL] timbres-pack failed notification:', err));
      }
    }

    if (typeof process.send === 'function') {
      const pay = await prisma.payment.findUnique({ where: { id: localPaymentId }, select: { tenantId: true } });
      if (pay) process.send({ type: 'invalidate-tenant-cache', tenantId: pay.tenantId });
    }
    return;
  }

  // Detecta pagos de prorateo (upgrade). external_reference = `proration:${tenantId}:${subscriptionId}`
  if (payment.externalReference.startsWith('proration:')) {
    const parts = payment.externalReference.split(':');
    const tenantId = parts[1];
    const subscriptionId = parts[2];
    if (!tenantId || !subscriptionId) {
      console.warn('[WEBHOOK] external_reference de proration malformado:', payment.externalReference);
      return;
    }

    const paymentRecord = await subscriptionService.recordPayment({
      tenantId,
      subscriptionId,
      mpPaymentId: paymentId,
      amount: payment.transactionAmount || 0,
      status: payment.status || 'unknown',
      paymentMethod: `proration-${payment.paymentMethodId || 'unknown'}`,
    });

    if (payment.status === 'approved') {
      try {
        await subscriptionService.applyApprovedUpgrade(subscriptionId);
      } catch (error: any) {
        // Re-lanza para que MP reintente el webhook
        console.error('[WEBHOOK] Error aplicando upgrade:', error.message);
        throw error;
      }
      // Auto-emisión de factura (fail-soft, no bloquea ni tira)
      await invoicingService.emitInvoiceIfApplicable(paymentRecord.id);
    }

    if (typeof process.send === 'function') {
      process.send({ type: 'invalidate-tenant-cache', tenantId });
    }
    return;
  }

  // Detecta pagos de addon. external_reference = `addon:{subscriptionAddonId}`
  if (payment.externalReference.startsWith('addon:')) {
    const addonId = payment.externalReference.replace('addon:', '');
    if (!addonId) {
      console.warn('[WEBHOOK] external_reference addon malformado:', payment.externalReference);
      return;
    }
    await handleAddonPayment(addonId, String(paymentId), payment.status || 'unknown');
    // Continue to normal flow only if we have a subscription to record against.
    // Addon payments are fully handled by handleAddonPayment; no further action needed.
    return;
  }

  // Detecta pagos únicos de suscripción anual (planes >$10k). external_reference = `subscription:${tenantId}:${subscriptionId}`
  if (payment.externalReference.startsWith('subscription:')) {
    const parts = payment.externalReference.split(':');
    const tenantId = parts[1];
    const subscriptionId = parts[2];
    if (!tenantId || !subscriptionId) {
      console.warn('[WEBHOOK] external_reference de subscription malformado:', payment.externalReference);
      return;
    }

    const paymentRecord = await subscriptionService.recordPayment({
      tenantId,
      subscriptionId,
      mpPaymentId: paymentId,
      amount: payment.transactionAmount || 0,
      status: payment.status || 'unknown',
      paymentMethod: payment.paymentMethodId || 'unknown',
    });

    if (payment.status === 'approved') {
      const subscription = await prisma.subscription.findUnique({ where: { id: subscriptionId } });
      if (subscription) {
        const now = new Date();
        const periodEnd = computeNextPeriodEnd(now, 'annual');
        await prisma.$transaction([
          prisma.subscription.update({
            where: { id: subscription.id },
            data: {
              status: 'authorized',
              currentPeriodStart: now,
              currentPeriodEnd: periodEnd,
            },
          }),
          prisma.tenant.update({
            where: { id: tenantId },
            data: { plan: subscription.plan },
          }),
        ]);
        subscriptionService.invalidateSubscriptionCache(tenantId);
        console.log(`[WEBHOOK] Suscripción ${subscriptionId} activada por pago único anual hasta ${periodEnd.toISOString()}`);
      }
      // Auto-emisión de factura (fail-soft)
      await invoicingService.emitInvoiceIfApplicable(paymentRecord.id);
    }

    if (typeof process.send === 'function') {
      process.send({ type: 'invalidate-tenant-cache', tenantId });
    }
    return;
  }

  // Flujo normal: pago recurrente del preapproval
  const tenantId = payment.externalReference;
  const subscription = await prisma.subscription.findFirst({
    where: { tenantId },
    orderBy: { createdAt: 'desc' },
  });

  if (!subscription) {
    console.warn('[WEBHOOK] No subscription found for tenant:', tenantId);
    return;
  }

  const paymentRecord = await subscriptionService.recordPayment({
    tenantId,
    subscriptionId: subscription.id,
    mpPaymentId: paymentId,
    amount: payment.transactionAmount || 0,
    status: payment.status || 'unknown',
    paymentMethod: payment.paymentMethodId || 'unknown',
  });

  if (payment.status === 'approved') {
    // Transición pending → authorized es el momento del *primer* pago aprobado.
    // En planes despacho con dualidad de precio (firstYear > renewal), bajamos
    // el monto recurrente del preapproval para que las renovaciones cobren el
    // precio de renewal. Se detecta comparando el monto cobrado contra lo que
    // `getPlanPrice(phase='firstYear')` devolvería para este plan.
    const esPrimerPago = subscription.status === 'pending';
    const updateData: { status: string; currentPeriodStart?: Date; currentPeriodEnd?: Date } = { status: 'authorized' };

    if (esPrimerPago) {
      // El primer pago aprobado define el inicio del período activo.
      // Algunos flujos (cambio de plan, creación manual) dejan currentPeriodEnd
      // en null, así que lo establecemos aquí para evitar que la suscripción
      // aparezca vencida aunque esté authorized.
      const periodStart = payment.dateApproved ? new Date(payment.dateApproved) : new Date();
      updateData.currentPeriodStart = periodStart;
      updateData.currentPeriodEnd = computeNextPeriodEnd(periodStart, subscription.frequency);
      console.log(`[WEBHOOK] Subscription ${subscription.id} primer pago aprobado: período ${updateData.currentPeriodStart.toISOString()} → ${updateData.currentPeriodEnd.toISOString()} (${subscription.frequency})`);
    } else if (subscription.currentPeriodEnd) {
      // Extender currentPeriodEnd para renovaciones recurrentes.
      const nextPeriodEnd = computeNextPeriodEnd(subscription.currentPeriodEnd, subscription.frequency);
      updateData.currentPeriodEnd = nextPeriodEnd;
      console.log(`[WEBHOOK] Subscription ${subscription.id} extended to ${nextPeriodEnd.toISOString()} (${subscription.frequency})`);
    }

    await prisma.subscription.update({
      where: { id: subscription.id },
      data: updateData,
    });
    subscriptionService.invalidateSubscriptionCache(tenantId);

    if (
      esPrimerPago &&
      subscription.mpPreapprovalId &&
      isDespachoPaidPlan(subscription.plan) &&
      await despachoPlanTieneDualidadDb(subscription.plan)
    ) {
      try {
        const renewalAmount = await subscriptionService.getPlanPrice(
          subscription.plan as any,
          subscription.frequency as any,
          'renewal',
        );
        await mpService.updatePreapprovalAmount(subscription.mpPreapprovalId, renewalAmount);
        await prisma.subscription.update({
          where: { id: subscription.id },
          data: { amount: renewalAmount },
        });
        subscriptionService.invalidateSubscriptionCache(tenantId);
        console.log(`[WEBHOOK] Preapproval ${subscription.mpPreapprovalId} bajado a $${renewalAmount} (renewal) tras primer pago`);
      } catch (err: any) {
        // No fallar el webhook — el cobro ya pasó. Logear para intervención manual.
        console.error(`[WEBHOOK] Error bajando preapproval a renewal:`, err?.message || err);
      }
    }

    // Auto-emisión de factura (fail-soft, no bloquea ni tira)
    await invoicingService.emitInvoiceIfApplicable(paymentRecord.id);
  }

  if (typeof process.send === 'function') {
    process.send({ type: 'invalidate-tenant-cache', tenantId });
  }
}

async function handlePreapprovalNotification(preapprovalId: string) {
  const preapproval = await mpService.getPreapproval(preapprovalId);

  if (preapproval.status) {
    await subscriptionService.updateSubscriptionStatus(preapprovalId, preapproval.status);
  }

  // Broadcast cache invalidation
  const subscription = await prisma.subscription.findFirst({
    where: { mpPreapprovalId: preapprovalId },
  });
  if (subscription && typeof process.send === 'function') {
    process.send({ type: 'invalidate-tenant-cache', tenantId: subscription.tenantId });
  }
}
