import { BadRequestException, ConflictException, ForbiddenException, Injectable, NotFoundException, Logger } from '@nestjs/common';
import { CobMoraEstado } from '@prisma/client';
import { AuditService } from 'src/audit/audit.service';
import { PersonasService } from 'src/personas/personas.service';
import { PlanLimitsService } from 'src/plan-limits/plan-limits.service';
import { PrismaService } from 'src/prisma/prisma.service';
import { CreateClienteDto } from './dto/create-cliente.dto';
import { UpdateClienteDto } from './dto/update-cliente.dto';

const MORA_ESTADOS_ACTIVOS: CobMoraEstado[] = [
  CobMoraEstado.GESTION_INTERNA,
  CobMoraEstado.INFORMCONF,
  CobMoraEstado.DEMANDA,
];

@Injectable()
export class ClientesService {
  private readonly logger = new Logger(ClientesService.name);
  constructor(
    private prisma: PrismaService,
    private readonly personasService: PersonasService,
    private readonly auditService: AuditService,
    private readonly planLimits: PlanLimitsService,
  ) {}

  async create(createClienteDto: CreateClienteDto, empresa_id: string, user_id?: string) {
    try {
      await this.planLimits.checkLimit(empresa_id, 'clientes');

      // Validar duplicado de RUC o nro_documento
      if (createClienteDto.ruc || createClienteDto.nro_documento) {
        const orDup: any[] = [];
        if (createClienteDto.ruc) orDup.push({ ruc: createClienteDto.ruc });
        if (createClienteDto.nro_documento) orDup.push({ nro_documento: createClienteDto.nro_documento });
        const dupCliente = await this.prisma.clientes.findFirst({
          where: { deleted: false, personas: { empresa_id, deleted: false, OR: orDup } },
          include: { personas: { select: { razon_social: true, ruc: true, nro_documento: true } } },
        });
        if (dupCliente) {
          const campo = createClienteDto.ruc ? `RUC ${createClienteDto.ruc}` : `Documento ${createClienteDto.nro_documento}`;
          throw new ConflictException(`Ya existe un cliente con ${campo}: ${dupCliente.personas?.razon_social}`);
        }
      }

      // Validar que no se cree otro cliente con razón social "Sin nombre"
      if (createClienteDto.razon_social?.toLowerCase().trim() === 'sin nombre') {
        const existeSinNombre = await this.prisma.clientes.findFirst({
          where: {
            deleted: false,
            personas: {
              empresa_id,
              razon_social: { equals: 'Sin nombre', mode: 'insensitive' },
            },
          },
        });
        if (existeSinNombre) {
          throw new ConflictException('Ya existe un cliente "Sin nombre" en esta empresa');
        }
      }

      const persona = await this.personasService.create(createClienteDto, empresa_id);
      // Verifico si ya hay un cliente vinculado a esa persona
      const clienteFind = await this.prisma.clientes.findFirst({
        where: { persona_id: persona.id },
      });
      if (clienteFind && !clienteFind.deleted) throw new ConflictException('El cliente ya existe');

      const cliente = await this.prisma.clientes.create({
        data: {
          persona_id: persona.id,
          tipo_operacion_id: createClienteDto.tipo_operacion_id,
          nombre_fantasia: createClienteDto.nombre_fantasia,
          cod_cliente: createClienteDto.cod_cliente,
          tipo_cliente: createClienteDto.tipo_cliente || null,
          lista_precios_id: createClienteDto.lista_precios_id || null,
          zona: createClienteDto.zona || null,
          referencia_domicilio: createClienteDto.referencia_domicilio || null,
          cobrador_id: createClienteDto.cobrador_id || null,
          es_agente_retencion: createClienteDto.es_agente_retencion ?? false,
          limite_credito: createClienteDto.limite_credito || null,
          credito_habilitado: createClienteDto.credito_habilitado ?? true,
          moneda_credito: createClienteDto.moneda_credito || null,
          dias_mora_maximo: createClienteDto.dias_mora_maximo ?? null,
          bloqueado_credito: createClienteDto.bloqueado_credito ?? false,
        },
      });
      this.logger.log(`Cliente creado: ${persona.razon_social} (${cliente.id})`, 'ClientesService');
      await this.auditService.log({
        empresa_id,
        user_id,
        action: 'CREATE',
        entity_type: 'cliente',
        entity_id: cliente.id,
        descripcion: `Cliente creado: ${persona.razon_social} | Código: ${cliente.cod_cliente ?? '-'}`,
        new_value: { razon_social: persona.razon_social, ruc: persona.ruc, cod_cliente: cliente.cod_cliente },
      });
      return {
        message: 'Cliente creado exitosamente',
        data: { persona: persona, cliente: cliente },
      };
    } catch (error) {
      console.log(error);
      if (error instanceof ConflictException) throw error;
      if (error instanceof NotFoundException) throw error;
      if (error instanceof ForbiddenException) throw error;

      const message = error instanceof Error ? error.message : 'Error desconocido';
      throw new Error(`Error al crear cliente: ${message}`);
    }
  }

  async search(query: string, empresa_id: string, limit = 20) {
    const q = (query ?? '').trim().replace(/^\+/, '');
    if (q.length < 2) {
      return [];
    }

    const clientes = await this.prisma.clientes.findMany({
      where: {
        deleted: false,
        personas: {
          empresa_id,
          OR: [
            { razon_social: { contains: q, mode: 'insensitive' } },
            { ruc: { contains: q, mode: 'insensitive' } },
            { nro_documento: { contains: q, mode: 'insensitive' } },
          ],
        },
      },
      select: {
        id: true,
        nombre_fantasia: true,
        cod_cliente: true,
        tipo_cliente: true,
        zona: true,
        lista_precios_id: true,
        cobrador_id: true,
        es_agente_retencion: true,
        cobrador: {
          select: { id: true, nombre: true, apellido: true, tipo: true },
        },
        tipo_operacion: true,
        lista_precios: {
          select: {
            id: true,
            nombre: true,
            codigo: true,
            moneda: true,
          },
        },
        active: true,
        personas: {
          select: {
            id: true,
            razon_social: true,
            ruc: true,
            dv: true,
            naturaleza_receptor: true,
            tipo_contribuyente: true,
            tipo_documento_identidad: true,
            nro_documento: true,
            direccion: true,
            telefono: true,
            email: true,
            paises: true,
          },
        },
      },
      take: limit,
      orderBy: { personas: { razon_social: 'asc' } },
    });

    return clientes;
  }

  async findAll(page = 1, limit = 10, empresa_id: string, q?: string, mora_estado?: string) {
    const take = Math.max(1, Math.min(100, Number(limit) || 10));
    const currentPage = Math.max(1, Number(page) || 1);
    const skip = (currentPage - 1) * take;

    const personaWhere: any = { empresa_id };
    const clienteWhere: any = { deleted: false, personas: personaWhere };

    if (q && q.trim().length >= 2) {
      const termino = q.trim();
      // `cod_cliente` vive en `clientes` y el resto en `personas`, así que el OR
      // va a nivel del cliente. Sin el código no había forma de pedir un cliente
      // puntual: el POS traía una página y filtraba en memoria, y el "Sin nombre"
      // (SN001), que es el más antiguo de cada empresa, nunca entraba.
      clienteWhere.OR = [
        { cod_cliente: { contains: termino, mode: 'insensitive' } },
        {
          personas: {
            ...personaWhere,
            OR: [
              { razon_social: { contains: termino, mode: 'insensitive' } },
              { ruc: { contains: termino, mode: 'insensitive' } },
              { nro_documento: { contains: termino, mode: 'insensitive' } },
            ],
          },
        },
      ];
    }
    if (mora_estado) {
      const estadoFilter =
        mora_estado === 'ACTIVOS'
          ? { in: MORA_ESTADOS_ACTIVOS }
          : (CobMoraEstado as any)[mora_estado]
            ? { equals: (CobMoraEstado as any)[mora_estado] }
            : null;
      if (estadoFilter) {
        clienteWhere.cob_gestion_mora = {
          some: { empresa_id, estado_actual: estadoFilter },
        };
      }
    }

    const [items, total] = await this.prisma.$transaction([
      this.prisma.clientes.findMany({
        where: clienteWhere,
        select: {
          id: true,
          nombre_fantasia: true,
          cod_cliente: true,
          tipo_cliente: true,
          zona: true,
          referencia_domicilio: true,
          lista_precios_id: true,
          tipo_operacion: true,
          lista_precios: {
            select: {
              id: true,
              nombre: true,
              codigo: true,
            },
          },
          cobrador_id: true,
          es_agente_retencion: true,
          cobrador: {
            select: { id: true, nombre: true, apellido: true, tipo: true },
          },
          active: true,
          created_at: true,
          updated_at: true,
          personas: {
            select: {
              id: true,
              razon_social: true,
              ruc: true,
              dv: true,
              naturaleza_receptor: true,
              tipo_contribuyente: true,
              tipo_documento_identidad: true,
              nro_documento: true,
              direccion: true,
              nro_casa: true,
              telefono: true,
              celular: true,
              email: true,
              active: true,
              deleted: true,
              deleted_at: true,
              created_at: true,
              updated_at: true,
              usuario: true,
              pais_id: true,
              ciudad_id: true,
              paises: {
                select: {
                  id: true,
                  codigo: true,
                  descripcion: true,
                },
              },
              ciudades: {
                select: {
                  id: true,
                  codigo: true,
                  descripcion: true,
                  distrito_id: true,
                  distritos: {
                    select: {
                      id: true,
                      codigo: true,
                      descripcion: true,
                      departamento_id: true,
                      departamentos: {
                        select: {
                          id: true,
                          codigo: true,
                          descripcion: true,
                          paises: {
                            select: {
                              id: true,
                              codigo: true,
                              descripcion: true,
                            },
                          },
                        },
                      },
                    },
                  },
                },
              },
            },
          },
        },
        skip,
        take,
        orderBy: { created_at: 'desc' },
      }),
      this.prisma.clientes.count({ where: clienteWhere }),
    ]);

    const clienteIds = items.map((c) => c.id);
    const gestionesActivas = clienteIds.length
      ? await this.prisma.cob_gestion_mora.findMany({
          where: {
            cliente_id: { in: clienteIds },
            estado_actual: { in: MORA_ESTADOS_ACTIVOS },
          },
          select: { cliente_id: true, estado_actual: true, fecha_ingreso: true },
          orderBy: { fecha_ingreso: 'desc' },
        })
      : [];
    const moraByCliente = new Map<string, CobMoraEstado>();
    for (const g of gestionesActivas) {
      if (!moraByCliente.has(g.cliente_id)) moraByCliente.set(g.cliente_id, g.estado_actual);
    }
    const itemsEnriched = items.map((c) => ({
      ...c,
      mora_estado_activo: moraByCliente.get(c.id) ?? null,
    }));

    return {
      items: itemsEnriched,
      total,
      page: currentPage,
      limit: take,
      lastPage: Math.max(1, Math.ceil(total / take) || 1),
    };
  }

  async findAllForExport(empresa_id: string, q?: string) {
    const personaWhere: any = { empresa_id };
    if (q && q.trim().length >= 2) {
      personaWhere.OR = [
        { razon_social: { contains: q.trim(), mode: 'insensitive' } },
        { ruc: { contains: q.trim(), mode: 'insensitive' } },
        { nro_documento: { contains: q.trim(), mode: 'insensitive' } },
      ];
    }
    return this.prisma.clientes.findMany({
      where: { deleted: false, personas: personaWhere },
      select: {
        id: true,
        cod_cliente: true,
        nombre_fantasia: true,
        tipo_cliente: true,
        tipo_operacion: true,
        zona: true,
        es_agente_retencion: true,
        active: true,
        lista_precios: { select: { nombre: true, codigo: true } },
        personas: {
          select: {
            razon_social: true,
            ruc: true,
            dv: true,
            nro_documento: true,
            email: true,
            telefono: true,
            celular: true,
            naturaleza_receptor: true,
          },
        },
        cliente_direcciones: {
          where: { es_principal: true },
          take: 1,
          select: {
            label: true,
            direccion: true,
            nro: true,
            referencia: true,
            departamentos: { select: { descripcion: true } },
            distritos: { select: { descripcion: true } },
            ciudades: { select: { descripcion: true } },
            vendedor: { select: { nombre: true, apellido: true } },
            supervisor: { select: { nombre: true, apellido: true } },
          },
        },
      },
      orderBy: { created_at: 'desc' },
    });
  }

  async findOne(id: string) {
    const cliente = await this.prisma.clientes.findUnique({
      where: { id },
      select: {
        id: true,
        nombre_fantasia: true,
        cod_cliente: true,
        tipo_cliente: true,
        zona: true,
        lista_precios_id: true,
        cobrador_id: true,
        es_agente_retencion: true,
        cobrador: {
          select: { id: true, nombre: true, apellido: true, tipo: true },
        },
        tipo_operacion: true,
        lista_precios: {
          select: {
            id: true,
            nombre: true,
            codigo: true,
          },
        },
        personas: {
          select: {
            id: true,
            razon_social: true,
            ruc: true,
            dv: true,
            naturaleza_receptor: true,
            tipo_contribuyente: true,
            tipo_documento_identidad: true,
            nro_documento: true,
            direccion: true,
            telefono: true,
            email: true,
            active: true,
            deleted: true,
            deleted_at: true,
            created_at: true,
            updated_at: true,
            usuario: true,
            pais_id: true,
            paises: {
              select: {
                id: true,
                codigo: true,
                descripcion: true,
              },
            },
            ciudades: {
              select: {
                id: true,
                codigo: true,
                descripcion: true,
                distritos: {
                  select: {
                    id: true,
                    codigo: true,
                    descripcion: true,
                    departamentos: {
                      select: {
                        id: true,
                        codigo: true,
                        descripcion: true,
                        paises: {
                          select: {
                            id: true,
                            codigo: true,
                            descripcion: true,
                          },
                        },
                      },
                    },
                  },
                },
              },
            },
          },
        },
      },
    });
    if (!cliente) throw new NotFoundException('Cliente no encontrado');
    return { message: 'Cliente encontrado exitosamente', data: cliente };
  }

  async update(id: string, updateClienteDto: UpdateClienteDto, empresa_id: string, user_id?: string) {
    try {
      const clienteFind = await this.prisma.clientes.findUnique({
        where: { id },
      });
      if (!clienteFind) throw new NotFoundException('Cliente no encontrado');
      if (clienteFind.deleted) throw new ConflictException('El cliente ya fue eliminado');

      // Validar duplicado de RUC o nro_documento (excluyendo el propio cliente)
      if (updateClienteDto.ruc || updateClienteDto.nro_documento) {
        const orDup: any[] = [];
        if (updateClienteDto.ruc) orDup.push({ ruc: updateClienteDto.ruc });
        if (updateClienteDto.nro_documento) orDup.push({ nro_documento: updateClienteDto.nro_documento });
        const dupCliente = await this.prisma.clientes.findFirst({
          where: {
            deleted: false,
            id: { not: id },
            personas: { empresa_id, deleted: false, OR: orDup },
          },
          include: { personas: { select: { razon_social: true, ruc: true, nro_documento: true } } },
        });
        if (dupCliente) {
          const campo = updateClienteDto.ruc ? `RUC ${updateClienteDto.ruc}` : `Documento ${updateClienteDto.nro_documento}`;
          throw new ConflictException(`Ya existe un cliente con ${campo}: ${dupCliente.personas?.razon_social}`);
        }
      }

      await this.personasService.update(clienteFind.persona_id, updateClienteDto, empresa_id);
      const cliente = await this.prisma.clientes.update({
        where: { id },
        data: {
          tipo_operacion_id: updateClienteDto.tipo_operacion_id,
          nombre_fantasia: updateClienteDto.nombre_fantasia,
          cod_cliente: updateClienteDto.cod_cliente,
          tipo_cliente: updateClienteDto.tipo_cliente,
          lista_precios_id: updateClienteDto.lista_precios_id || null,
          zona: updateClienteDto.zona,
          referencia_domicilio: updateClienteDto.referencia_domicilio,
          cobrador_id: updateClienteDto.cobrador_id !== undefined ? (updateClienteDto.cobrador_id || null) : undefined,
          es_agente_retencion: updateClienteDto.es_agente_retencion,
          // undefined = el formulario no mandó el campo, así que no se toca.
          limite_credito: updateClienteDto.limite_credito || null,
          credito_habilitado: updateClienteDto.credito_habilitado,
          moneda_credito: updateClienteDto.moneda_credito,
          dias_mora_maximo: updateClienteDto.dias_mora_maximo,
          bloqueado_credito: updateClienteDto.bloqueado_credito,
          updated_at: new Date(),
        },
      });
      this.logger.log(`Cliente actualizado: (${id})`, 'ClientesService');
      await this.auditService.log({
        empresa_id,
        user_id,
        action: 'UPDATE',
        entity_type: 'cliente',
        entity_id: id,
        descripcion: `Cliente actualizado | Código: ${cliente.cod_cliente ?? '-'}`,
        old_value: {
          cod_cliente: clienteFind.cod_cliente,
          nombre_fantasia: clienteFind.nombre_fantasia,
          tipo_cliente: clienteFind.tipo_cliente,
          active: clienteFind.active,
        },
        new_value: {
          cod_cliente: cliente.cod_cliente,
          nombre_fantasia: cliente.nombre_fantasia,
          tipo_cliente: cliente.tipo_cliente,
          active: cliente.active,
        },
      });
      return { message: 'Cliente actualizado exitosamente', data: cliente };
    } catch (error) {
      console.log('Error al actualizar cliente:', error);
      if (error instanceof NotFoundException) throw error;
      if (error instanceof ConflictException) throw error;
      const message = error instanceof Error ? error.message : 'Error desconocido';
      throw new Error(`Error al actualizar cliente: ${message}`);
    }
  }
  async activarDesactivar(id: string, empresa_id?: string, user_id?: string) {
    try {
      const clienteFind = await this.prisma.clientes.findUnique({
        where: { id },
      });
      if (!clienteFind) throw new NotFoundException('Cliente no encontrado');
      if (clienteFind.deleted) throw new ConflictException('El cliente ya fue eliminado');
      // No permitir cambiar estado del cliente de sistema (Sin nombre)
      if (clienteFind.cod_cliente === 'SN001') {
        throw new BadRequestException('No se puede cambiar el estado del cliente de sistema "Sin nombre"');
      }
      const cliente = await this.prisma.clientes.update({
        where: { id },
        data: { active: !clienteFind.active },
      });
      const action = cliente.active ? 'ACTIVATE' : 'DEACTIVATE';
      this.logger.log(`Cliente (${id}) ${cliente.active ? 'activado' : 'desactivado'}`, 'ClientesService');
      await this.auditService.log({
        empresa_id,
        user_id,
        action,
        entity_type: 'cliente',
        entity_id: id,
        descripcion: `Cliente ${cliente.active ? 'activado' : 'desactivado'} | Código: ${clienteFind.cod_cliente ?? '-'}`,
        old_value: { active: clienteFind.active },
        new_value: { active: cliente.active },
      });
      return { message: 'Cliente actualizado exitosamente', data: cliente };
    } catch (error) {
      console.log('Error al actualizar cliente:', error);
      if (error instanceof NotFoundException) throw error;
      if (error instanceof ConflictException) throw error;
      const message = error instanceof Error ? error.message : 'Error desconocido';
      throw new Error(`Error al actualizar cliente: ${message}`);
    }
  }
  async remove(id: string, empresa_id?: string, user_id?: string) {
    try {
      const clienteFind = await this.prisma.clientes.findUnique({
        where: { id },
      });
      if (!clienteFind) throw new NotFoundException('Cliente no encontrado');
      if (clienteFind.deleted) throw new ConflictException('El cliente ya fue eliminado');
      // No permitir eliminar el cliente de sistema (Sin nombre)
      if (clienteFind.cod_cliente === 'SN001') {
        throw new BadRequestException('No se puede eliminar el cliente de sistema "Sin nombre"');
      }
      const cliente = await this.prisma.clientes.update({
        where: { id },
        data: { deleted: true, deleted_at: new Date() },
      });
      if (!cliente) throw new NotFoundException('Cliente no encontrado');
      this.logger.log(`Cliente eliminado: (${id})`, 'ClientesService');
      await this.auditService.log({
        empresa_id,
        user_id,
        action: 'DELETE',
        entity_type: 'cliente',
        entity_id: id,
        descripcion: `Cliente eliminado | Código: ${clienteFind.cod_cliente ?? '-'}`,
        old_value: {
          cod_cliente: clienteFind.cod_cliente,
          nombre_fantasia: clienteFind.nombre_fantasia,
          active: clienteFind.active,
        },
      });
      return { message: 'Cliente eliminado exitosamente', data: cliente };
    } catch (error) {
      console.log('Error al eliminar cliente:', error);
      if (error instanceof NotFoundException) throw error;
      const message = error instanceof Error ? error.message : 'Error desconocido';
      throw new Error(`Error al eliminar cliente: ${message}`);
    }
  }

  // ==================== FASE 2: CONTROL DE CRÉDITO ====================

  /**
   * Única fuente de verdad de "¿puede comprar a crédito?": la consumen el POS
   * retail (vía getCreditInfo) y la facturación clásica.
   *
   * `credito_habilitado` responde si le vendemos a crédito; `limite_credito`
   * responde hasta cuánto, y vacío significa sin tope. Antes las dos preguntas
   * estaban metidas en el número: un límite en 0 quería decir "ilimitado" acá y
   * "no vende a crédito" en el POS.
   */
  async verificarCredito(
    cliente_id: string,
    monto: number,
  ): Promise<{ permitido: boolean; mensaje?: string; sin_tope?: boolean; saldo_disponible: number | null }> {
    const cliente = await this.prisma.clientes.findUnique({ where: { id: cliente_id } });
    if (!cliente) throw new NotFoundException('Cliente no encontrado');

    const limiteCredito = Number(cliente.limite_credito) || 0;
    const saldoPendiente = Number(cliente.saldo_pendiente) || 0;

    if (cliente.credito_habilitado === false) {
      return {
        permitido: false,
        mensaje: 'El cliente no tiene habilitada la venta a crédito',
        saldo_disponible: null,
      };
    }

    // Antes del tope: un cliente bloqueado a mano no factura a crédito ni
    // aunque no tenga límite cargado.
    if (cliente.bloqueado_credito) {
      return {
        permitido: false,
        mensaje: `Cliente bloqueado por crédito. Saldo pendiente: ${saldoPendiente}`,
        saldo_disponible: null,
      };
    }

    // Sin tope: el control queda en la mora y el bloqueo manual.
    if (limiteCredito <= 0) {
      return { permitido: true, sin_tope: true, saldo_disponible: null };
    }

    const saldoDisponible = limiteCredito - saldoPendiente;
    if (monto > saldoDisponible) {
      return {
        permitido: false,
        mensaje: `Monto ${monto} excede el crédito disponible de ${saldoDisponible} (Límite: ${limiteCredito}, Pendiente: ${saldoPendiente})`,
        sin_tope: false,
        saldo_disponible: saldoDisponible,
      };
    }

    return { permitido: true, sin_tope: false, saldo_disponible: saldoDisponible };
  }

  async actualizarSaldoPendiente(
    cliente_id: string,
    monto: number,
    operacion: 'sumar' | 'restar',
    empresa_id?: string,
    user_id?: string,
  ) {
    const cliente = await this.prisma.clientes.findUnique({ where: { id: cliente_id } });
    if (!cliente) throw new NotFoundException('Cliente no encontrado');

    const saldoActual = Number(cliente.saldo_pendiente) || 0;
    const nuevoSaldo = operacion === 'sumar' ? saldoActual + monto : Math.max(0, saldoActual - monto);

    const dataUpdate: Record<string, any> = {
      saldo_pendiente: nuevoSaldo,
      updated_at: new Date(),
    };

    // Registrar fecha de último pago si es una resta (pago recibido)
    if (operacion === 'restar') {
      dataUpdate.fecha_ultimo_pago = new Date();
    }

    // Auto-bloquear si excede límite
    const limiteCredito = Number(cliente.limite_credito) || 0;
    if (limiteCredito > 0 && nuevoSaldo > limiteCredito) {
      dataUpdate.bloqueado_credito = true;
    }
    // Auto-desbloquear si vuelve a estar dentro del límite y estaba bloqueado
    if (limiteCredito > 0 && nuevoSaldo <= limiteCredito && cliente.bloqueado_credito) {
      dataUpdate.bloqueado_credito = false;
    }

    const updated = await this.prisma.clientes.update({
      where: { id: cliente_id },
      data: dataUpdate,
    });

    this.logger.log(
      `Saldo crédito actualizado: cliente ${cliente_id} | ${operacion} ${monto} | Nuevo saldo: ${nuevoSaldo}`,
      'ClientesService',
    );

    await this.auditService.log({
      empresa_id,
      user_id,
      action: 'UPDATE',
      entity_type: 'cliente_credito',
      entity_id: cliente_id,
      descripcion: `Saldo crédito ${operacion === 'sumar' ? 'incrementado' : 'reducido'} en ${monto} | Nuevo saldo: ${nuevoSaldo}`,
      old_value: { saldo_pendiente: saldoActual, bloqueado_credito: cliente.bloqueado_credito },
      new_value: {
        saldo_pendiente: nuevoSaldo,
        bloqueado_credito: dataUpdate.bloqueado_credito ?? cliente.bloqueado_credito,
      },
    });

    return updated;
  }

  async getCreditInfo(cliente_id: string) {
    const cliente = await this.prisma.clientes.findUnique({
      where: { id: cliente_id },
      select: {
        id: true,
        limite_credito: true,
        credito_habilitado: true,
        saldo_pendiente: true,
        moneda_credito: true,
        bloqueado_credito: true,
        fecha_ultimo_pago: true,
        dias_mora_maximo: true,
      },
    });
    if (!cliente) throw new NotFoundException('Cliente no encontrado');

    const limite = Number(cliente.limite_credito) || 0;
    const saldo = Number(cliente.saldo_pendiente) || 0;

    return {
      ...cliente,
      // El front no vuelve a inferir la regla de un número: recibe el flag y
      // el "sin tope" ya resueltos.
      limite_credito: limite > 0 ? limite : null,
      sin_tope: limite <= 0,
      saldo_pendiente: saldo,
      saldo_disponible: limite > 0 ? Math.max(0, limite - saldo) : null,
      porcentaje_uso: limite > 0 ? Math.round((saldo / limite) * 100) : null,
    };
  }

  async updateCreditConfig(
    cliente_id: string,
    data: {
      limite_credito?: number;
      credito_habilitado?: boolean;
      moneda_credito?: string;
      dias_mora_maximo?: number;
      bloqueado_credito?: boolean;
    },
    empresa_id?: string,
    user_id?: string,
  ) {
    const cliente = await this.prisma.clientes.findUnique({ where: { id: cliente_id } });
    if (!cliente) throw new NotFoundException('Cliente no encontrado');

    const updateData: Record<string, any> = { updated_at: new Date() };
    if (data.limite_credito !== undefined) updateData.limite_credito = data.limite_credito || null;
    if (data.credito_habilitado !== undefined) updateData.credito_habilitado = data.credito_habilitado;
    if (data.moneda_credito !== undefined) updateData.moneda_credito = data.moneda_credito;
    if (data.dias_mora_maximo !== undefined) updateData.dias_mora_maximo = data.dias_mora_maximo;
    if (data.bloqueado_credito !== undefined) updateData.bloqueado_credito = data.bloqueado_credito;

    const updated = await this.prisma.clientes.update({
      where: { id: cliente_id },
      data: updateData,
    });

    this.logger.log(`Config crédito actualizada: cliente ${cliente_id}`, 'ClientesService');
    await this.auditService.log({
      empresa_id,
      user_id,
      action: 'UPDATE',
      entity_type: 'cliente_credito_config',
      entity_id: cliente_id,
      descripcion: `Configuración de crédito actualizada`,
      old_value: {
        limite_credito: Number(cliente.limite_credito),
        credito_habilitado: cliente.credito_habilitado,
        moneda_credito: cliente.moneda_credito,
        dias_mora_maximo: cliente.dias_mora_maximo,
        bloqueado_credito: cliente.bloqueado_credito,
      },
      new_value: data,
    });

    return { message: 'Configuración de crédito actualizada', data: updated };
  }

  // ── Direcciones adicionales ──────────────────────────────────────

  /**
   * Valida el payload de una dirección de cliente antes de crear/actualizar.
   * Reglas:
   *  - `pais_id` es obligatorio (default: Paraguay).
   *  - `direccion` (texto libre) es opcional.
   *  - Departamento / distrito / ciudad **solo aplican para Paraguay**.
   *    En Paraguay van "de la mano": o los tres seteados o los tres nulos.
   *    Si el usuario setea alguno, el sistema exige el resto (en orden depto → distrito → ciudad).
   *  - Consistencia de la cadena: distrito debe pertenecer al departamento, ciudad al distrito.
   */
  private async validarDireccionPayload(data: any): Promise<void> {
    if (!data.pais_id) {
      throw new BadRequestException('El país es obligatorio.');
    }
    const pais = await this.prisma.paises.findUnique({
      where: { id: data.pais_id },
      select: { id: true, codigo: true, descripcion: true },
    });
    if (!pais) throw new BadRequestException('El país seleccionado no existe.');
    const esParaguay =
      pais.codigo === 'PRY' || (pais.descripcion || '').toLowerCase() === 'paraguay';
    if (!esParaguay) return; // País extranjero: la ubicación local no aplica.

    const alguno =
      !!data.departamento_id || !!data.distrito_id || !!data.ciudad_id;
    if (!alguno) return; // Todos nulos: válido (dirección textual libre alcanza).

    // Al menos uno seteado → los tres son obligatorios y deben ir en orden.
    if (!data.departamento_id) {
      throw new BadRequestException(
        'Falta el departamento. Departamento / distrito / ciudad van juntos: elegí primero el departamento.',
      );
    }
    if (!data.distrito_id) {
      throw new BadRequestException(
        'Falta el distrito. Después del departamento hay que elegir el distrito.',
      );
    }
    if (!data.ciudad_id) {
      throw new BadRequestException(
        'Falta la ciudad. Después del distrito hay que elegir la ciudad.',
      );
    }

    // Consistencia de la cadena depto → distrito → ciudad.
    const distrito = await this.prisma.distritos.findUnique({
      where: { id: data.distrito_id },
      select: { id: true, departamento_id: true },
    });
    if (!distrito || distrito.departamento_id !== data.departamento_id) {
      throw new BadRequestException('El distrito no pertenece al departamento seleccionado.');
    }
    const ciudad = await this.prisma.ciudades.findUnique({
      where: { id: data.ciudad_id },
      select: { id: true, distrito_id: true },
    });
    if (!ciudad || ciudad.distrito_id !== data.distrito_id) {
      throw new BadRequestException('La ciudad no pertenece al distrito seleccionado.');
    }

    // Con la ubicación completa (depto + distrito + ciudad), la dirección textual
    // pasa a ser obligatoria — no tiene sentido registrar la ciudad sin la calle.
    if (!data.direccion || String(data.direccion).trim() === '') {
      throw new BadRequestException(
        'Con departamento, distrito y ciudad cargados, la dirección textual (calle / número) es obligatoria.',
      );
    }
  }

  async getDirecciones(clienteId: string, empresaId: string) {
    return this.prisma.cliente_direcciones.findMany({
      where: { cliente_id: clienteId, empresa_id: empresaId },
      include: {
        paises: { select: { id: true, descripcion: true } },
        departamentos: { select: { id: true, descripcion: true } },
        distritos: { select: { id: true, descripcion: true } },
        ciudades: { select: { id: true, descripcion: true } },
        vendedor: { select: { id: true, nombre: true, apellido: true, tipo: true } },
        cobrador: { select: { id: true, nombre: true, apellido: true, tipo: true } },
        supervisor: { select: { id: true, nombre: true, apellido: true } },
      },
      orderBy: [{ es_principal: 'desc' }, { created_at: 'asc' }],
    });
  }

  async createDireccion(clienteId: string, data: any, empresaId: string) {
    await this.validarDireccionPayload(data);
    if (data.es_principal) {
      await this.prisma.cliente_direcciones.updateMany({
        where: { cliente_id: clienteId, empresa_id: empresaId },
        data: { es_principal: false },
      });
    }
    return this.prisma.cliente_direcciones.create({
      data: {
        cliente_id: clienteId,
        empresa_id: empresaId,
        label: data.label || null,
        direccion: data.direccion || null,
        nro: data.nro || null,
        referencia: data.referencia || null,
        pais_id: data.pais_id || null,
        departamento_id: data.departamento_id || null,
        distrito_id: data.distrito_id || null,
        ciudad_id: data.ciudad_id || null,
        es_principal: data.es_principal ?? false,
        vendedor_id: data.vendedor_id || null,
        cobrador_id: data.cobrador_id || null,
        supervisor_id: data.supervisor_id || null,
      },
    });
  }

  async updateDireccion(clienteId: string, dirId: string, data: any, empresaId: string) {
    await this.validarDireccionPayload(data);
    if (data.es_principal) {
      await this.prisma.cliente_direcciones.updateMany({
        where: { cliente_id: clienteId, empresa_id: empresaId, NOT: { id: dirId } },
        data: { es_principal: false },
      });
    }
    return this.prisma.cliente_direcciones.update({
      where: { id: dirId },
      data: {
        label: data.label,
        direccion: data.direccion,
        nro: data.nro,
        referencia: data.referencia,
        pais_id: data.pais_id || null,
        departamento_id: data.departamento_id || null,
        distrito_id: data.distrito_id || null,
        ciudad_id: data.ciudad_id || null,
        es_principal: data.es_principal,
        vendedor_id: data.vendedor_id !== undefined ? (data.vendedor_id || null) : undefined,
        cobrador_id: data.cobrador_id !== undefined ? (data.cobrador_id || null) : undefined,
        supervisor_id: data.supervisor_id !== undefined ? (data.supervisor_id || null) : undefined,
        updated_at: new Date(),
      },
    });
  }

  async deleteDireccion(clienteId: string, dirId: string, empresaId: string) {
    await this.prisma.cliente_direcciones.delete({
      where: { id: dirId, cliente_id: clienteId, empresa_id: empresaId },
    });
    return { message: 'Dirección eliminada' };
  }
}
