/**
 * Auto-facturación de pagos de suscripción.
 *
 * Cada vez que MercadoPago confirma un pago (webhook `payment.approved`), este
 * servicio emite automáticamente un CFDI al público en general vía Facturapi,
 * usando la organización de Horux 360 como emisor.
 *
 * Reglas:
 *   - El **primer pago** aprobado de cada tenant NO se factura automáticamente —
 *     el admin lo hace manualmente para verificar/capturar los datos fiscales del
 *     cliente. Los pagos subsecuentes sí van auto a público en general.
 *   - Trials (amount=0) no se facturan.
 *   - Idempotente: si `Payment.facturapiInvoiceId` ya existe, skip.
 *   - Si Facturapi falla (API down, CSD inválido), se logea el error pero NO se
 *     tira el webhook — `facturapiInvoiceId` queda null y el admin puede re-emitir
 *     manualmente después. Esto evita que MP reintente el webhook y que se
 *     dupliquen registros de Payment.
 *
 * Emisor: Horux 360 (RFC HTS240708LJA, RESICO PM, régimen 626, sin retenciones).
 * Receptor: PUBLICO EN GENERAL (XAXX010101000, régimen 616).
 * Concepto: clave prod/serv 81112502 (Servicios de alojamiento de aplicaciones).
 */
import { prisma } from '../../config/database.js';
import * as facturapiService from '../facturapi.service.js';
import { GLOBAL_ADMIN_RFC } from '@horux/shared';
import { auditLog } from '../../utils/audit.js';
import { getTenantOwnerEmail } from '../../utils/memberships.js';

// Constantes de facturación — ajustar aquí si cambia la convención
const CONCEPT_PRODUCT_KEY = '81112502'; // Servicios de alojamiento de aplicaciones
const CONCEPT_UNIT_KEY = 'E48';          // Unidad de servicio
const CONCEPT_UNIT_NAME = 'Servicio';
// Fallback público en general — se usa cuando el tenant pagador no tiene
// suficientes datos fiscales (sin CSF cargada, sin domicilio, etc.).
const FALLBACK_TAX_ID = 'XAXX010101000';
const FALLBACK_LEGAL_NAME = 'PUBLICO EN GENERAL';
const FALLBACK_TAX_SYSTEM = '616';       // Sin obligaciones fiscales
const FALLBACK_USE_CFDI = 'S01';         // Sin efectos fiscales
// Default cuando facturamos con datos reales del cliente — gastos en general.
// Fase 2 hará esto configurable por tenant.
const DEFAULT_USE_CFDI = 'G03';
const IVA_RATE = 0.16;

// Mapeo MP payment_method → SAT forma_pago. Conservador: por default TEF (03).
const FORMA_PAGO_POR_METHOD: Record<string, string> = {
  credit_card: '04',     // Tarjeta de crédito
  debit_card: '28',      // Tarjeta de débito
  account_money: '03',   // Transferencia (MP wallet)
  bank_transfer: '03',
};

const PLAN_LABELS: Record<string, string> = {
  trial: 'Trial',
  custom: 'Custom',
  mi_empresa: 'Mi Empresa',
  mi_empresa_plus: 'Mi Empresa Plus',
  business_control: 'Business Control',
  business_cloud: 'Enterprise',
};

/**
 * Cuenta si este tenant ya tuvo un pago aprobado antes del actual.
 * Si no hay ninguno, es el primer pago → devolvemos true (skip auto-emit).
 */
async function isFirstApprovedPayment(
  tenantId: string,
  excludePaymentId: string,
): Promise<boolean> {
  const count = await prisma.payment.count({
    where: {
      tenantId,
      status: 'approved',
      id: { not: excludePaymentId },
    },
  });
  return count === 0;
}

/**
 * Busca el tenant emisor (Horux 360) con su organización Facturapi configurada.
 * Si falta, lanza error — el admin global tiene que crear la organización primero.
 */
export async function getEmitterTenant() {
  const tenant = await prisma.tenant.findUnique({
    where: { rfc: GLOBAL_ADMIN_RFC },
    select: {
      id: true,
      nombre: true,
      rfc: true,
      codigoPostal: true,
      facturapiOrgId: true,
    },
  });
  if (!tenant) {
    throw new Error(`Tenant emisor (RFC ${GLOBAL_ADMIN_RFC}) no existe — corre pnpm bootstrap:admin-global`);
  }
  if (!tenant.facturapiOrgId) {
    throw new Error(`Tenant emisor no tiene organización Facturapi — configúrala en /configuracion`);
  }
  if (!tenant.codigoPostal) {
    throw new Error(`Tenant emisor no tiene código postal — configúralo en /configuracion/domicilio-fiscal`);
  }
  return tenant;
}

/**
 * Datos fiscales del receptor para la factura. `null` si no hay datos suficientes
 * (RFC + razón social + CP + régimen) — el caller cae a público en general.
 */
interface CustomerData {
  legalName: string;
  taxId: string;
  taxSystem: string;
  email: string;
  zip: string;
}

/**
 * Resuelve los datos fiscales del receptor desde el tenant que paga.
 * Requiere CSF sincronizada (régimen) + domicilio fiscal (CP).
 *
 * Heurística cuando hay múltiples regímenes activos: usa el más antiguo
 * (primer regímen agregado al tenant). Fase 2 lo hará configurable.
 *
 * Retorna `null` si falta cualquier dato requerido — el caller debe caer
 * a público en general en ese caso.
 */
export async function getCustomerFromTenant(payerTenantId: string): Promise<CustomerData | null> {
  const tenant = await prisma.tenant.findUnique({
    where: { id: payerTenantId },
    select: {
      nombre: true,
      rfc: true,
      codigoPostal: true,
      factPreferencia: true,
      factRegimenPreferido: true,
    },
  });
  if (!tenant) return null;
  // Si el cliente eligió "público en general" explícitamente, respetar.
  if (tenant.factPreferencia === 'publico_general') return null;
  if (!tenant.rfc || !tenant.nombre || !tenant.codigoPostal) return null;

  // Régimen fiscal: si el tenant configuró uno preferido, usar ese (validar
  // que sigue activo). Si no, heurística "primer activo por createdAt".
  let regimenClave: string | null = null;
  if (tenant.factRegimenPreferido) {
    const activo = await prisma.tenantRegimenActivo.findFirst({
      where: {
        tenantId: payerTenantId,
        regimen: { clave: tenant.factRegimenPreferido },
      },
      include: { regimen: true },
    });
    if (activo) regimenClave = activo.regimen.clave;
  }
  if (!regimenClave) {
    const regimenActivo = await prisma.tenantRegimenActivo.findFirst({
      where: { tenantId: payerTenantId },
      include: { regimen: true },
      orderBy: { createdAt: 'asc' },
    });
    if (!regimenActivo) return null;
    regimenClave = regimenActivo.regimen.clave;
  }

  const email = await getTenantOwnerEmail(payerTenantId);

  return {
    legalName: tenant.nombre.toUpperCase(),
    taxId: tenant.rfc.toUpperCase(),
    taxSystem: regimenClave,
    email: email || '',
    zip: tenant.codigoPostal,
  };
}

/**
 * Construye el payload para Facturapi. Acepta customer real (datos del cliente)
 * o fallback a público en general si `customer` es null.
 */
export function buildInvoicePayload(params: {
  amount: number;
  description: string; // Texto del concepto — varía por kind (subscription vs timbres)
  emitterCp: string;
  paymentMethod: string | null;
  customer: CustomerData | null;
  usoCfdi: string;     // Resuelto por el caller según preferencia del tenant
}) {
  const description = params.description;

  // Normaliza método de pago MP → código SAT. Default 03 (TEF) si no mapea.
  const normalizedMethod = params.paymentMethod?.toLowerCase().replace(/^proration-/, '') || '';
  const formaPago = FORMA_PAGO_POR_METHOD[normalizedMethod] || '03';

  const useCustomerData = params.customer !== null;
  const customerPayload = useCustomerData
    ? {
        legalName: params.customer!.legalName,
        taxId: params.customer!.taxId,
        taxSystem: params.customer!.taxSystem,
        email: params.customer!.email,
        zip: params.customer!.zip,
      }
    : {
        legalName: FALLBACK_LEGAL_NAME,
        taxId: FALLBACK_TAX_ID,
        taxSystem: FALLBACK_TAX_SYSTEM,
        email: '',
        zip: params.emitterCp,
      };

  return {
    customer: customerPayload as any,
    items: [
      {
        description,
        productKey: CONCEPT_PRODUCT_KEY,
        unitKey: CONCEPT_UNIT_KEY,
        unitName: CONCEPT_UNIT_NAME,
        quantity: 1,
        price: params.amount,           // Ya incluye IVA
        taxIncluded: true,              // Facturapi desagrega subtotal + IVA 16%
        taxes: [
          { type: 'IVA', rate: IVA_RATE, factor: 'Tasa' },
          // RESICO PM → sin retenciones
        ],
      },
    ],
    use: params.usoCfdi,
    paymentForm: formaPago,
    paymentMethod: 'PUE',
    currency: 'MXN',
  } as facturapiService.FacturapiInvoiceData;
}

/**
 * Entry point. Se llama desde el webhook de MP cuando un pago se confirma.
 * Todas las validaciones son fail-soft: loggear y retornar silenciosamente.
 */
export async function emitInvoiceIfApplicable(paymentId: string): Promise<void> {
  try {
    const payment = await prisma.payment.findUnique({
      where: { id: paymentId },
      include: { subscription: true },
    });

    if (!payment) {
      console.warn(`[Invoicing] Payment ${paymentId} no existe`);
      return;
    }

    // Gate 1: ya facturado (idempotencia)
    if (payment.facturapiInvoiceId) {
      console.log(`[Invoicing] Payment ${paymentId} ya facturado (${payment.facturapiInvoiceId}), skip`);
      return;
    }

    // Gate 2: status
    if (payment.status !== 'approved') {
      console.log(`[Invoicing] Payment ${paymentId} status=${payment.status}, skip (sólo approved se factura)`);
      return;
    }

    // Gate 3: amount
    const amount = Number(payment.amount);
    if (!(amount > 0)) {
      console.log(`[Invoicing] Payment ${paymentId} amount=${amount}, skip (trial o cero)`);
      return;
    }

    // Gate 4: primer pago del tenant → manual
    if (await isFirstApprovedPayment(payment.tenantId, payment.id)) {
      console.log(`[Invoicing] Payment ${paymentId} es el PRIMER pago aprobado del tenant ${payment.tenantId}, skip (factura manual)`);
      // Notificar al admin global para que emita la factura manualmente
      const tenant = await prisma.tenant.findUnique({
        where: { id: payment.tenantId },
        select: { nombre: true, rfc: true },
      });
      if (tenant) {
        const { emailService } = await import('../email/email.service.js');
        emailService.sendPrimerPagoFacturar({
          clienteNombre: tenant.nombre,
          clienteRfc: tenant.rfc || '',
          amount,
          plan: payment.subscription?.plan || 'custom',
          paymentDate: payment.paidAt?.toISOString() || new Date().toISOString(),
          paymentId: payment.id,
        }).catch(err => console.error('[Invoicing] Error enviando notificación de primer pago:', err));
      }
      return;
    }

    // Gate 5: emisor configurado
    const emitter = await getEmitterTenant();

    // Construir payload. El concepto varía por tipo de pago:
    //  - subscription: "Suscripción {plan} {freq} a Horux 360"
    //  - timbres_pack: "{cantidad} timbres adicionales — Horux 360"
    let description: string;
    let auditMetadata: Record<string, any>;

    if (payment.kind === 'timbres_pack') {
      // Recupera cantidad del paquete — vinculado 1:1 con Payment
      const paquete = await prisma.timbrePaquete.findUnique({
        where: { paymentId: payment.id },
      });
      const cantidad = paquete?.cantidad ?? 0;
      description = `${cantidad.toLocaleString('es-MX')} timbres adicionales — Horux Despachos`;
      auditMetadata = { cantidad, amount, kind: 'timbres_pack' };
    } else {
      const plan = payment.subscription?.plan || 'trial';
      const frequency = payment.subscription?.frequency || 'monthly';
      const descFrecuencia = frequency === 'annual' ? 'anual' : 'mensual';
      description = `Suscripción ${PLAN_LABELS[plan] || plan} ${descFrecuencia} a Horux Despachos`;
      auditMetadata = { amount, plan, frequency, kind: 'subscription' };
    }

    // Resuelve customer real si el tenant pagador tiene CSF + domicilio +
    // preferencia 'mis_datos'. Si no, null → buildInvoicePayload cae a público
    // en general como fallback seguro.
    const customer = await getCustomerFromTenant(payment.tenantId);
    if (!customer) {
      console.log(`[Invoicing] Tenant ${payment.tenantId} sin datos fiscales completos o preferencia=publico_general. Facturando a Público en General.`);
    }

    // Lee uso CFDI preferido del tenant (default G03 ya cargado en BD via default).
    const tenantPref = await prisma.tenant.findUnique({
      where: { id: payment.tenantId },
      select: { factUsoCfdi: true },
    });
    const usoCfdi = customer ? (tenantPref?.factUsoCfdi || DEFAULT_USE_CFDI) : FALLBACK_USE_CFDI;

    const payload = buildInvoicePayload({
      amount,
      description,
      emitterCp: emitter.codigoPostal!,
      paymentMethod: payment.paymentMethod,
      customer,
      usoCfdi,
    });

    console.log(`[Invoicing] Emitiendo factura para Payment ${paymentId} (tenant ${payment.tenantId}, $${amount}, receptor=${customer?.taxId || FALLBACK_TAX_ID})`);
    const invoice = await facturapiService.createInvoice(emitter.id, payload);

    await prisma.payment.update({
      where: { id: payment.id },
      data: { facturapiInvoiceId: invoice.id },
    });

    // Enviar factura por email al cliente cuando se factura con datos reales
    // (no público en general). Fail-soft: si el envío falla, no bloquea.
    if (customer?.email) {
      try {
        await facturapiService.sendInvoiceByEmail(emitter.id, invoice.id, customer.email);
        console.log(`[Invoicing] Factura ${invoice.id} enviada a ${customer.email}`);
      } catch (emailErr: any) {
        console.error(`[Invoicing] Error enviando factura ${invoice.id} a ${customer.email}:`, emailErr.message || emailErr);
      }
    }

    auditLog({
      tenantId: payment.tenantId,
      action: 'invoice.emitted_auto',
      entityType: 'Payment',
      entityId: payment.id,
      metadata: {
        facturapiInvoiceId: invoice.id,
        ...auditMetadata,
      },
    });

    console.log(`[Invoicing] Factura ${invoice.id} emitida y vinculada a Payment ${paymentId}`);
  } catch (error: any) {
    // Fail-soft: log y retorno silencioso. El admin puede re-emitir manualmente.
    console.error(`[Invoicing] Error emitiendo factura para Payment ${paymentId}:`, error.message || error);
  }
}
