import { prisma } from '../../config/database.js';
import * as mpService from './mercadopago.service.js';
import { getTenantOwnerEmail } from '../../utils/memberships.js';
import { computeEffectiveLimits, type PlanLimits, type AddonDelta } from '../plan-catalogo.service.js';
import { permiteOverage } from '@horux/shared';

export async function listActiveAddons(tenantId: string, contribuyenteId?: string | null) {
  const subscription = await prisma.subscription.findFirst({
    where: { tenantId, status: { in: ['authorized', 'pending', 'trial'] } },
    include: {
      addons: {
        include: { planAddonCatalogo: true },
        where: {
          status: { in: ['authorized', 'pending'] },
          // Si se pide por contribuyente, solo trae los de ese contribuyente.
          // Si no, trae todos (tenant-level + todos los contribuyentes).
          ...(contribuyenteId !== undefined ? { contribuyenteId } : {}),
        },
      },
    },
  });

  if (!subscription) return { addons: [], subscription: null };

  return {
    subscription: { id: subscription.id, plan: subscription.plan, status: subscription.status },
    addons: subscription.addons.map(a => ({
      id: a.id,
      codename: a.planAddonCatalogo.codename,
      nombre: a.planAddonCatalogo.nombre,
      precio: Number(a.amount),
      quantity: a.quantity,
      contribuyenteId: a.contribuyenteId,
      status: a.status,
      currentPeriodStart: a.currentPeriodStart?.toISOString() ?? null,
      currentPeriodEnd: a.currentPeriodEnd?.toISOString() ?? null,
    })),
  };
}

export async function subscribeAddon(params: {
  tenantId: string;
  addonCodename: string;
  quantity?: number;
  payerEmail?: string;
  /**
   * UUID del contribuyente (entidad_id en BD tenant) cuando el add-on se ata a
   * un RFC específico. Omitido para add-ons tenant-level.
   */
  contribuyenteId?: string | null;
}): Promise<{ addon: any; paymentUrl: string }> {
  const { tenantId, addonCodename, quantity = 1, contribuyenteId = null } = params;

  const addon = await prisma.planAddonCatalogo.findUnique({ where: { codename: addonCodename } });
  if (!addon || !addon.active) throw new Error('Addon no disponible');

  const subscription = await prisma.subscription.findFirst({
    where: { tenantId, status: { in: ['authorized', 'trial'] } },
  });
  if (!subscription) throw new Error('No hay suscripción activa');

  // Un solo addon activo por (subscription, addon, contribuyente?). Para
  // tenant-level (contribuyenteId=null) cualquier activo bloquea; para
  // per-contribuyente, solo bloquea si ya existe activo para ese mismo RFC.
  const existing = await prisma.subscriptionAddon.findFirst({
    where: {
      subscriptionId: subscription.id,
      planAddonCatalogoId: addon.id,
      contribuyenteId: contribuyenteId ?? null,
      status: { in: ['authorized', 'pending'] },
    },
  });
  if (existing) throw new Error('Ya tienes este addon activo');

  const ownerEmail = params.payerEmail || await getTenantOwnerEmail(tenantId);
  if (!ownerEmail) throw new Error('No se pudo determinar un email para el cobro');

  const tenant = await prisma.tenant.findUnique({ where: { id: tenantId }, select: { nombre: true } });
  const amount = Number(addon.precio) * quantity;

  // Create the SubscriptionAddon record first so we have an id for external_reference
  const subscriptionAddon = await prisma.subscriptionAddon.create({
    data: {
      subscriptionId: subscription.id,
      planAddonCatalogoId: addon.id,
      contribuyenteId: contribuyenteId ?? null,
      status: 'pending',
      quantity,
      amount,
    },
  });

  let mp: { preapprovalId: string; initPoint: string; status: string };
  try {
    mp = await mpService.createPreapproval({
      tenantId,
      reason: `Horux Despachos - ${addon.nombre}${contribuyenteId ? ` (RFC ${contribuyenteId.slice(0, 8)})` : ''} x${quantity} - ${tenant?.nombre || tenantId}`,
      amount,
      payerEmail: ownerEmail,
      frequency: addon.frecuencia === 'anual' ? 'annual' : 'monthly',
      externalReference: `addon:${subscriptionAddon.id}`,
    });
  } catch (err) {
    // If MP creation fails, clean up the pending record so user can retry
    await prisma.subscriptionAddon.delete({ where: { id: subscriptionAddon.id } });
    throw err;
  }

  // Update record with the mp preapproval id and final status
  const updated = await prisma.subscriptionAddon.update({
    where: { id: subscriptionAddon.id },
    data: {
      mpPreapprovalId: mp.preapprovalId,
      status: mp.status || 'pending',
    },
  });

  return { addon: updated, paymentUrl: mp.initPoint };
}

export async function cancelAddon(tenantId: string, addonId: string): Promise<void> {
  const addon = await prisma.subscriptionAddon.findUnique({
    where: { id: addonId },
    include: { subscription: { select: { tenantId: true } } },
  });

  if (!addon || addon.subscription.tenantId !== tenantId) {
    throw new Error('Addon no encontrado');
  }

  if (addon.mpPreapprovalId) {
    try {
      await mpService.cancelPreapproval(addon.mpPreapprovalId);
    } catch (err) {
      console.error('[Addon] Error cancelling MP preapproval:', err);
    }
  }

  await prisma.subscriptionAddon.update({
    where: { id: addonId },
    data: { status: 'cancelled' },
  });
}

export async function handleAddonPayment(addonId: string, mpPaymentId: string, status: string): Promise<void> {
  const addon = await prisma.subscriptionAddon.findUnique({ where: { id: addonId } });
  if (!addon) {
    console.error(`[Addon Webhook] SubscriptionAddon ${addonId} not found`);
    return;
  }

  if (status === 'authorized' || status === 'approved') {
    const now = new Date();
    const periodEnd = new Date(now);
    if (addon.amount) {
      periodEnd.setMonth(periodEnd.getMonth() + 1);
    }

    await prisma.subscriptionAddon.update({
      where: { id: addonId },
      data: {
        status: 'authorized',
        currentPeriodStart: now,
        currentPeriodEnd: periodEnd,
      },
    });
  } else if (status === 'cancelled' || status === 'paused') {
    await prisma.subscriptionAddon.update({
      where: { id: addonId },
      data: { status },
    });
  }
}

// ────────────────────────────────────────────────────────────────
// Overage automático: Business Control y Enterprise (business_cloud) incluyen
// 100 contribuyentes; cada uno adicional cuesta $45/mes. Se modela como un
// único `SubscriptionAddon` con `codename = 'contribuyente_extra_business_cloud'`
// (codename heredado por compat con suscripciones existentes; nombre display ya
// es genérico), `contribuyenteId = null` (tenant-level) y
// `quantity = activeCount − 100`. El cobro MP usa un preapproval propio; cuando
// `quantity` cambia, se actualiza vía `updatePreapprovalAmount` (sin
// re-autorización del usuario).
// ────────────────────────────────────────────────────────────────

const DESPACHO_INCLUDED_RFCS = 100;
const OVERAGE_ADDON_CODENAME = 'contribuyente_extra_business_cloud';

export type OverageAction = 'none' | 'created' | 'updated' | 'cancelled' | 'skipped';

export interface OverageAdjustResult {
  action: OverageAction;
  /** Cantidad cobrada tras el ajuste (activeCount − 3, mínimo 0). */
  overageCount: number;
  /** Sólo cuando action='created': URL de MercadoPago a presentar al usuario. */
  paymentUrl?: string;
  /** Razón si action='skipped' o 'none' (útil para logs/UI). */
  reason?: string;
}

/**
 * Ajusta el add-on de overage para el tenant según el número actual de
 * contribuyentes activos. Aplica a planes Business Control y Enterprise
 * (business_cloud) — ambos incluyen 100 contribuyentes y cobran $45/mes por
 * cada adicional. Idempotente: llamar varias veces con el mismo `activeCount`
 * no tiene efecto.
 *
 * Casos:
 *   - Plan no permite overage (mi_empresa, mi_empresa_plus, trial, etc.) → 'skipped'
 *   - Sin suscripción activa → 'skipped' (addon requiere sub)
 *   - Catálogo no seeded → 'skipped' (error de setup)
 *   - overage=0 y no hay addon → 'none'
 *   - overage=0 y hay addon → 'cancelled' (revoca preapproval)
 *   - overage>0 y no hay addon → 'created' (crea addon + preapproval → paymentUrl)
 *   - overage>0 y addon.quantity == overage → 'none' (idempotente)
 *   - overage>0 y addon.quantity distinto → 'updated' (updatePreapprovalAmount)
 */
export async function adjustDespachoOverage(
  tenantId: string,
  activeContribuyenteCount: number,
): Promise<OverageAdjustResult> {
  const sub = await prisma.subscription.findFirst({
    where: { tenantId, status: { in: ['authorized', 'trial', 'pending'] } },
    orderBy: { createdAt: 'desc' },
  });
  if (!sub) return { action: 'skipped', overageCount: 0, reason: 'Sin suscripción activa' };
  if (!permiteOverage(sub.plan)) {
    return { action: 'skipped', overageCount: 0, reason: `Plan ${sub.plan} no aplica overage` };
  }

  const overage = Math.max(0, activeContribuyenteCount - DESPACHO_INCLUDED_RFCS);

  const catalogo = await prisma.planAddonCatalogo.findUnique({
    where: { codename: OVERAGE_ADDON_CODENAME },
  });
  if (!catalogo) {
    return { action: 'skipped', overageCount: overage, reason: 'Catálogo overage no seeded' };
  }

  const existing = await prisma.subscriptionAddon.findFirst({
    where: {
      subscriptionId: sub.id,
      planAddonCatalogoId: catalogo.id,
      contribuyenteId: null,
      status: { in: ['authorized', 'pending'] },
    },
  });

  // Bajo o en el límite: cancela el addon si existe
  if (overage === 0) {
    if (!existing) return { action: 'none', overageCount: 0 };
    if (existing.mpPreapprovalId) {
      try {
        await mpService.cancelPreapproval(existing.mpPreapprovalId);
      } catch (err) {
        console.error('[Overage] Error cancelling MP preapproval:', err);
      }
    }
    await prisma.subscriptionAddon.update({
      where: { id: existing.id },
      data: { status: 'cancelled' },
    });
    return { action: 'cancelled', overageCount: 0 };
  }

  // Hay overage → crear o actualizar addon
  const price = Number(catalogo.precio);
  const newAmount = price * overage;

  if (!existing) {
    const ownerEmail = await getTenantOwnerEmail(tenantId);
    if (!ownerEmail) {
      return { action: 'skipped', overageCount: overage, reason: 'Sin email del owner para cobro' };
    }
    const tenant = await prisma.tenant.findUnique({ where: { id: tenantId }, select: { nombre: true } });

    const addon = await prisma.subscriptionAddon.create({
      data: {
        subscriptionId: sub.id,
        planAddonCatalogoId: catalogo.id,
        contribuyenteId: null,
        status: 'pending',
        quantity: overage,
        amount: newAmount,
      },
    });

    let mp: { preapprovalId: string; initPoint: string; status: string };
    try {
      mp = await mpService.createPreapproval({
        tenantId,
        reason: `Horux Despachos - ${catalogo.nombre} x${overage} - ${tenant?.nombre || tenantId}`,
        amount: newAmount,
        payerEmail: ownerEmail,
        frequency: 'monthly',
        externalReference: `addon:${addon.id}`,
      });
    } catch (err) {
      await prisma.subscriptionAddon.delete({ where: { id: addon.id } });
      throw err;
    }

    await prisma.subscriptionAddon.update({
      where: { id: addon.id },
      data: { mpPreapprovalId: mp.preapprovalId, status: mp.status || 'pending' },
    });

    return { action: 'created', overageCount: overage, paymentUrl: mp.initPoint };
  }

  // Idempotente: si quantity ya coincide, no hay nada que hacer
  if (existing.quantity === overage) {
    return { action: 'none', overageCount: overage };
  }

  // Actualizar quantity + amount + MP preapproval
  await prisma.subscriptionAddon.update({
    where: { id: existing.id },
    data: { quantity: overage, amount: newAmount },
  });
  if (existing.mpPreapprovalId) {
    try {
      await mpService.updatePreapprovalAmount(existing.mpPreapprovalId, newAmount);
    } catch (err) {
      console.error('[Overage] Error updating MP amount:', err);
    }
  }

  return { action: 'updated', overageCount: overage };
}

/**
 * Cuenta contribuyentes activos del tenant abriendo el pool tenant. Helper
 * para callers que viven fuera de un endpoint con `req.tenantPool` (ej.
 * `subscription.service.ts` cuando aplica un cambio de plan).
 */
export async function countActiveContribuyentesForTenant(tenantId: string): Promise<number> {
  const tenant = await prisma.tenant.findUnique({
    where: { id: tenantId },
    select: { databaseName: true },
  });
  if (!tenant?.databaseName) return 0;
  const { tenantDb } = await import('../../config/database.js');
  const pool = await tenantDb.getPool(tenantId, tenant.databaseName);
  const { rows: [{ cnt }] } = await pool.query<{ cnt: string }>(
    `SELECT COUNT(*)::text AS cnt FROM entidades_gestionadas
     WHERE active = true AND tipo = 'CONTRIBUYENTE'`,
  );
  return Number(cnt) || 0;
}

/**
 * Cancela el add-on de overage del tenant (si existe) sin importar el status
 * actual de la suscripción. Útil cuando una suscripción se cancela y queremos
 * cerrar también el preapproval mensual del overage. Idempotente.
 */
export async function cancelOverageAddonForTenant(tenantId: string): Promise<{ cancelled: boolean }> {
  const sub = await prisma.subscription.findFirst({
    where: { tenantId },
    orderBy: { createdAt: 'desc' },
  });
  if (!sub) return { cancelled: false };

  const catalogo = await prisma.planAddonCatalogo.findUnique({
    where: { codename: OVERAGE_ADDON_CODENAME },
  });
  if (!catalogo) return { cancelled: false };

  const existing = await prisma.subscriptionAddon.findFirst({
    where: {
      subscriptionId: sub.id,
      planAddonCatalogoId: catalogo.id,
      contribuyenteId: null,
      status: { in: ['authorized', 'pending'] },
    },
  });
  if (!existing) return { cancelled: false };

  if (existing.mpPreapprovalId) {
    try {
      await mpService.cancelPreapproval(existing.mpPreapprovalId);
    } catch (err) {
      console.error('[Overage] Error cancelling MP preapproval on tenant cancel:', err);
    }
  }
  await prisma.subscriptionAddon.update({
    where: { id: existing.id },
    data: { status: 'cancelled' },
  });
  return { cancelled: true };
}

// Re-export types used by callers that need to compose addon deltas with plan limits
export type { PlanLimits, AddonDelta };
export { computeEffectiveLimits };
