import type { Request, Response, NextFunction } from 'express';
import * as subscriptionService from '../services/payment/subscription.service.js';
import { listActiveAddons, subscribeAddon, cancelAddon } from '../services/payment/addon.service.js';
import { isGlobalAdmin } from '../utils/global-admin.js';
import { auditFromReq } from '../utils/audit.js';

async function requireGlobalAdmin(req: Request, res: Response): Promise<boolean> {
  const isAdmin = await isGlobalAdmin(req.user!.tenantId, req.user!.role);
  if (!isAdmin) {
    res.status(403).json({ message: 'Solo el administrador global puede gestionar suscripciones' });
  }
  return isAdmin;
}

/**
 * Permite si el usuario es admin global O si está consultando su propio tenant.
 * Úsalo para endpoints de lectura/acción sobre la suscripción del mismo tenant
 * del usuario (ver estado, generar link de pago pendiente).
 */
async function requireOwnTenantOrGlobalAdmin(req: Request, res: Response, targetTenantId: string): Promise<boolean> {
  if (targetTenantId === req.user!.tenantId) return true;
  const isAdmin = await isGlobalAdmin(req.user!.tenantId, req.user!.role);
  if (!isAdmin) {
    res.status(403).json({ message: 'Solo puedes gestionar la suscripción de tu propio tenant' });
  }
  return isAdmin;
}

/**
 * Devuelve los precios vigentes de los planes self-serve (excluye custom).
 * Cualquier admin/cfo puede consultarlo — no requiere admin global.
 */
export async function getPlans(_req: Request, res: Response, next: NextFunction) {
  try {
    const { prisma } = await import('../config/database.js');
    const prices = await prisma.planPrice.findMany({
      orderBy: [{ plan: 'asc' }, { frequency: 'asc' }],
    });
    res.json(prices);
  } catch (error) {
    next(error);
  }
}

/**
 * Actualiza el precio de un plan (por id). Solo admin global.
 * El nuevo precio aplica solo a suscripciones **nuevas o renovaciones futuras**
 * — suscripciones ya vigentes conservan el precio al que las contrataron.
 */
export async function updatePlanPrice(req: Request, res: Response, next: NextFunction) {
  try {
    if (!(await requireGlobalAdmin(req, res))) return;

    const id = parseInt(String(req.params.id), 10);
    if (!Number.isFinite(id)) {
      return res.status(400).json({ message: 'ID inválido' });
    }
    const amount = Number(req.body?.amount);
    if (!Number.isFinite(amount) || amount < 0) {
      return res.status(400).json({ message: 'El monto debe ser un número no negativo' });
    }

    const { prisma } = await import('../config/database.js');
    const before = await prisma.planPrice.findUnique({ where: { id } });
    if (!before) return res.status(404).json({ message: 'Precio no encontrado' });

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

    auditFromReq(req, 'price.updated', {
      entityType: 'PlanPrice',
      entityId: String(id),
      metadata: {
        plan: before.plan,
        frequency: before.frequency,
        fromAmount: Number(before.amount),
        toAmount: amount,
      },
    });

    res.json(updated);
  } catch (error: any) {
    if (error?.code === 'P2025') {
      return res.status(404).json({ message: 'Precio no encontrado' });
    }
    next(error);
  }
}

export async function getAllSubscriptions(req: Request, res: Response, next: NextFunction) {
  try {
    if (!(await requireGlobalAdmin(req, res))) return;

    const { prisma } = await import('../config/database.js');
    const subscriptions = await prisma.subscription.findMany({
      include: {
        tenant: {
          select: { id: true, nombre: true, rfc: true, plan: true, active: true },
        },
      },
      orderBy: { createdAt: 'desc' },
    });

    res.json(subscriptions);
  } catch (error) {
    next(error);
  }
}

export async function getSubscription(req: Request, res: Response, next: NextFunction) {
  try {
    const tenantId = String(req.params.tenantId);
    if (!(await requireOwnTenantOrGlobalAdmin(req, res, tenantId))) return;

    const subscription = await subscriptionService.getActiveSubscription(tenantId);
    if (!subscription) {
      return res.status(404).json({ message: 'No se encontró suscripción' });
    }
    res.json(subscription);
  } catch (error) {
    next(error);
  }
}

export async function generatePaymentLink(req: Request, res: Response, next: NextFunction) {
  try {
    const tenantId = String(req.params.tenantId);
    if (!(await requireOwnTenantOrGlobalAdmin(req, res, tenantId))) return;

    const result = await subscriptionService.generatePaymentLink(tenantId);
    res.json(result);
  } catch (error) {
    next(error);
  }
}

export async function markAsPaid(req: Request, res: Response, next: NextFunction) {
  try {
    if (!(await requireGlobalAdmin(req, res))) return;

    const tenantId = String(req.params.tenantId);
    const { amount } = req.body;

    if (!amount || amount <= 0) {
      return res.status(400).json({ message: 'Monto inválido' });
    }

    const payment = await subscriptionService.markAsPaidManually(tenantId, amount);
    res.json(payment);
  } catch (error) {
    next(error);
  }
}

export async function getPayments(req: Request, res: Response, next: NextFunction) {
  try {
    const tenantId = String(req.params.tenantId);
    if (!(await requireOwnTenantOrGlobalAdmin(req, res, tenantId))) return;

    const payments = await subscriptionService.getPaymentHistory(tenantId);
    res.json(payments);
  } catch (error) {
    next(error);
  }
}

// ============================================================================
// Self-serve endpoints (actúan sobre el tenant del usuario autenticado)
// ============================================================================

type FrequencyInput = 'monthly' | 'annual';
const VALID_PLANS = [
  'starter', 'business', 'business_ia', 'enterprise',
  'business_control', 'business_cloud',
  'mi_empresa', 'mi_empresa_plus',
] as const;
// Planes despacho que se cobran SOLO anual. Mi Empresa y Mi Empresa+ aceptan
// monthly o annual (annual con descuento ~17% — paga 10 meses); Business
// Control y Enterprise siguen exclusivamente anuales.
const DESPACHO_ONLY_ANNUAL = new Set([
  'business_control', 'business_cloud',
]);

function validatePlanFrequency(body: any): { plan: typeof VALID_PLANS[number]; frequency: FrequencyInput } | { error: string } {
  const plan = body?.plan;
  const frequency = body?.frequency;
  if (!plan || !VALID_PLANS.includes(plan)) {
    return { error: `plan inválido. Valores aceptados: ${VALID_PLANS.join(', ')}` };
  }
  if (frequency !== 'monthly' && frequency !== 'annual') {
    return { error: `frequency inválida. Debe ser 'monthly' o 'annual'` };
  }
  if (DESPACHO_ONLY_ANNUAL.has(plan) && frequency !== 'annual') {
    return { error: `El plan ${plan} solo está disponible con frecuencia anual` };
  }
  return { plan, frequency };
}

export async function startMyTrial(req: Request, res: Response, next: NextFunction) {
  try {
    const parsed = validatePlanFrequency(req.body);
    if ('error' in parsed) return res.status(400).json({ message: parsed.error });

    const result = await subscriptionService.startTrial({
      tenantId: req.user!.tenantId,
      plan: parsed.plan,
      frequency: parsed.frequency,
      ownerUserId: req.user!.userId,
    });
    res.status(201).json(result);
  } catch (error: any) {
    if (
      error.message?.includes('ya usó') ||
      error.message?.includes('Ya existe') ||
      error.message?.includes('no se puede') ||
      error.message?.includes('Ya consumiste') ||
      error.message?.includes('ya consumió')
    ) {
      return res.status(400).json({ message: error.message });
    }
    next(error);
  }
}

export async function subscribeMe(req: Request, res: Response, next: NextFunction) {
  try {
    const parsed = validatePlanFrequency(req.body);
    if ('error' in parsed) return res.status(400).json({ message: parsed.error });

    const result = await subscriptionService.subscribe({
      tenantId: req.user!.tenantId,
      plan: parsed.plan,
      frequency: parsed.frequency,
      payerEmail: req.user!.email,
    });
    res.status(201).json(result);
  } catch (error: any) {
    const msg: string = error?.message || '';
    if (msg.includes('Ya existe') || msg.includes('custom')) {
      return res.status(400).json({ message: msg });
    }
    if (msg.includes('MercadoPago no está configurado')) {
      return res.status(503).json({ message: msg });
    }
    // Otros errores de MP al crear preapproval (monto inválido, email inválido, etc.)
    if (msg.includes('Unauthorized access') || error?.status === 401) {
      return res.status(503).json({
        message: 'MercadoPago rechazó la solicitud. Verifica que MP_ACCESS_TOKEN sea válido y esté vigente.',
      });
    }
    next(error);
  }
}

export async function changeMyPlan(req: Request, res: Response, next: NextFunction) {
  try {
    const parsed = validatePlanFrequency(req.body);
    if ('error' in parsed) return res.status(400).json({ message: parsed.error });

    const result = await subscriptionService.scheduleChange({
      tenantId: req.user!.tenantId,
      newPlan: parsed.plan,
      newFrequency: parsed.frequency,
    });
    res.json(result);
  } catch (error: any) {
    if (error.message?.includes('iguales') || error.message?.includes('No hay') || error.message?.includes('custom')) {
      return res.status(400).json({ message: error.message });
    }
    next(error);
  }
}

export async function cancelMySubscription(req: Request, res: Response, next: NextFunction) {
  try {
    const result = await subscriptionService.cancelSubscription(req.user!.tenantId);
    res.json(result);
  } catch (error: any) {
    if (error.message?.includes('No hay')) {
      return res.status(400).json({ message: error.message });
    }
    next(error);
  }
}

/**
 * Reactiva suscripción cancelada que aún está dentro de su período pagado.
 * Crea un preapproval nuevo en MP con start_date al final del período actual.
 * Retorna paymentUrl para que el usuario autorice.
 */
export async function reactivateMe(req: Request, res: Response, next: NextFunction) {
  try {
    const result = await subscriptionService.reactivateSubscription({
      tenantId: req.user!.tenantId,
      payerEmail: req.user!.email,
    });
    res.status(201).json(result);
  } catch (error: any) {
    const msg: string = error?.message || '';
    if (msg.includes('No hay') || msg.includes('vencido') || msg.includes('custom')) {
      return res.status(400).json({ message: msg });
    }
    if (msg.includes('MercadoPago no está configurado')) {
      return res.status(503).json({ message: msg });
    }
    if (msg.includes('Unauthorized access') || error?.status === 401) {
      return res.status(503).json({
        message: 'MercadoPago rechazó la solicitud. Verifica que MP_ACCESS_TOKEN sea válido.',
      });
    }
    next(error);
  }
}

/**
 * Inicia un upgrade con cobro prorateado inmediato. Body: `{ plan }`.
 * La frecuencia actual se preserva — para cambiar frecuencia usa `/me/change`.
 * Retorna `{ checkoutUrl, proratedAmount }` — el cliente debe abrir la URL para que
 * el usuario pague en MP. Al confirmarse el pago (webhook), se aplica el plan nuevo.
 */
export async function upgradeMe(req: Request, res: Response, next: NextFunction) {
  try {
    const plan = req.body?.plan;
    if (!plan || !VALID_PLANS.includes(plan)) {
      return res.status(400).json({ message: `plan inválido. Valores: ${VALID_PLANS.join(', ')}` });
    }

    const result = await subscriptionService.initiateUpgrade({
      tenantId: req.user!.tenantId,
      newPlan: plan,
      payerEmail: req.user!.email,
    });
    res.status(201).json(result);
  } catch (error: any) {
    const msg: string = error?.message || '';
    if (
      msg.includes('No hay suscripción') ||
      msg.includes('en curso') ||
      msg.includes('no es un upgrade') ||
      msg.includes('días restantes') ||
      msg.includes('custom') ||
      msg.includes('Precio no configurado')
    ) {
      return res.status(400).json({ message: msg });
    }
    if (msg.includes('MercadoPago no está configurado')) {
      return res.status(503).json({ message: msg });
    }
    if (msg.includes('Unauthorized access') || error?.status === 401) {
      return res.status(503).json({
        message: 'MercadoPago rechazó la solicitud. Verifica que MP_ACCESS_TOKEN sea válido.',
      });
    }
    next(error);
  }
}

export async function cancelMyPendingUpgrade(req: Request, res: Response, next: NextFunction) {
  try {
    await subscriptionService.cancelPendingUpgrade(req.user!.tenantId);
    res.json({ ok: true });
  } catch (error: any) {
    if (error.message?.includes('No hay upgrade')) {
      return res.status(400).json({ message: error.message });
    }
    next(error);
  }
}

// ============================================================================
// Addon endpoints (self-serve)
// ============================================================================

export async function getMyAddons(req: Request, res: Response, next: NextFunction) {
  try {
    // Query param `contribuyenteId` opcional: filtra al contribuyente específico.
    // Sin param → retorna todos los add-ons del tenant (incluye los de todos los RFCs).
    const contribuyenteId = typeof req.query.contribuyenteId === 'string' && req.query.contribuyenteId
      ? req.query.contribuyenteId
      : undefined;
    const result = await listActiveAddons(req.user!.tenantId, contribuyenteId);
    return res.json(result);
  } catch (err) { return next(err); }
}

export async function addMyAddon(req: Request, res: Response, next: NextFunction) {
  try {
    const { addonCodename, quantity, contribuyenteId } = req.body;
    if (!addonCodename) return res.status(400).json({ message: 'addonCodename requerido' });

    const result = await subscribeAddon({
      tenantId: req.user!.tenantId,
      addonCodename,
      quantity: quantity || 1,
      payerEmail: req.user!.email,
      contribuyenteId: typeof contribuyenteId === 'string' ? contribuyenteId : null,
    });
    return res.status(201).json(result);
  } catch (err: any) {
    if (err.message?.includes('no disponible') || err.message?.includes('Ya tienes')) {
      return res.status(409).json({ message: err.message });
    }
    return next(err);
  }
}

export async function cancelMyAddon(req: Request, res: Response, next: NextFunction) {
  try {
    await cancelAddon(req.user!.tenantId, String(req.params.addonId));
    return res.json({ message: 'Addon cancelado' });
  } catch (err: any) {
    if (err.message?.includes('no encontrado')) {
      return res.status(404).json({ message: err.message });
    }
    return next(err);
  }
}
