import { BadRequestException, Injectable } from '@nestjs/common';
import { presupuesto_estado } from '@prisma/client';
import { PrismaService } from 'src/prisma/prisma.service';
import { resolverPermisos } from 'src/productos/permisos-columnas';
import { alcanceSucursalUsuario, filtroSucursalOperativo } from 'src/utils/alcance-sucursal';
import { QueryPresupuestosDto } from './dto/query-presupuestos.dto';
import { armarResumen, PresupuestoResumen, ProductoInfo } from './presupuestos-resumen';
import { wherePresupuestos } from './presupuestos.service';

/** Sin filtro de estado, el resumen deja afuera lo que no se va a vender. */
export const ESTADOS_FUERA_DEL_RESUMEN: presupuesto_estado[] = [
  presupuesto_estado.REJECTED,
  presupuesto_estado.EXPIRED,
  presupuesto_estado.CANCELLED,
  presupuesto_estado.SUPERSEDED,
];

/** Tope de presupuestos por resumen: más que eso es un rango de fechas demasiado amplio. */
const MAX_PRESUPUESTOS = 2000;

/** Mismo permiso que muestra costos y valorización en Inventario. */
const PERMISO_COSTO = 'INV_STK_COSTO_VER';

@Injectable()
export class PresupuestosResumenService {
  constructor(private readonly prisma: PrismaService) {}

  async resumen(query: Omit<QueryPresupuestosDto, 'page' | 'limit'>, empresa_id: string, usuarioId: string) {
    const alcance = await alcanceSucursalUsuario(this.prisma, usuarioId, empresa_id);
    const where = {
      ...wherePresupuestos(query, empresa_id, filtroSucursalOperativo(alcance)),
      ...(query.estado ? {} : { estado: { notIn: ESTADOS_FUERA_DEL_RESUMEN } }),
    };

    const cantidad = await this.prisma.presupuesto_cab.count({ where });
    if (cantidad > MAX_PRESUPUESTOS) {
      throw new BadRequestException(
        `El filtro trae ${cantidad} presupuestos; el resumen admite hasta ${MAX_PRESUPUESTOS}. Acotá el rango de fechas o elegí un vendedor.`,
      );
    }

    const cabs = await this.prisma.presupuesto_cab.findMany({
      where,
      orderBy: [{ fecha_emision: 'asc' }, { numero: 'asc' }],
      select: {
        id: true,
        numero: true,
        fecha_emision: true,
        estado: true,
        cliente_id: true,
        clientes: { select: { personas: { select: { razon_social: true, ruc: true } } } },
        vendedor: { select: { nombre: true, apellido: true } },
        moneda: { select: { codigo: true } },
        items: {
          orderBy: { orden: 'asc' },
          select: {
            tipo: true,
            producto_id: true,
            descripcion: true,
            cantidad: true,
            unidad_medida: true,
            precio_unitario: true,
            subtotal_con_iva: true,
          },
        },
      },
    });

    const productoIds = [...new Set(cabs.flatMap((c) => c.items.map((i) => i.producto_id)).filter(Boolean))] as string[];
    const productos = productoIds.length
      ? await this.prisma.productos.findMany({
          where: { id: { in: productoIds }, empresa_id },
          select: {
            id: true,
            cod_producto: true,
            descripcion: true,
            precio_costo: true,
            producto_presentaciones: {
              // Mismo criterio que la carga de viajes (por defecto, después orden) para que
              // los dos reportes hablen de la misma caja; entre iguales, la más grande.
              where: { active: true, factor_conversion: { gt: 1 } },
              orderBy: [{ es_default: 'desc' }, { orden: 'asc' }, { factor_conversion: 'desc' }],
              take: 1,
              select: { nombre: true, factor_conversion: true },
            },
          },
        })
      : [];
    const infoProductos = new Map<string, ProductoInfo>(
      productos.map((p) => [
        p.id,
        {
          id: p.id,
          codigo: p.cod_producto ?? null,
          descripcion: p.descripcion ?? '',
          costo: p.precio_costo != null && Number(p.precio_costo) > 0 ? Number(p.precio_costo) : null,
          empaque: p.producto_presentaciones[0]
            ? { nombre: p.producto_presentaciones[0].nombre, factor: Number(p.producto_presentaciones[0].factor_conversion) }
            : null,
        },
      ]),
    );

    const verCosto = (await resolverPermisos(this.prisma, usuarioId, [PERMISO_COSTO])).has(PERMISO_COSTO);

    const presupuestos: PresupuestoResumen[] = cabs.map((c) => ({
      id: c.id,
      numero: c.numero,
      fecha: c.fecha_emision ? c.fecha_emision.toISOString().slice(0, 10) : null,
      estado: c.estado,
      moneda: c.moneda?.codigo ?? 'PYG',
      cliente: {
        id: c.cliente_id ?? null,
        nombre: c.clientes?.personas?.razon_social ?? 'Sin cliente',
        documento: c.clientes?.personas?.ruc ?? null,
      },
      vendedor: c.vendedor ? `${c.vendedor.nombre ?? ''} ${c.vendedor.apellido ?? ''}`.trim() : null,
      items: c.items.map((i) => ({
        tipo: i.tipo,
        producto_id: i.producto_id,
        descripcion: i.descripcion,
        cantidad: Number(i.cantidad),
        unidad_medida: i.unidad_medida,
        precio_unitario: Number(i.precio_unitario),
        total: Number(i.subtotal_con_iva),
      })),
    }));

    return armarResumen(presupuestos, infoProductos, { verCosto });
  }
}
