import { Controller, Get, Query, UseGuards } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { ApiBearerAuth, ApiOperation, ApiQuery, ApiTags } from '@nestjs/swagger';
import type { LoginUserInfo } from 'src/auth/dto/login.dto';
import { GetUser } from 'src/auth/get-user.decorator';
import { RequireModule, RequirePermission } from 'src/auth/decorators';
import { ModuleGuard } from 'src/auth/guards/module.guard';
import { PermissionGuard } from 'src/auth/guards/permission.guard';
import { PrismaService } from '../prisma/prisma.service';

/**
 * Reporte de intereses moratorios cobrados (A.3.9).
 * Filtra por fecha_emision del recibo asociado (no por created_at del cob_interes_cobrado)
 * porque semánticamente el interés se "cobra" cuando se emite el recibo que lo incluye.
 */
@ApiTags('Reporte Intereses Cobrados')
@ApiBearerAuth()
@UseGuards(AuthGuard('jwt'), ModuleGuard, PermissionGuard)
@RequireModule('COBRANZAS')
@Controller({ path: 'reporte-intereses-cobrados', version: '1' })
export class ReporteInteresesCobradosController {
  constructor(private readonly prisma: PrismaService) {}

  @Get('resumen')
  @RequirePermission('COBRANZAS', 'COB_INT_VER')
  @ApiOperation({ summary: 'Totales y ranking por cliente / cobrador' })
  @ApiQuery({ name: 'fecha_desde', required: false, type: String })
  @ApiQuery({ name: 'fecha_hasta', required: false, type: String })
  @ApiQuery({ name: 'cobrador_id', required: false, type: String })
  @ApiQuery({ name: 'cliente_id', required: false, type: String })
  async getResumen(
    @GetUser() user: LoginUserInfo,
    @Query('fecha_desde') fechaDesde?: string,
    @Query('fecha_hasta') fechaHasta?: string,
    @Query('cobrador_id') cobradorId?: string,
    @Query('cliente_id') clienteId?: string,
  ) {
    const empresa_id = user.empresa_id;
    const desde = fechaDesde ? new Date(fechaDesde + 'T00:00:00') : this.inicioMesActual();
    const hasta = fechaHasta ? new Date(fechaHasta + 'T23:59:59') : new Date();

    const filas = await this.fetchFilas(empresa_id, desde, hasta, cobradorId, clienteId);

    // Totales globales
    const totales = filas.reduce(
      (acc, f) => ({
        cantidad_cuotas: acc.cantidad_cuotas + 1,
        monto_bruto: acc.monto_bruto + Number(f.monto_interes_bruto),
        monto_exonerado: acc.monto_exonerado + Number(f.monto_exonerado),
        monto_cobrado: acc.monto_cobrado + Number(f.monto_interes_cobrado),
        monto_iva: acc.monto_iva + Number(f.monto_iva),
      }),
      { cantidad_cuotas: 0, monto_bruto: 0, monto_exonerado: 0, monto_cobrado: 0, monto_iva: 0 },
    );

    const recibos_unicos = new Set(filas.map((f) => f.recibo_cobro_id)).size;

    // Ranking por cliente
    const porCliente = new Map<string, any>();
    for (const f of filas) {
      const cliId = f._cliente_id;
      if (!porCliente.has(cliId)) {
        porCliente.set(cliId, {
          cliente_id: cliId,
          cliente_nombre: f._cliente_nombre,
          cantidad_cuotas: 0,
          recibos: new Set<string>(),
          monto_bruto: 0,
          monto_exonerado: 0,
          monto_cobrado: 0,
        });
      }
      const acc = porCliente.get(cliId);
      acc.cantidad_cuotas += 1;
      acc.recibos.add(f.recibo_cobro_id);
      acc.monto_bruto += Number(f.monto_interes_bruto);
      acc.monto_exonerado += Number(f.monto_exonerado);
      acc.monto_cobrado += Number(f.monto_interes_cobrado);
    }
    const ranking_clientes = Array.from(porCliente.values())
      .map((c) => ({
        cliente_id: c.cliente_id,
        cliente_nombre: c.cliente_nombre,
        cantidad_cuotas: c.cantidad_cuotas,
        cantidad_recibos: c.recibos.size,
        monto_bruto: Math.round(c.monto_bruto),
        monto_exonerado: Math.round(c.monto_exonerado),
        monto_cobrado: Math.round(c.monto_cobrado),
      }))
      .sort((a, b) => b.monto_cobrado - a.monto_cobrado);

    // Ranking por cobrador
    const porCobrador = new Map<string, any>();
    for (const f of filas) {
      const cobId = f._cobrador_id ?? 'sin_cobrador';
      if (!porCobrador.has(cobId)) {
        porCobrador.set(cobId, {
          cobrador_id: f._cobrador_id,
          cobrador_nombre: f._cobrador_nombre ?? 'Sin cobrador asignado',
          cantidad_cuotas: 0,
          recibos: new Set<string>(),
          monto_bruto: 0,
          monto_exonerado: 0,
          monto_cobrado: 0,
        });
      }
      const acc = porCobrador.get(cobId);
      acc.cantidad_cuotas += 1;
      acc.recibos.add(f.recibo_cobro_id);
      acc.monto_bruto += Number(f.monto_interes_bruto);
      acc.monto_exonerado += Number(f.monto_exonerado);
      acc.monto_cobrado += Number(f.monto_interes_cobrado);
    }
    const ranking_cobradores = Array.from(porCobrador.values())
      .map((c) => ({
        cobrador_id: c.cobrador_id,
        cobrador_nombre: c.cobrador_nombre,
        cantidad_cuotas: c.cantidad_cuotas,
        cantidad_recibos: c.recibos.size,
        monto_bruto: Math.round(c.monto_bruto),
        monto_exonerado: Math.round(c.monto_exonerado),
        monto_cobrado: Math.round(c.monto_cobrado),
      }))
      .sort((a, b) => b.monto_cobrado - a.monto_cobrado);

    // Serie diaria
    const porDia: Record<string, { monto_cobrado: number; monto_exonerado: number; cantidad: number }> = {};
    for (const f of filas) {
      const dia = new Date(f._fecha).toISOString().split('T')[0];
      if (!porDia[dia]) porDia[dia] = { monto_cobrado: 0, monto_exonerado: 0, cantidad: 0 };
      porDia[dia].monto_cobrado += Number(f.monto_interes_cobrado);
      porDia[dia].monto_exonerado += Number(f.monto_exonerado);
      porDia[dia].cantidad += 1;
    }
    const serie = Object.entries(porDia)
      .map(([fecha, v]) => ({
        fecha,
        monto_cobrado: Math.round(v.monto_cobrado),
        monto_exonerado: Math.round(v.monto_exonerado),
        cantidad: v.cantidad,
      }))
      .sort((a, b) => a.fecha.localeCompare(b.fecha));

    return {
      totales: {
        cantidad_cuotas: totales.cantidad_cuotas,
        cantidad_recibos: recibos_unicos,
        monto_bruto: Math.round(totales.monto_bruto),
        monto_exonerado: Math.round(totales.monto_exonerado),
        monto_cobrado: Math.round(totales.monto_cobrado),
        monto_iva: Math.round(totales.monto_iva),
        tasa_exoneracion:
          totales.monto_bruto > 0
            ? Math.round((totales.monto_exonerado / totales.monto_bruto) * 100)
            : 0,
      },
      ranking_clientes,
      ranking_cobradores,
      serie,
      periodo: { desde: desde.toISOString(), hasta: hasta.toISOString() },
    };
  }

  @Get('detalle')
  @RequirePermission('COBRANZAS', 'COB_INT_VER')
  @ApiOperation({ summary: 'Detalle paginado de intereses cobrados' })
  @ApiQuery({ name: 'fecha_desde', required: false, type: String })
  @ApiQuery({ name: 'fecha_hasta', required: false, type: String })
  @ApiQuery({ name: 'cobrador_id', required: false, type: String })
  @ApiQuery({ name: 'cliente_id', required: false, type: String })
  @ApiQuery({ name: 'page', required: false, type: Number })
  @ApiQuery({ name: 'pageSize', required: false, type: Number })
  async getDetalle(
    @GetUser() user: LoginUserInfo,
    @Query('fecha_desde') fechaDesde?: string,
    @Query('fecha_hasta') fechaHasta?: string,
    @Query('cobrador_id') cobradorId?: string,
    @Query('cliente_id') clienteId?: string,
    @Query('page') page = '1',
    @Query('pageSize') pageSize = '20',
  ) {
    const empresa_id = user.empresa_id;
    const desde = fechaDesde ? new Date(fechaDesde + 'T00:00:00') : this.inicioMesActual();
    const hasta = fechaHasta ? new Date(fechaHasta + 'T23:59:59') : new Date();
    const pageNum = Math.max(1, parseInt(page));
    const size = Math.min(100, Math.max(1, parseInt(pageSize)));

    const filas = await this.fetchFilas(empresa_id, desde, hasta, cobradorId, clienteId);
    filas.sort((a, b) => new Date(b._fecha).getTime() - new Date(a._fecha).getTime());

    const total = filas.length;
    const start = (pageNum - 1) * size;
    const slice = filas.slice(start, start + size);

    // Fetch info adicional de cuotas para el slice
    const cuotaIds = slice.map((f) => f.factura_cuota_id);
    const cuotas = cuotaIds.length
      ? await this.prisma.factura_cuotas.findMany({
          where: { id: { in: cuotaIds } },
          select: {
            id: true,
            nro_cuota: true,
            dvenccuo: true,
            factura_cab: { select: { dest: true, dpunexp: true, dnumdoc: true } },
          },
        })
      : [];
    const cuotaMap = new Map(cuotas.map((c) => [c.id, c]));

    const data = slice.map((f) => {
      const cuota = cuotaMap.get(f.factura_cuota_id);
      return {
        id: f.id,
        fecha: new Date(f._fecha).toISOString().split('T')[0],
        recibo: { id: f.recibo_cobro_id, numero: f._numero_recibo },
        cliente: { id: f._cliente_id, nombre: f._cliente_nombre },
        cobrador: f._cobrador_id
          ? { id: f._cobrador_id, nombre: f._cobrador_nombre }
          : null,
        factura: cuota?.factura_cab
          ? `${cuota.factura_cab.dest}-${cuota.factura_cab.dpunexp}-${cuota.factura_cab.dnumdoc}`
          : '—',
        nro_cuota: cuota?.nro_cuota ?? null,
        vencimiento: cuota?.dvenccuo?.toISOString().split('T')[0] ?? null,
        dias_mora: f.dias_mora,
        tasa_aplicada: Number(f.tasa_aplicada),
        base_calculo: Number(f.base_calculo),
        monto_bruto: Number(f.monto_interes_bruto),
        monto_exonerado: Number(f.monto_exonerado),
        monto_cobrado: Number(f.monto_interes_cobrado),
        monto_iva: Number(f.monto_iva),
        tipo_comprobante: f.tipo_comprobante,
        emitido: !!f.emitido_at,
        autorizacion_descuento_id: f.autorizacion_descuento_id,
      };
    });

    return { data, total, page: pageNum, pageSize: size, totalPages: Math.ceil(total / size) };
  }

  /**
   * Fetch agregado: trae todas las filas de cob_interes_cobrado del período
   * junto con datos enriquecidos del recibo (cliente + cobrador). Filtra por
   * fecha_emision del recibo (no del registro) y aplica los filtros opcionales.
   */
  private async fetchFilas(
    empresa_id: string,
    desde: Date,
    hasta: Date,
    cobradorId?: string,
    clienteId?: string,
  ) {
    // 1) Recibos del período (con cobrador + cliente)
    const reciboWhere: any = {
      empresa_id,
      estado: { not: 'anulado' },
      fecha_emision: { gte: desde, lte: hasta },
    };
    if (cobradorId) reciboWhere.cobrador_id = cobradorId;
    if (clienteId) reciboWhere.cliente_id = clienteId;

    const recibos = await this.prisma.recibos_cobro.findMany({
      where: reciboWhere,
      select: {
        id: true,
        numero_recibo: true,
        fecha_emision: true,
        cobrador_id: true,
        cliente_id: true,
        clientes: {
          select: {
            id: true,
            nombre_fantasia: true,
            personas: { select: { razon_social: true } },
          },
        },
      },
    });

    if (recibos.length === 0) return [];

    const reciboMap = new Map(recibos.map((r) => [r.id, r]));
    const reciboIds = recibos.map((r) => r.id);

    // 2) Cobradores asociados
    const cobradorIds = Array.from(
      new Set(recibos.map((r) => r.cobrador_id).filter((x): x is string => !!x)),
    );
    const cobradores = cobradorIds.length
      ? await this.prisma.vendedores_cobradores.findMany({
          where: { id: { in: cobradorIds } },
          select: { id: true, nombre: true, apellido: true },
        })
      : [];
    const cobMap = new Map(cobradores.map((c) => [c.id, c]));

    // 3) Intereses cobrados de esos recibos
    const intereses = await this.prisma.cob_interes_cobrado.findMany({
      where: { empresa_id, recibo_cobro_id: { in: reciboIds } },
    });

    return intereses.map((i) => {
      const r = reciboMap.get(i.recibo_cobro_id);
      const cob = r?.cobrador_id ? cobMap.get(r.cobrador_id) : undefined;
      const clienteNombre =
        r?.clientes?.nombre_fantasia ||
        r?.clientes?.personas?.razon_social ||
        '—';
      return {
        ...i,
        _fecha: r?.fecha_emision ?? i.created_at,
        _numero_recibo: r?.numero_recibo,
        _cliente_id: r?.cliente_id ?? '',
        _cliente_nombre: clienteNombre,
        _cobrador_id: r?.cobrador_id ?? null,
        _cobrador_nombre: cob ? `${cob.nombre} ${cob.apellido ?? ''}`.trim() : null,
      };
    });
  }

  private inicioMesActual() {
    const d = new Date();
    d.setDate(1);
    d.setHours(0, 0, 0, 0);
    return d;
  }
}
