import { ConflictException } from '@nestjs/common';
import { inm_unidad_estado, Prisma } from '@prisma/client';

/** 'AAAA-MM-DD' (o ISO) → fecha sin hora; null borra, undefined no toca. */
export function aFecha(valor: string | null | undefined): Date | null | undefined {
  if (valor === undefined) return undefined;
  if (valor === null || valor === '') return null;
  return new Date(`${valor.slice(0, 10)}T00:00:00.000Z`);
}

/** Fecha de hoy en Paraguay, AAAA-MM-DD. */
export function hoyPy(): string {
  return new Date(Date.now() - 3 * 3600_000).toISOString().slice(0, 10);
}

/** Suma días a una fecha AAAA-MM-DD. */
export function sumarDias(fecha: string, dias: number): string {
  const d = new Date(`${fecha.slice(0, 10)}T00:00:00.000Z`);
  d.setUTCDate(d.getUTCDate() + dias);
  return d.toISOString().slice(0, 10);
}

export const isoDia = (d: Date | null | undefined) => (d ? d.toISOString().slice(0, 10) : null);

/** Guaraníes sin decimales; el resto de las monedas, con centavos. */
export const decimalesDe = (codigoMoneda?: string | null) => (!codigoMoneda || codigoMoneda === 'PYG' ? 0 : 2);

export const redondear = (n: number, dec: number) => Math.round(n * 10 ** dec) / 10 ** dec;

/**
 * Transición de estado de una unidad con bloqueo optimista: sólo pasa si la unidad
 * sigue en uno de los estados esperados (y en la versión leída, si se indica).
 * Dos vendedores reservando la misma unidad a la vez: uno pasa, el otro recibe 409.
 */
export async function transicionarUnidad(
  tx: Prisma.TransactionClient,
  args: {
    id: string;
    empresa_id: string;
    desde: inm_unidad_estado[];
    hacia: inm_unidad_estado;
    version?: number;
    extra?: Prisma.inm_unidadesUncheckedUpdateManyInput;
  },
) {
  const r = await tx.inm_unidades.updateMany({
    where: {
      id: args.id,
      empresa_id: args.empresa_id,
      estado: { in: args.desde },
      ...(args.version !== undefined && { version: args.version }),
    },
    data: { ...args.extra, estado: args.hacia, version: { increment: 1 }, updated_at: new Date() },
  });
  if (r.count === 0) {
    throw new ConflictException(
      'La unidad cambió de estado mientras la mirabas (otro usuario la tomó). Recargá y volvé a intentar.',
    );
  }
}

type PrismaLike = Pick<Prisma.TransactionClient, 'usuario' | 'clientes'>;

/** ¿El usuario tiene este privilegio? Superadmin siempre; el resto, por sus perfiles. */
export async function tienePrivilegio(
  prisma: PrismaLike,
  user_id: string | undefined,
  codigo: string,
): Promise<boolean> {
  if (!user_id) return false;
  const u = await prisma.usuario.findUnique({
    where: { id: user_id },
    select: {
      is_superadmin: true,
      usuario_perfiles: {
        select: {
          perfiles: { select: { perfiles_privilegios: { select: { privilegios: { select: { codigo: true } } } } } },
        },
      },
    },
  });
  if (!u) return false;
  if (u.is_superadmin) return true;
  return u.usuario_perfiles.some((up) =>
    up.perfiles?.perfiles_privilegios?.some((pp) => pp.privilegios?.codigo === codigo),
  );
}

export interface DatosCliente {
  id: string;
  nombre: string;
  documento: string | null;
  telefono: string | null;
  email: string | null;
}

/** Nombre y documento de clientes del ERP (clientes → personas), sin relación Prisma desde el vertical. */
export async function datosClientes(
  prisma: PrismaLike,
  empresa_id: string,
  ids: string[],
): Promise<Map<string, DatosCliente>> {
  const unicos = [...new Set(ids.filter(Boolean))];
  if (!unicos.length) return new Map();
  const filas = await prisma.clientes.findMany({
    where: { id: { in: unicos }, personas: { empresa_id } },
    select: {
      id: true,
      personas: {
        select: { razon_social: true, ruc: true, dv: true, nro_documento: true, telefono: true, email: true },
      },
    },
  });
  return new Map(
    filas.map((c) => [
      c.id,
      {
        id: c.id,
        nombre: c.personas?.razon_social ?? '—',
        documento: c.personas?.ruc
          ? `${c.personas.ruc}${c.personas.dv ? `-${c.personas.dv}` : ''}`
          : (c.personas?.nro_documento ?? null),
        telefono: c.personas?.telefono ?? null,
        email: c.personas?.email ?? null,
      },
    ]),
  );
}

/**
 * Copia del cliente para meter en una respuesta. El BigIntSerializerInterceptor
 * global reemplaza por "[Circular]" un objeto que aparece dos veces en la misma
 * respuesta (p. ej. el comprador que también es titular, o el mismo cliente en
 * dos filas de un listado): por eso nunca se devuelve la instancia del Map.
 */
export const clienteDe = (mapa: Map<string, DatosCliente>, id: string | null | undefined): DatosCliente | null => {
  const c = id ? mapa.get(id) : undefined;
  return c ? { ...c } : null;
};
