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';

@ApiTags('Reporte Cobrador')
@ApiBearerAuth()
@UseGuards(AuthGuard('jwt'), ModuleGuard, PermissionGuard)
@RequireModule('COBRANZAS')
@Controller({ path: 'reporte-cobrador', version: '1' })
export class ReporteCobradorController {
  constructor(private readonly prisma: PrismaService) {}

  /**
   * Resumen de cobros por cobrador dentro del rango de fechas.
   * Incluye: total cobrado, cantidad cobros, cuotas vencidas pendientes, tasa morosidad.
   */
  @Get('por-cobrador')
  @RequirePermission('COBRANZAS', 'COB_REP_REPORTE_VER')
  @ApiOperation({ summary: 'Estadísticas de cobros agrupadas por cobrador' })
  @ApiQuery({ name: 'fecha_desde', required: false, type: String, description: 'YYYY-MM-DD' })
  @ApiQuery({ name: 'fecha_hasta', required: false, type: String, description: 'YYYY-MM-DD' })
  async getPorCobrador(
    @GetUser() user: LoginUserInfo,
    @Query('fecha_desde') fechaDesde?: string,
    @Query('fecha_hasta') fechaHasta?: 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();

    // Todos los cobradores activos de la empresa
    const cobradores = await this.prisma.vendedores_cobradores.findMany({
      where: { empresa_id, active: true },
      select: { id: true, nombre: true, apellido: true },
      orderBy: { nombre: 'asc' },
    });

    const hoy = new Date();
    hoy.setHours(0, 0, 0, 0);

    // Para cada cobrador: cobros en rango + cuotas pendientes/vencidas
    const resultados = await Promise.all(
      cobradores.map(async (cob) => {
        // Recibos emitidos en el período
        const recibos = await this.prisma.recibos_cobro.findMany({
          where: {
            empresa_id,
            cobrador_id: cob.id,
            estado: { not: 'anulado' },
            fecha_emision: { gte: desde, lte: hasta },
          },
          select: {
            monto_total: true,
            descuento_monto: true,
            mora_total: true,
            fecha_emision: true,
          },
        });

        const total_cobrado = recibos.reduce((s, r) => s + Number(r.monto_total), 0);
        const total_descuentos = recibos.reduce((s, r) => s + Number(r.descuento_monto || 0), 0);
        const total_mora_cobrada = recibos.reduce((s, r) => s + Number(r.mora_total || 0), 0);
        const cantidad_cobros = recibos.length;

        // Cuotas pendientes asignadas a este cobrador
        const whereBase: any = {
          cuotas_calculadas: {
            factura_cab: {
              empresa_id,
              OR: [
                { cobrador_id: cob.id },
                { asignacion_facturas: { some: { cobrador_id: cob.id, estado: { not: 'cancelado' } } } },
              ],
            },
          },
          monto_pagado: { equals: 0 },
        };

        const [total_pendientes, total_vencidas] = await Promise.all([
          this.prisma.cuotas_calculadas_detalle.count({ where: whereBase }),
          this.prisma.cuotas_calculadas_detalle.count({
            where: { ...whereBase, fecha_vencimiento: { lt: hoy } },
          }),
        ]);

        // Monto pendiente total
        const cuotasPendientes = await this.prisma.cuotas_calculadas_detalle.aggregate({
          where: whereBase,
          _sum: { monto_cuota: true },
        });

        const tasa_morosidad =
          total_pendientes > 0 ? Math.round((total_vencidas / total_pendientes) * 100) : 0;

        // Productividad diaria dentro del período (cobros por día)
        const cobrosPorDia: Record<string, number> = {};
        for (const r of recibos) {
          const dia = new Date(r.fecha_emision).toISOString().split('T')[0];
          cobrosPorDia[dia] = (cobrosPorDia[dia] || 0) + 1;
        }
        const dias_activos = Object.keys(cobrosPorDia).length;
        const promedio_diario = dias_activos > 0 ? Math.round(total_cobrado / dias_activos) : 0;

        return {
          cobrador: { id: cob.id, nombre: cob.nombre, apellido: cob.apellido },
          cobros: {
            total_cobrado: Math.round(total_cobrado),
            total_descuentos: Math.round(total_descuentos),
            total_mora_cobrada: Math.round(total_mora_cobrada),
            cantidad_cobros,
            promedio_por_cobro: cantidad_cobros > 0 ? Math.round(total_cobrado / cantidad_cobros) : 0,
            dias_activos,
            promedio_diario,
          },
          pendientes: {
            total_pendientes,
            total_vencidas,
            monto_pendiente: Math.round(Number(cuotasPendientes._sum.monto_cuota || 0)),
            tasa_morosidad,
          },
        };
      }),
    );

    // Totales globales
    const totales = resultados.reduce(
      (acc, r) => ({
        total_cobrado: acc.total_cobrado + r.cobros.total_cobrado,
        cantidad_cobros: acc.cantidad_cobros + r.cobros.cantidad_cobros,
        total_descuentos: acc.total_descuentos + r.cobros.total_descuentos,
        total_mora_cobrada: acc.total_mora_cobrada + r.cobros.total_mora_cobrada,
        total_pendientes: acc.total_pendientes + r.pendientes.total_pendientes,
        total_vencidas: acc.total_vencidas + r.pendientes.total_vencidas,
        monto_pendiente: acc.monto_pendiente + r.pendientes.monto_pendiente,
      }),
      { total_cobrado: 0, cantidad_cobros: 0, total_descuentos: 0, total_mora_cobrada: 0, total_pendientes: 0, total_vencidas: 0, monto_pendiente: 0 },
    );

    return { cobradores: resultados, totales, periodo: { desde: desde.toISOString(), hasta: hasta.toISOString() } };
  }

  /**
   * Productividad diaria: cobros por día en el rango.
   * Filtrando opcionalmente por cobrador_id.
   */
  @Get('productividad')
  @RequirePermission('COBRANZAS', 'COB_REP_REPORTE_VER')
  @ApiOperation({ summary: 'Cobros por día en el rango, opcionalmente filtrado por 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 })
  async getProductividad(
    @GetUser() user: LoginUserInfo,
    @Query('fecha_desde') fechaDesde?: string,
    @Query('fecha_hasta') fechaHasta?: string,
    @Query('cobrador_id') cobradorId?: 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 where: any = {
      empresa_id,
      estado: { not: 'anulado' },
      fecha_emision: { gte: desde, lte: hasta },
    };
    if (cobradorId) where.cobrador_id = cobradorId;

    const recibos = await this.prisma.recibos_cobro.findMany({
      where,
      select: { monto_total: true, fecha_emision: true, cobrador_id: true },
      orderBy: { fecha_emision: 'asc' },
    });

    // Agrupar por día
    const porDia: Record<string, { monto: number; cantidad: number }> = {};
    for (const r of recibos) {
      const dia = new Date(r.fecha_emision).toISOString().split('T')[0];
      if (!porDia[dia]) porDia[dia] = { monto: 0, cantidad: 0 };
      porDia[dia].monto += Number(r.monto_total);
      porDia[dia].cantidad += 1;
    }

    const dias = Object.entries(porDia)
      .map(([fecha, stats]) => ({
        fecha,
        monto: Math.round(stats.monto),
        cantidad: stats.cantidad,
      }))
      .sort((a, b) => a.fecha.localeCompare(b.fecha));

    const total_monto = dias.reduce((s, d) => s + d.monto, 0);
    const total_cantidad = dias.reduce((s, d) => s + d.cantidad, 0);
    const max_dia = dias.reduce((max, d) => (d.monto > max ? d.monto : max), 0);

    return { dias, totales: { monto: total_monto, cantidad: total_cantidad }, max_dia };
  }

  /**
   * Detalle de cobros de un cobrador: lista de recibos con cliente, facturas y montos.
   */
  @Get('detalle')
  @RequirePermission('COBRANZAS', 'COB_REP_REPORTE_VER')
  @ApiOperation({ summary: 'Detalle de recibos por cobrador con cliente y facturas' })
  @ApiQuery({ name: 'cobrador_id', required: true, type: String })
  @ApiQuery({ name: 'fecha_desde', required: false, type: String })
  @ApiQuery({ name: 'fecha_hasta', required: false, type: String })
  @ApiQuery({ name: 'page', required: false, type: Number })
  @ApiQuery({ name: 'pageSize', required: false, type: Number })
  async getDetalle(
    @GetUser() user: LoginUserInfo,
    @Query('cobrador_id') cobradorId: string,
    @Query('fecha_desde') fechaDesde?: string,
    @Query('fecha_hasta') fechaHasta?: 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 skip = (pageNum - 1) * size;

    const where = {
      empresa_id,
      cobrador_id: cobradorId,
      estado: { not: 'anulado' },
      fecha_emision: { gte: desde, lte: hasta },
    };

    const [recibos, total] = await Promise.all([
      this.prisma.recibos_cobro.findMany({
        where,
        select: {
          id: true,
          numero_recibo: true,
          fecha_emision: true,
          fecha_registro: true,
          monto_total: true,
          descuento_monto: true,
          mora_total: true,
          estado: true,
          clientes: {
            select: {
              id: true,
              nombre_fantasia: true,
              personas: { select: { razon_social: true, telefono: true } },
            },
          },
          recibo_cobro_detalle: {
            select: {
              id: true,
              monto_pagado: true,
              descuento_aplicado: true,
              mora_monto: true,
              es_pago_parcial: true,
              factura_cuotas: {
                select: {
                  nro_cuota: true,
                  dmoncuota: true,
                  dvenccuo: true,
                  factura_cab: {
                    select: {
                      id: true,
                      dest: true,
                      dpunexp: true,
                      dnumdoc: true,
                    },
                  },
                },
              },
            },
          },
        },
        orderBy: { fecha_emision: 'desc' },
        skip,
        take: size,
      }),
      this.prisma.recibos_cobro.count({ where }),
    ]);

    const data = recibos.map((r) => ({
      id: r.id,
      numero_recibo: r.numero_recibo,
      fecha: (r.fecha_registro ?? r.fecha_emision)?.toISOString().split('T')[0],
      estado: r.estado,
      cliente: {
        id: r.clientes?.id,
        nombre: r.clientes?.nombre_fantasia || r.clientes?.personas?.razon_social || '—',
        telefono: r.clientes?.personas?.telefono,
      },
      monto_total: Number(r.monto_total),
      descuento_monto: Number(r.descuento_monto || 0),
      mora_total: Number(r.mora_total || 0),
      detalles: r.recibo_cobro_detalle.map((d) => ({
        factura: d.factura_cuotas?.factura_cab
          ? `${d.factura_cuotas.factura_cab.dest}-${d.factura_cuotas.factura_cab.dpunexp}-${d.factura_cuotas.factura_cab.dnumdoc}`
          : '—',
        nro_cuota: d.factura_cuotas?.nro_cuota,
        vencimiento: d.factura_cuotas?.dvenccuo?.toISOString().split('T')[0],
        monto_cuota: Number(d.factura_cuotas?.dmoncuota || 0),
        monto_pagado: Number(d.monto_pagado),
        descuento: Number(d.descuento_aplicado || 0),
        mora: Number(d.mora_monto || 0),
        parcial: d.es_pago_parcial,
      })),
    }));

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

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