import { Injectable, Logger, NotFoundException } from '@nestjs/common';
import { Prisma } from '@prisma/client';
import { PrismaService } from 'src/prisma/prisma.service';
import { NovasisPayClient } from 'src/novasis-pay/novasis-pay.client';
import { NovasisPayConfigService } from 'src/novasis-pay/novasis-pay-config.service';
import { EcommercePedidosService } from '../pedidos/ecommerce-pedidos.service';

/**
 * Estados en los que el pago ya se cobró (el pedido salió de `pendiente_pago`
 * hacia el flujo operativo y no fue cancelado/expirado). Debe cubrir TODOS los
 * estados que produce el motor de workflow tras `pago_confirmado`, si no la
 * métrica "Cobrado" subcuenta apenas el pedido avanza de "preparando".
 */
const ESTADOS_COBRADOS = new Set<string>([
  'pago_confirmado',
  'en_verificacion',
  'verificado',
  'preparando',
  'empaquetado',
  'listo_retiro',
  'listo_despacho',
  'en_camino',
  'entregado',
  'retirado',
]);

/** Estados del intent del gateway → estado de la sesión de checkout. */
const ESTADO_GATEWAY: Record<string, string> = {
  approved: 'pago_confirmado',
  succeeded: 'pago_confirmado',
  failed: 'cancelado',
  rejected: 'cancelado',
  expired: 'expirado',
  cancelled: 'cancelado',
  canceled: 'cancelado',
};

/**
 * Pestaña "Pagos" del panel: da visibilidad de cómo se está cobrando en la
 * tienda (gateway vs. manual), permite reconciliar contra Novasis Pay y ver
 * los últimos webhooks recibidos para diagnosticar.
 */
@Injectable()
export class EcommercePagosService {
  private readonly logger = new Logger(EcommercePagosService.name);

  constructor(
    private readonly prisma: PrismaService,
    private readonly novasisPayConfig: NovasisPayConfigService,
    private readonly novasisPayClient: NovasisPayClient,
    private readonly pedidos: EcommercePedidosService,
  ) {}

  /** Estado del gateway: si no está configurado, la tienda cobra manualmente. */
  async gatewayStatus(empresaId: string) {
    const cfg = await this.novasisPayConfig.requireActiveConfig(empresaId).catch(() => null);
    // `requireActiveConfig` no devuelve el merchant_id (sólo credenciales
    // mapeadas), así que lo leemos aparte para mostrarlo en el panel.
    const row = cfg
      ? await this.prisma.novasis_pay_config.findUnique({
          where: { empresa_id: empresaId },
          select: { merchant_id: true },
        })
      : null;
    return {
      activo: Boolean(cfg),
      merchantId: row?.merchant_id ?? null,
      pais: (cfg as any)?.defaultCountry ?? null,
      // Sin gateway el checkout sigue funcionando: el cliente transfiere y el
      // operador confirma a mano desde la pestaña Pedidos.
      modo: cfg ? 'novasis_pay' : 'manual',
    };
  }

  /** Listado de pagos (sesiones de checkout) + resumen por estado y método. */
  async list(empresaId: string, filtros: { estado?: string; metodo?: string; q?: string }) {
    const where: any = { empresa_id: empresaId };
    if (filtros.estado) where.estado = filtros.estado;
    if (filtros.metodo) where.metodo_pago = filtros.metodo;
    if (filtros.q?.trim()) where.codigo = { contains: filtros.q.trim(), mode: 'insensitive' };

    const [sesiones, porEstado, porMetodo, montos, gateway] = await Promise.all([
      this.prisma.ecommerce_checkout_session.findMany({
        where,
        orderBy: { created_at: 'desc' },
        take: 200,
        select: {
          id: true,
          codigo: true,
          estado: true,
          metodo_pago: true,
          payment_intent_id: true,
          payment_hosted_url: true,
          pedido_id: true,
          notas: true,
          confirmado_at: true,
          created_at: true,
          customer_snapshot: true,
          totals_snapshot: true,
        },
      }),
      this.prisma.ecommerce_checkout_session.groupBy({
        by: ['estado'],
        where: { empresa_id: empresaId },
        _count: { _all: true },
      }),
      this.prisma.ecommerce_checkout_session.groupBy({
        by: ['metodo_pago'],
        where: { empresa_id: empresaId },
        _count: { _all: true },
      }),
      // Montos globales (TODAS las sesiones de la empresa, sin el límite de 200
      // ni los filtros de la vista): coherentes con porEstado/porMetodo. Se
      // suma sobre el JSONB totals_snapshot, por eso va en SQL crudo.
      this.montosGlobales(empresaId),
      this.gatewayStatus(empresaId),
    ]);

    const { cobrado, pendiente } = montos;

    return {
      gateway,
      resumen: {
        porEstado: Object.fromEntries(porEstado.map((g) => [g.estado, g._count._all])),
        porMetodo: Object.fromEntries(porMetodo.map((g) => [g.metodo_pago ?? 'manual', g._count._all])),
        cobrado,
        pendiente,
      },
      items: sesiones.map((s) => ({
        id: s.id,
        codigo: s.codigo,
        estado: s.estado,
        metodoPago: s.metodo_pago ?? 'manual',
        intentId: s.payment_intent_id,
        hostedUrl: s.payment_hosted_url,
        pedidoId: s.pedido_id,
        notas: s.notas,
        confirmadoAt: s.confirmado_at,
        createdAt: s.created_at,
        cliente: (s.customer_snapshot as any)?.name ?? null,
        total: Number((s.totals_snapshot as any)?.total ?? 0),
        // Sólo tiene sentido reconciliar lo que sigue esperando pago por gateway.
        sincronizable: s.estado === 'pendiente_pago' && Boolean(s.payment_intent_id),
      })),
    };
  }

  /**
   * Suma global de montos por situación de cobro (todas las sesiones de la
   * empresa, sin filtros ni límite). El total vive en el JSONB
   * `totals_snapshot->>'total'`; se castea a numeric para sumar y a float8 para
   * devolver un `number` JS (no un Decimal).
   */
  private async montosGlobales(
    empresaId: string,
  ): Promise<{ cobrado: number; pendiente: number }> {
    const [row] = await this.prisma.$queryRaw<
      { cobrado: number; pendiente: number }[]
    >(Prisma.sql`
      SELECT
        COALESCE(SUM(CASE WHEN estado::text IN (${Prisma.join([...ESTADOS_COBRADOS])})
          THEN (totals_snapshot->>'total')::numeric ELSE 0 END), 0)::float8 AS cobrado,
        COALESCE(SUM(CASE WHEN estado::text = 'pendiente_pago'
          THEN (totals_snapshot->>'total')::numeric ELSE 0 END), 0)::float8 AS pendiente
      FROM ecommerce_checkout_session
      WHERE empresa_id = ${empresaId}::uuid
    `);
    return {
      cobrado: Number(row?.cobrado ?? 0),
      pendiente: Number(row?.pendiente ?? 0),
    };
  }

  /**
   * Reconcilia una sesión contra el gateway: consulta el intent y aplica el
   * resultado. Si el pago está aprobado, delega en `confirmarPagoDesdeGateway`
   * (crea el pedido ERP y reserva stock), igual que el webhook.
   *
   * Sirve para el caso en que el webhook se perdió o el cliente nunca volvió a
   * la tienda: el operador destraba el pedido desde el panel.
   */
  async sincronizar(empresaId: string, sessionId: string) {
    const s = await this.prisma.ecommerce_checkout_session.findFirst({
      where: { id: sessionId, empresa_id: empresaId },
      select: { id: true, codigo: true, estado: true, payment_intent_id: true },
    });
    if (!s) throw new NotFoundException('Pago no encontrado.');
    if (!s.payment_intent_id) {
      return { ok: false, mensaje: 'Este pedido no tiene un pago online asociado (cobro manual).' };
    }

    let intent: any;
    try {
      intent = await this.novasisPayClient.getPaymentIntent(empresaId, s.payment_intent_id);
    } catch (err) {
      const msg = err instanceof Error ? err.message : String(err);
      return { ok: false, mensaje: `No se pudo consultar el gateway: ${msg}` };
    }

    const nuevoEstado = ESTADO_GATEWAY[intent?.status];
    if (!nuevoEstado) {
      return { ok: true, mensaje: `El pago sigue en curso (estado del gateway: ${intent?.status}).`, estado: s.estado };
    }
    if (nuevoEstado === s.estado) {
      return { ok: true, mensaje: 'El pedido ya estaba sincronizado.', estado: s.estado };
    }

    if (nuevoEstado === 'pago_confirmado') {
      await this.pedidos.confirmarPagoDesdeGateway(empresaId, s.id);
      this.logger.log(`Pago ${s.codigo} reconciliado desde el panel → confirmado.`);
    } else {
      await this.prisma.ecommerce_checkout_session.updateMany({
        where: { id: s.id, estado: 'pendiente_pago' },
        data: { estado: nuevoEstado as any, updated_at: new Date() },
      });
    }

    const fresh = await this.prisma.ecommerce_checkout_session.findUnique({
      where: { id: s.id },
      select: { estado: true },
    });
    return { ok: true, mensaje: `Pago sincronizado: ${fresh?.estado}.`, estado: fresh?.estado };
  }

  /** Últimos webhooks recibidos del gateway — para diagnosticar cobros que no impactaron. */
  async webhooks(empresaId: string) {
    const eventos = await this.prisma.novasis_pay_webhook_event_in.findMany({
      where: { empresa_id: empresaId },
      orderBy: { processed_at: 'desc' },
      take: 25,
      select: {
        id: true,
        event_type: true,
        intent_id: true,
        result: true,
        error_message: true,
        processed_at: true,
      },
    });
    return eventos.map((e) => ({
      id: e.id,
      tipo: e.event_type,
      intentId: e.intent_id,
      resultado: e.result,
      error: e.error_message,
      fecha: e.processed_at,
    }));
  }
}
