import Facturapi from 'facturapi';
import { env } from '../config/env.js';
import { prisma } from '../config/database.js';
import * as mpService from './payment/mercadopago.service.js';
import { getTenantOwnerEmail } from '../utils/memberships.js';
import { encryptString, decryptToString } from './sat/sat-crypto.service.js';

/**
 * Cliente Facturapi con User Key (nivel cuenta, para gestión de organizaciones).
 */
function getUserClient(): Facturapi {
  if (!env.FACTURAPI_USER_KEY) {
    throw new Error('FACTURAPI_USER_KEY no configurada');
  }
  return new Facturapi(env.FACTURAPI_USER_KEY);
}

/**
 * Genera una Live Secret Key vía PUT idempotente. Devuelve la existente
 * si la org ya tiene una; crea nueva si no.
 */
async function generateLiveKey(orgId: string): Promise<string> {
  const userKey = env.FACTURAPI_USER_KEY!;
  const res = await fetch(`https://www.facturapi.io/v2/organizations/${orgId}/apikeys/live`, {
    method: 'PUT',
    headers: {
      'Authorization': `Bearer ${userKey}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({}),
  });
  if (!res.ok) {
    const errBody = await res.text().catch(() => '');
    throw new Error(`Facturapi PUT /apikeys/live falló (${res.status}): ${errBody}`);
  }
  const key = (await res.text()).replace(/"/g, '').trim();
  if (!key.startsWith('sk_live_')) {
    throw new Error(`Respuesta inesperada de Facturapi (no es sk_live_*): ${key.slice(0, 10)}...`);
  }
  return key;
}

/**
 * Cliente Facturapi con la Live Secret Key de la organización del tenant.
 * Cache cifrada en `Tenant.facturapiOrgKeyEnc/Iv/Tag` (AES-256-GCM con
 * derivación FIEL_ENCRYPTION_KEY). Si no hay cache, genera vía PUT y persiste.
 */
async function getOrgClient(tenantId: string): Promise<Facturapi> {
  const tenant = await prisma.tenant.findUnique({
    where: { id: tenantId },
    select: {
      facturapiOrgId: true,
      facturapiOrgKeyEnc: true,
      facturapiOrgKeyIv: true,
      facturapiOrgKeyTag: true,
    },
  });

  if (!tenant?.facturapiOrgId) {
    throw new Error('Tenant no tiene organización Facturapi configurada');
  }

  // 1. Reutilizar Live Secret Key cacheada (descifrar de BD).
  if (tenant.facturapiOrgKeyEnc && tenant.facturapiOrgKeyIv && tenant.facturapiOrgKeyTag) {
    const apiKey = decryptToString(tenant.facturapiOrgKeyEnc, tenant.facturapiOrgKeyIv, tenant.facturapiOrgKeyTag);
    return new Facturapi(apiKey);
  }

  // 2. Generar Live Secret Key vía PUT y persistir cifrada (lazy fallback
  // para tenants legacy creados antes del refactor live).
  const apiKey = await generateLiveKey(tenant.facturapiOrgId);
  const { encrypted, iv, tag } = encryptString(apiKey);
  await prisma.tenant.update({
    where: { id: tenantId },
    data: {
      facturapiOrgKeyEnc: encrypted,
      facturapiOrgKeyIv: iv,
      facturapiOrgKeyTag: tag,
    },
  });
  return new Facturapi(apiKey);
}

// ============================================
// Organizaciones
// ============================================

export async function createOrganization(tenantId: string): Promise<{ orgId: string }> {
  const tenant = await prisma.tenant.findUnique({
    where: { id: tenantId },
    select: { nombre: true, rfc: true, facturapiOrgId: true },
  });

  if (!tenant) throw new Error('Tenant no encontrado');
  if (tenant.facturapiOrgId) throw new Error('Tenant ya tiene organización Facturapi');

  const client = getUserClient();
  const org = await client.organizations.create({ name: tenant.nombre });

  // Eager: generar Live Secret Key inmediatamente y persistirla cifrada
  // para que la org quede lista para emitir desde el primer momento sin un
  // PUT extra al primer emit.
  const apiKey = await generateLiveKey(org.id);
  const { encrypted, iv, tag } = encryptString(apiKey);

  await prisma.tenant.update({
    where: { id: tenantId },
    data: {
      facturapiOrgId: org.id,
      facturapiOrgKeyEnc: encrypted,
      facturapiOrgKeyIv: iv,
      facturapiOrgKeyTag: tag,
    },
  });

  return { orgId: org.id };
}

export async function getOrganizationStatus(tenantId: string): Promise<{
  configured: boolean;
  orgId?: string;
  legalName?: string;
  hasCsd?: boolean;
}> {
  const tenant = await prisma.tenant.findUnique({
    where: { id: tenantId },
    select: { facturapiOrgId: true },
  });

  if (!tenant?.facturapiOrgId) {
    return { configured: false };
  }

  try {
    const client = getUserClient();
    const org = await client.organizations.retrieve(tenant.facturapiOrgId);

    return {
      configured: true,
      orgId: org.id,
      legalName: org.legal?.name || undefined,
      hasCsd: !!org.certificate?.has_certificate,
    };
  } catch {
    return { configured: false };
  }
}

// ============================================
// CSD (Certificado de Sello Digital)
// ============================================

export async function uploadCsd(
  tenantId: string,
  cerFile: string, // base64
  keyFile: string, // base64
  password: string
): Promise<{ success: boolean; message: string }> {
  const tenant = await prisma.tenant.findUnique({
    where: { id: tenantId },
    select: { facturapiOrgId: true },
  });

  if (!tenant?.facturapiOrgId) {
    throw new Error('Primero debe crearse la organización en Facturapi');
  }

  const client = getUserClient();

  try {
    await client.organizations.uploadCertificate(
      tenant.facturapiOrgId,
      Buffer.from(cerFile, 'base64'),
      Buffer.from(keyFile, 'base64'),
      password,
    );

    return { success: true, message: 'CSD subido correctamente' };
  } catch (error: any) {
    return { success: false, message: error.message || 'Error al subir CSD' };
  }
}

// ============================================
// Clientes
// ============================================

export interface FacturapiCustomerData {
  legalName: string;
  taxId: string; // RFC o Tax ID extranjero
  taxSystem?: string; // clave régimen fiscal (no aplica para extranjeros)
  email?: string;
  zip: string;
  country?: string; // ISO 3166 alpha-3 (solo extranjeros, ej: USA, SWE)
}

export async function createOrUpdateCustomer(
  tenantId: string,
  data: FacturapiCustomerData
): Promise<string> {
  const client = await getOrgClient(tenantId);

  // Buscar si ya existe por búsqueda de texto
  let existingId: string | null = null;
  try {
    const existing = await client.customers.list({ search: data.taxId });
    if (existing.data && existing.data.length > 0) {
      const match = existing.data.find((c: any) => c.tax_id === data.taxId);
      if (match) existingId = match.id;
    }
  } catch { /* no existing */ }

  const isForiegn = !!data.country && data.country !== 'MEX';

  if (existingId) {
    const updateData: any = {
      legal_name: data.legalName,
      email: data.email,
      address: { zip: data.zip, ...(isForiegn ? { country: data.country } : {}) },
    };
    if (!isForiegn && data.taxSystem) updateData.tax_system = data.taxSystem;
    await client.customers.update(existingId, updateData);
    return existingId;
  }

  const createData: any = {
    legal_name: data.legalName,
    email: data.email,
    address: { zip: data.zip, ...(isForiegn ? { country: data.country } : {}) },
  };
  if (isForiegn) {
    createData.tax_id = data.taxId; // Tax ID extranjero (NumRegIdTrib)
  } else {
    createData.tax_id = data.taxId; // RFC mexicano
    if (data.taxSystem) createData.tax_system = data.taxSystem;
  }

  const customer = await client.customers.create(createData);

  return customer.id;
}

// ============================================
// Facturas (Emisión)
// ============================================

export interface FacturapiLineItem {
  description: string;
  productKey: string; // ClaveProdServ SAT
  unitKey?: string; // ClaveUnidad SAT
  unitName?: string;
  quantity: number;
  price: number;
  taxIncluded?: boolean;
  taxes?: Array<{
    type: string; // 'IVA', 'ISR', 'IEPS'
    rate: number; // 0.16, 0.10, etc.
    factor?: string; // 'Tasa', 'Cuota', 'Exento'
    withholding?: boolean; // true = retención, false/undefined = traslado
  }>;
}

export interface FacturapiInvoiceData {
  // Receptor
  customer: FacturapiCustomerData;
  // Conceptos
  items: FacturapiLineItem[];
  // Campos CFDI
  use: string; // UsoCFDI: G01, G03, etc.
  paymentForm: string; // FormaPago: 01, 03, 28, etc.
  paymentMethod?: string; // MetodoPago: PUE, PPD
  currency?: string; // MXN, USD
  exchangeRate?: number;
  // Opcionales
  series?: string;
  folioNumber?: number;
  conditions?: string;
  /**
   * Documentos CFDI relacionados. Estructura SAT 4.0: una entrada por tipo
   * de relación, agrupando N UUIDs. Facturapi en modo Live valida la
   * estructura estricta — el formato {uuid, relationship} suelto es rechazado.
   */
  relatedDocuments?: Array<{ relationship: string; uuids: string[] }>;
  /**
   * Régimen fiscal del emisor (override del default de la organización).
   * Requerido cuando el contribuyente tiene múltiples régimenes y Facturapi
   * necesita saber cuál usar para esta factura específica. Se envía como
   * `issuer.tax_system` a Facturapi.
   */
  issuerTaxSystem?: string;
}

export async function createInvoice(
  tenantId: string,
  data: FacturapiInvoiceData
): Promise<any> {
  const client = await getOrgClient(tenantId);

  // Crear/actualizar cliente en Facturapi
  const customerId = await createOrUpdateCustomer(tenantId, data.customer);

  const tipo = (data as any).type || 'I';
  const invoiceData: any = { customer: customerId };

  // Tipo de comprobante
  if (tipo !== 'I') invoiceData.type = tipo;

  // Items (solo para I, E, T — P no lleva conceptos)
  if (data.items?.length) {
    invoiceData.items = data.items.map(item => ({
      quantity: item.quantity,
      product: {
        description: item.description,
        product_key: item.productKey,
        unit_key: item.unitKey || 'E48',
        unit_name: item.unitName || 'Servicio',
        price: item.price,
        tax_included: item.taxIncluded ?? false,
        taxes: item.taxes?.map(t => ({
          type: t.type,
          rate: t.rate,
          factor: t.factor || 'Tasa',
          ...(t.withholding ? { withholding: true } : {}),
        })) || [{ type: 'IVA', rate: 0.16 }],
      },
      ...((data as any).cuentaPredial ? { property_tax_account: (data as any).cuentaPredial } : {}),
    }));
  }

  // Campos del comprobante (no aplican para tipo P)
  if (tipo === 'I' || tipo === 'E') {
    invoiceData.use = data.use || 'G01';
    invoiceData.payment_form = data.paymentForm || '99';
    invoiceData.payment_method = data.paymentMethod || 'PUE';
    invoiceData.currency = data.currency || 'MXN';
    if (data.exchangeRate && data.currency !== 'MXN') {
      invoiceData.exchange = data.exchangeRate;
    }
    if (data.conditions) invoiceData.conditions = data.conditions;
  }

  if (data.series) invoiceData.series = data.series;
  if (data.folioNumber) invoiceData.folio_number = data.folioNumber;
  if ((data as any).fechaEmision) invoiceData.date = (data as any).fechaEmision;

  // Documentos relacionados (Ingreso / Egreso / Pago / Traslado).
  if (data.relatedDocuments?.length) {
    invoiceData.related_documents = data.relatedDocuments.map(r => ({
      relationship: r.relationship,
      documents: r.uuids,
    }));
  }

  // Complemento de pago (tipo P)
  if ((data as any).complements?.length) {
    invoiceData.complements = (data as any).complements;
  }

  // Factura global
  if ((data as any).global) {
    invoiceData.global = (data as any).global;
  }

  // El régimen fiscal del emisor lo toma Facturapi del `legal.tax_system` de
  // la organización — NO acepta override per-invoice via campo `issuer` (la
  // API rechaza con "issuer is not allowed"). Si se pasa `issuerTaxSystem`,
  // debe actualizarse el `legal` de la org ANTES de crear el invoice. Para
  // el path tenant-level no lo hacemos (la org comparte régimen único); solo
  // el path contribuyente (contribuyente-facturapi.service.ts) implementa
  // el sync legal porque cada contribuyente tiene sus propios regímenes.

  const invoice = await client.invoices.create(invoiceData);
  return invoice;
}

// ============================================
// Cancelación
// ============================================

// ============================================
// Personalización (logo, color)
// ============================================

export async function uploadLogo(tenantId: string, logoBase64: string): Promise<{ success: boolean; message: string }> {
  const tenant = await prisma.tenant.findUnique({
    where: { id: tenantId },
    select: { facturapiOrgId: true },
  });
  if (!tenant?.facturapiOrgId) throw new Error('Organización no configurada');

  const userClient = getUserClient();
  try {
    const buffer = Buffer.from(logoBase64, 'base64');
    await userClient.organizations.uploadLogo(tenant.facturapiOrgId, buffer);
    return { success: true, message: 'Logo subido correctamente' };
  } catch (error: any) {
    return { success: false, message: error.message || 'Error al subir logo' };
  }
}

export async function updateColor(tenantId: string, color: string): Promise<{ success: boolean; message: string }> {
  const tenant = await prisma.tenant.findUnique({
    where: { id: tenantId },
    select: { facturapiOrgId: true },
  });
  if (!tenant?.facturapiOrgId) throw new Error('Organización no configurada');

  const userClient = getUserClient();
  try {
    await userClient.organizations.updateCustomization(tenant.facturapiOrgId, { color: color.replace('#', '') });
    return { success: true, message: 'Color actualizado' };
  } catch (error: any) {
    return { success: false, message: error.message || 'Error al actualizar color' };
  }
}

export async function getCustomization(tenantId: string): Promise<{ logoUrl?: string; color?: string } | null> {
  const tenant = await prisma.tenant.findUnique({
    where: { id: tenantId },
    select: { facturapiOrgId: true },
  });
  if (!tenant?.facturapiOrgId) return null;

  const userClient = getUserClient();
  try {
    const org = await userClient.organizations.retrieve(tenant.facturapiOrgId);
    return {
      logoUrl: org.customization?.has_logo ? (org.logo_url ?? undefined) : undefined,
      color: org.customization?.color || undefined,
    };
  } catch { return null; }
}

export async function sendInvoiceByEmail(
  tenantId: string,
  facturapiId: string,
  email: string
): Promise<void> {
  const client = await getOrgClient(tenantId);
  await client.invoices.sendByEmail(facturapiId, { email });
}

export async function cancelInvoice(
  tenantId: string,
  facturapiId: string,
  motive: '01' | '02' | '03' | '04' = '02',
  substitution?: string
): Promise<any> {
  const client = await getOrgClient(tenantId);

  const cancelData: any = { motive };
  if (motive === '01' && substitution) {
    cancelData.substitution = substitution;
  }

  return client.invoices.cancel(facturapiId, cancelData);
}

// ============================================
// Descargas
// ============================================

export async function downloadPdf(tenantId: string, facturapiId: string): Promise<Buffer> {
  const client = await getOrgClient(tenantId);
  const stream = await client.invoices.downloadPdf(facturapiId);
  return streamToBuffer(stream);
}

export async function downloadXml(tenantId: string, facturapiId: string): Promise<Buffer> {
  const client = await getOrgClient(tenantId);
  const stream = await client.invoices.downloadXml(facturapiId);
  return streamToBuffer(stream);
}

export async function downloadZip(tenantId: string, facturapiId: string): Promise<Buffer> {
  const client = await getOrgClient(tenantId);
  const stream = await client.invoices.downloadZip(facturapiId);
  return streamToBuffer(stream);
}

function streamToBuffer(stream: any): Promise<Buffer> {
  return new Promise((resolve, reject) => {
    if (Buffer.isBuffer(stream)) return resolve(stream);
    const chunks: Buffer[] = [];
    stream.on('data', (chunk: Buffer) => chunks.push(chunk));
    stream.on('end', () => resolve(Buffer.concat(chunks)));
    stream.on('error', reject);
  });
}

// ============================================
// Timbres
// ============================================

export interface TimbreStatus {
  configured: boolean;
  // Campos flat — backward compat con UI existente que lee `limite/usados/disponibles`
  // al top level. Representan SOLO el pool mensual (no suman los paquetes).
  tipo?: string;
  limite?: number;
  usados?: number;
  disponibles?: number;
  periodoFin?: string;
  // Shape nuevo (fase B): separa mensual vs adicionales para la UI detallada
  mensual?: {
    tipo: string;
    limite: number;
    usados: number;
    disponibles: number;
    periodoFin: string;
  };
  adicionales?: {
    total: number;         // suma de cantidades originales de paquetes vigentes
    usados: number;        // suma de usados
    disponibles: number;   // total - usados
    paquetes: Array<{
      id: number;
      cantidad: number;
      usados: number;
      disponibles: number;
      adquiridoEn: string;
      expiraEn: string;
    }>;
  };
  /** suma total disponible (mensual + adicionales vigentes). */
  totalDisponibles: number;
}

export async function getTimbreStatus(tenantId: string): Promise<TimbreStatus> {
  const now = new Date();

  const [suscripcion, paquetes] = await Promise.all([
    prisma.timbreSuscripcion.findUnique({ where: { tenantId } }),
    prisma.timbrePaquete.findMany({
      where: { tenantId, expiraEn: { gt: now } },
      orderBy: { expiraEn: 'asc' },
    }),
  ]);

  if (!suscripcion && paquetes.length === 0) {
    return { configured: false, totalDisponibles: 0 };
  }

  const mensualVigente = suscripcion && now <= suscripcion.periodoFin;
  const mensual = mensualVigente
    ? {
        tipo: suscripcion.tipo,
        limite: suscripcion.timbresLimite,
        usados: suscripcion.timbresUsados,
        disponibles: Math.max(0, suscripcion.timbresLimite - suscripcion.timbresUsados),
        periodoFin: suscripcion.periodoFin.toISOString().split('T')[0],
      }
    : undefined;

  const paquetesDetail = paquetes.map(p => ({
    id: p.id,
    cantidad: p.cantidad,
    usados: p.usados,
    disponibles: Math.max(0, p.cantidad - p.usados),
    adquiridoEn: p.adquiridoEn.toISOString(),
    expiraEn: p.expiraEn.toISOString(),
  }));

  const adicionales = paquetesDetail.length > 0
    ? {
        total: paquetesDetail.reduce((s, p) => s + p.cantidad, 0),
        usados: paquetesDetail.reduce((s, p) => s + p.usados, 0),
        disponibles: paquetesDetail.reduce((s, p) => s + p.disponibles, 0),
        paquetes: paquetesDetail,
      }
    : undefined;

  return {
    configured: true,
    // backward-compat flat: refleja el mensual si existe, sino deja undefined
    tipo: mensual?.tipo,
    limite: mensual?.limite,
    usados: mensual?.usados,
    disponibles: mensual?.disponibles,
    periodoFin: mensual?.periodoFin,
    // nuevo shape nested
    mensual,
    adicionales,
    totalDisponibles: (mensual?.disponibles || 0) + (adicionales?.disponibles || 0),
  };
}

/**
 * Consume 1 timbre respetando las reglas del feature:
 *   1) Intenta contra TimbreSuscripcion si está en periodo vigente y queda cupo.
 *   2) Si mensual agotado/vencido, consume del TimbrePaquete con menor expiraEn
 *      (FIFO para no desperdiciar los próximos a vencer).
 *   3) Si no hay nada disponible, lanza error.
 *
 * La transacción protege contra race conditions en emisiones concurrentes.
 */
export async function consumeTimbre(tenantId: string): Promise<{ source: 'mensual' | 'paquete'; paqueteId?: number }> {
  return await prisma.$transaction(async (tx) => {
    const now = new Date();

    // 1) Intenta mensual
    const suscripcion = await tx.timbreSuscripcion.findUnique({ where: { tenantId } });
    if (suscripcion && now <= suscripcion.periodoFin && suscripcion.timbresUsados < suscripcion.timbresLimite) {
      await tx.timbreSuscripcion.update({
        where: { tenantId },
        data: { timbresUsados: { increment: 1 } },
      });
      return { source: 'mensual' as const };
    }

    // 2) Fallback a paquetes adicionales FIFO por expiraEn
    const paquete = await tx.timbrePaquete.findFirst({
      where: {
        tenantId,
        expiraEn: { gt: now },
        usados: { lt: prisma.timbrePaquete.fields.cantidad },
      },
      orderBy: { expiraEn: 'asc' },
    });

    if (!paquete) {
      // Diferencia los mensajes de error para que el frontend sepa qué ofrecer
      if (!suscripcion) {
        throw new Error('No hay suscripción de timbres configurada');
      }
      if (now > suscripcion.periodoFin) {
        throw new Error('La suscripción de timbres ha expirado. Compra timbres adicionales o renueva tu plan.');
      }
      throw new Error('Se agotaron los timbres del plan mensual y no tienes paquetes adicionales. Compra un paquete para continuar.');
    }

    await tx.timbrePaquete.update({
      where: { id: paquete.id },
      data: { usados: { increment: 1 } },
    });

    return { source: 'paquete' as const, paqueteId: paquete.id };
  });
}

/**
 * Revierte un consumo previo de timbre. Idempotente por fuente:
 *   - mensual → decrementa timbresUsados (no baja de 0 gracias al guard)
 *   - paquete → decrementa usados del paquete específico (mismo guard)
 *
 * Se invoca cuando la emisión en Facturapi falla después de haber consumido
 * (SAT nunca selló → el timbre no debe cobrarse).
 */
export async function refundTimbre(
  tenantId: string,
  consumed: { source: 'mensual' | 'paquete'; paqueteId?: number },
): Promise<void> {
  await prisma.$transaction(async (tx) => {
    if (consumed.source === 'mensual') {
      const sub = await tx.timbreSuscripcion.findUnique({ where: { tenantId } });
      if (sub && sub.timbresUsados > 0) {
        await tx.timbreSuscripcion.update({
          where: { tenantId },
          data: { timbresUsados: { decrement: 1 } },
        });
      }
      return;
    }
    if (consumed.source === 'paquete' && consumed.paqueteId != null) {
      const pkg = await tx.timbrePaquete.findUnique({ where: { id: consumed.paqueteId } });
      if (pkg && pkg.usados > 0) {
        await tx.timbrePaquete.update({
          where: { id: consumed.paqueteId },
          data: { usados: { decrement: 1 } },
        });
      }
    }
  });
}

/**
 * Reset mensual de TimbreSuscripcion: para cada tenant cuyo periodoFin ya pasó,
 * resetea `timbresUsados=0` y avanza la ventana un mes (tipo='mensual') o un año
 * (tipo='anual'). Usado por cron diario. Idempotente: si no hay vencidas, no-op.
 *
 * Los paquetes adicionales NO se tocan aquí — su vigencia es 1 año fijo desde
 * la compra y el filtro `expiraEn > now` los excluye automáticamente cuando
 * caducan.
 */
export async function resetExpiredMonthlyTimbres(): Promise<{ reset: number }> {
  const now = new Date();
  const vencidas = await prisma.timbreSuscripcion.findMany({
    where: { periodoFin: { lt: now } },
  });

  let count = 0;
  for (const s of vencidas) {
    const nextInicio = new Date(s.periodoFin);
    nextInicio.setDate(nextInicio.getDate() + 1);
    const nextFin = new Date(nextInicio);
    if (s.tipo === 'anual') {
      nextFin.setFullYear(nextFin.getFullYear() + 1);
      nextFin.setDate(nextFin.getDate() - 1);
    } else {
      nextFin.setMonth(nextFin.getMonth() + 1);
      nextFin.setDate(nextFin.getDate() - 1);
    }

    await prisma.timbreSuscripcion.update({
      where: { id: s.id },
      data: {
        timbresUsados: 0,
        periodoInicio: nextInicio,
        periodoFin: nextFin,
      },
    });
    count++;
    console.log(`[Timbres] Reset mensual tenant ${s.tenantId}: nuevo periodo ${nextInicio.toISOString().split('T')[0]} → ${nextFin.toISOString().split('T')[0]}`);
  }

  return { reset: count };
}

// ============================================
// Paquetes adicionales: catálogo + compra + activación
// ============================================

/** Lista los paquetes activos del catálogo, ordenados por cantidad ASC. */
export async function listPaquetesCatalogo() {
  const rows = await prisma.timbrePaqueteCatalogo.findMany({
    where: { active: true },
    orderBy: { cantidad: 'asc' },
  });
  return rows.map(r => ({
    id: r.id,
    cantidad: r.cantidad,
    precio: Number(r.precio),
  }));
}

/**
 * Lista todos los paquetes del catálogo (incluyendo inactivos). Para admin
 * global que edita precios — necesita ver los dados de baja también.
 */
export async function listAllPaquetesCatalogo() {
  const rows = await prisma.timbrePaqueteCatalogo.findMany({
    orderBy: { cantidad: 'asc' },
  });
  return rows.map(r => ({
    id: r.id,
    cantidad: r.cantidad,
    precio: Number(r.precio),
    active: r.active,
    updatedAt: r.updatedAt.toISOString(),
  }));
}

/**
 * Actualiza precio y/o estado activo de un paquete del catálogo. Solo admin
 * global. Los cambios NO afectan paquetes ya vendidos (TimbrePaquete guarda
 * snapshot del precio al momento de compra).
 */
export async function updatePaqueteCatalogo(params: {
  id: number;
  precio?: number;
  active?: boolean;
}) {
  const data: { precio?: number; active?: boolean } = {};
  if (params.precio !== undefined) {
    if (params.precio <= 0) throw new Error('El precio debe ser mayor a 0');
    data.precio = params.precio;
  }
  if (params.active !== undefined) data.active = params.active;

  if (Object.keys(data).length === 0) {
    throw new Error('Nada que actualizar');
  }

  const updated = await prisma.timbrePaqueteCatalogo.update({
    where: { id: params.id },
    data,
  });

  return {
    id: updated.id,
    cantidad: updated.cantidad,
    precio: Number(updated.precio),
    active: updated.active,
    updatedAt: updated.updatedAt.toISOString(),
  };
}

/**
 * Inicia la compra de un paquete. Crea un Payment con status=pending y una
 * MP Preference (checkout one-shot). Retorna la URL a la que redirigir al user.
 *
 * Flujo post-pago:
 *   1. User paga en MP
 *   2. MP dispara webhook `payment.approved` con external_reference = `timbres-pack:{paymentId}`
 *   3. `applyApprovedTimbrePack` (fase webhook) crea el TimbrePaquete y emite factura
 */
export async function iniciarCompraPaquete(params: {
  tenantId: string;
  catalogoId: number;
  callerEmail: string; // email del user que inicia la compra (caller)
}): Promise<{ paymentId: string; checkoutUrl: string }> {
  const paquete = await prisma.timbrePaqueteCatalogo.findUnique({
    where: { id: params.catalogoId },
  });
  if (!paquete || !paquete.active) {
    throw new Error('Paquete no disponible');
  }

  // Email de pago: preferencia al owner del tenant (para continuidad del flujo
  // normal de facturación). Si no hay owner activo (caso edge: tenant sin
  // membership owner por ahora), caemos al email del caller para no bloquear.
  const ownerEmail = await getTenantOwnerEmail(params.tenantId);
  const payerEmail = ownerEmail || params.callerEmail;
  if (!payerEmail) {
    throw new Error('No se pudo determinar un email para el cobro');
  }

  // Payment pre-creado como pending. El webhook lo aprueba. Guardamos cantidad
  // en un campo que podamos recuperar — usamos paymentMethod como placeholder
  // para carry-over del cantidad mientras no haya un campo metadata dedicado.
  // Mejor: el paqueteId del catálogo es único y persistente, no hace falta
  // guardar cantidad aparte.
  const payment = await prisma.payment.create({
    data: {
      tenantId: params.tenantId,
      amount: paquete.precio,
      status: 'pending',
      kind: 'timbres_pack',
      paymentMethod: `catalogo:${paquete.id}`, // marker para recuperar cantidad en webhook
    },
  });

  const { preferenceId, checkoutUrl } = await mpService.createTimbrePackPreference({
    paymentId: payment.id,
    tenantId: params.tenantId,
    cantidad: paquete.cantidad,
    amount: Number(paquete.precio),
    payerEmail,
  });

  // Guardamos el preferenceId por si lo necesitamos para debugging o cancel
  await prisma.payment.update({
    where: { id: payment.id },
    data: { mpPaymentId: preferenceId }, // temporal hasta que llegue el paymentId real
  });

  return { paymentId: payment.id, checkoutUrl };
}

/**
 * Activa el paquete una vez que MP confirmó el pago. Idempotente: si ya hay un
 * TimbrePaquete para este paymentId, no-op.
 *
 * Llamado desde el webhook cuando external_reference = timbres-pack:{paymentId}
 * Y status=approved.
 */
export async function activarPaqueteTrasPago(paymentId: string): Promise<{ created: boolean; paqueteId?: number }> {
  const existing = await prisma.timbrePaquete.findUnique({
    where: { paymentId },
  });
  if (existing) {
    console.log(`[Timbres] Paquete ya activado para payment ${paymentId} (idempotente)`);
    return { created: false, paqueteId: existing.id };
  }

  const payment = await prisma.payment.findUnique({ where: { id: paymentId } });
  if (!payment) throw new Error(`Payment ${paymentId} no encontrado`);
  if (payment.kind !== 'timbres_pack') {
    throw new Error(`Payment ${paymentId} no es de tipo timbres_pack`);
  }

  // Recupera cantidad del marker paymentMethod (catalogo:ID)
  const match = /^catalogo:(\d+)$/.exec(payment.paymentMethod || '');
  if (!match) {
    throw new Error(`Payment ${paymentId} sin marker de catálogo válido`);
  }
  const catalogoId = Number(match[1]);
  const catalogo = await prisma.timbrePaqueteCatalogo.findUnique({ where: { id: catalogoId } });
  if (!catalogo) {
    throw new Error(`Catálogo ${catalogoId} referenciado por Payment ${paymentId} ya no existe`);
  }

  const adquiridoEn = new Date();
  const expiraEn = new Date(adquiridoEn);
  expiraEn.setFullYear(expiraEn.getFullYear() + 1);

  const paquete = await prisma.timbrePaquete.create({
    data: {
      tenantId: payment.tenantId,
      paymentId: payment.id,
      cantidad: catalogo.cantidad,
      precio: payment.amount, // precio pagado (historial, snapshot del momento)
      adquiridoEn,
      expiraEn,
    },
  });

  console.log(`[Timbres] Activado paquete ${paquete.id} (${catalogo.cantidad} timbres) para tenant ${payment.tenantId}, expira ${expiraEn.toISOString().split('T')[0]}`);
  return { created: true, paqueteId: paquete.id };
}
