import type { Pool } from 'pg';
import { prisma } from '../config/database.js';
import { emailService } from './email/email.service.js';
import { getTenantOwnerEmails, getUserEmailById } from '../utils/memberships.js';
import { env } from '../config/env.js';
import { filterRecipientsByRole, type RecipientWithRole } from './notification-preferences.service.js';
import type { DocumentoSubidoData } from './email/templates/documento-subido.js';
import type { EmailAttachment } from '@horux/core';

/** Límite total de adjuntos para evitar rechazos por SMTP (20 MB). */
const MAX_ATTACHMENT_BYTES = 20 * 1024 * 1024;

/**
 * Notifica a los destinatarios relevantes cuando se sube una declaración
 * o un documento extra. Destinatarios:
 *   - Owners activos del despacho (getTenantOwnerEmails)
 *   - Supervisor del contribuyente (entidades_gestionadas.supervisor_user_id),
 *     si existe y no coincide con un owner ya incluido
 *
 * El uploader mismo SE EXCLUYE (no tiene sentido notificarle su propia acción).
 *
 * Fire-and-forget: el caller hace `.catch()` y esta función no re-lanza.
 * Fail-soft: si SMTP no está configurado, los envíos se loguean a consola
 * vía el transport de @horux/core.
 */
export async function notifyDocumentoSubido(params: {
  pool: Pool;
  tenantId: string;
  contribuyenteId: string | null;
  subidoPor: string;
  kind: DocumentoSubidoData['kind'];
  declaracion?: DocumentoSubidoData['declaracion'];
  declaracionId?: number;
  extra?: DocumentoSubidoData['extra'];
  evidencia?: DocumentoSubidoData['evidencia'];
  /** PDF en base64 para adjuntar en notificaciones de evidencia de obligación. */
  pdfBase64?: string;
}): Promise<void> {
  const { pool, tenantId, contribuyenteId, subidoPor } = params;

  // 1. Datos del contribuyente (desde BD tenant). Sin contribuyenteId no hay
  // subject informativo ni supervisor — skip.
  if (!contribuyenteId) return;



  const { rows } = await pool.query<{
    rfc: string;
    nombre: string;
    supervisor_user_id: string | null;
  }>(
    `SELECT c.rfc, eg.nombre, eg.supervisor_user_id
     FROM contribuyentes c
     JOIN entidades_gestionadas eg ON eg.id = c.entidad_id
     WHERE c.entidad_id = $1`,
    [contribuyenteId.replace(/[^a-f0-9-]/gi, '')],
  );
  if (rows.length === 0) return;
  const contrib = rows[0];

  // 2. Recipients. Owners primero; luego supervisor si aplica.
  const ownerEmails = await getTenantOwnerEmails(tenantId);
  const recipientsWithRole: RecipientWithRole[] = ownerEmails.map(email => ({ email, role: 'owner' }));

  if (contrib.supervisor_user_id) {
    const supervisorEmail = await getUserEmailById(contrib.supervisor_user_id);
    if (supervisorEmail) recipientsWithRole.push({ email: supervisorEmail, role: 'supervisor' });
  }

  // Filtra por preferencias de rol para documento_subido.
  const recipients = new Set(await filterRecipientsByRole(pool, 'documento_subido', recipientsWithRole));

  // Excluir al uploader: no notificarle su propia acción.
  recipients.delete(subidoPor.toLowerCase());
  recipients.delete(subidoPor);

  if (recipients.size === 0) return;

  // 3. Datos del despacho (para mostrar en el body).
  const tenant = await prisma.tenant.findUnique({
    where: { id: tenantId },
    select: { nombre: true },
  });

  // 4. Link al sistema. Usa FRONTEND_URL del env.
  const link = `${env.FRONTEND_URL}/documentos`;

  // Adjuntar los PDFs cuando se trata de una declaración recién creada o de una evidencia de obligación.
  let attachments: EmailAttachment[] | undefined;
  let attachmentsOmitted = false;
  if (params.kind === 'declaracion' && params.declaracionId) {
    const built = await buildDeclaracionAttachments(pool, params.declaracionId);
    attachments = built.attachments;
    attachmentsOmitted = built.omitted;
  } else if (params.kind === 'obligacion_evidencia' && params.pdfBase64 && params.evidencia) {
    const content = Buffer.from(params.pdfBase64, 'base64');
    if (content.length > MAX_ATTACHMENT_BYTES) {
      attachmentsOmitted = true;
      console.warn(`[notifyDocumentoSubido] Evidencia de obligación excede ${MAX_ATTACHMENT_BYTES} bytes (${content.length}). Se envía sin adjunto.`);
    } else {
      attachments = [{ filename: params.evidencia.filename, content }];
    }
  }

  await emailService.sendDocumentoSubido(Array.from(recipients), {
    kind: params.kind,
    subidoPor,
    contribuyenteRfc: contrib.rfc,
    contribuyenteNombre: contrib.nombre,
    despachoNombre: tenant?.nombre,
    declaracion: params.declaracion,
    extra: params.extra,
    evidencia: params.evidencia,
    link,
    attachmentsOmitted,
  }, attachments);
}

async function buildDeclaracionAttachments(
  pool: Pool,
  declaracionId: number,
): Promise<{ attachments?: EmailAttachment[]; omitted: boolean }> {
  const { rows } = await pool.query(
    `SELECT pdf_declaracion, pdf_filename,
            pdf_liga_pago, pdf_liga_pago_filename
     FROM declaraciones_provisionales
     WHERE id = $1`,
    [declaracionId],
  );

  const row = rows[0];
  if (!row) return { omitted: false };

  let totalSize = 0;
  const attachments: EmailAttachment[] = [];

  if (row.pdf_declaracion && row.pdf_filename) {
    const content = Buffer.from(row.pdf_declaracion);
    totalSize += content.length;
    attachments.push({ filename: row.pdf_filename, content });
  }

  if (row.pdf_liga_pago && row.pdf_liga_pago_filename) {
    const content = Buffer.from(row.pdf_liga_pago);
    totalSize += content.length;
    attachments.push({ filename: row.pdf_liga_pago_filename, content });
  }

  if (totalSize > MAX_ATTACHMENT_BYTES) {
    console.warn(`[notifyDocumentoSubido] Adjuntos de declaración ${declaracionId} exceden ${MAX_ATTACHMENT_BYTES} bytes (${totalSize}). Se envía sin adjuntos.`);
    return { omitted: true };
  }

  return { attachments, omitted: false };
}
