import { Injectable, Logger } from '@nestjs/common';
import { Prisma } from '@prisma/client';
import { PrismaService } from '../../prisma/prisma.service';

/**
 * Vincula a cada funcionario con su ficha de cliente.
 *
 * Para qué: lo que el funcionario compra en la empresa se le factura como a
 * cualquier cliente (con cobro diferido) y después se le descuenta del sueldo.
 * Sin este vínculo la liquidación no sabe qué facturas proponerle, y la deuda
 * del funcionario queda abierta para siempre aunque ya se le haya descontado.
 *
 * Por qué no alcanza con buscar por cédula a secas: en la base conviven dos
 * formatos y dos campos distintos.
 *   - rrhh_empleados.cedula_identidad viene CON puntos ("5.616.813")
 *   - personas la guarda SIN puntos, y según sea contribuyente o no, la deja
 *     en `ruc` (Adelaida) o en `nro_documento` (Blanca, Ever, Mónica...).
 * Buscando literal, de 15 empleados de DOBA matcheaba 1. Normalizando y
 * mirando los dos campos, matchean 9. Si no se normaliza, el alta automática
 * duplica la ficha de esos 9 y la deuda queda partida entre dos clientes.
 */
@Injectable()
export class EmpleadoClienteService {
  private readonly logger = new Logger(EmpleadoClienteService.name);

  constructor(private readonly prisma: PrismaService) {}

  /** Cédulas obviamente de prueba: no se les busca ni crea ficha de cliente. */
  static readonly CEDULAS_DE_PRUEBA = new Set(['123456', '654321', '1234567', '0', '11111111']);

  /** Deja solo dígitos: "5.616.813" y "5616813" tienen que ser la misma cédula. */
  static normalizarDocumento(valor: string | null | undefined): string {
    return (valor ?? '').replace(/\D/g, '');
  }

  /**
   * Devuelve el `cliente_id` que le corresponde al funcionario: el de su ficha
   * existente si ya es cliente, o el de una ficha nueva.
   *
   * Idempotente: si el empleado ya tiene `cliente_id`, lo devuelve sin tocar nada.
   * Nunca lanza: si algo falla, loguea y devuelve null — no queremos que un
   * problema al crear la ficha de cliente impida dar de alta a un empleado.
   *
   * @param crearSiNoExiste  Si es false y el funcionario no tiene ficha previa,
   *   no crea ninguna. Lo usa el alta de empleado, que respeta el flag
   *   `consumo_funcionario_crear_cliente` de la empresa. El backfill y la
   *   vinculación manual lo llaman en true.
   */
  async resolverParaEmpleado(
    empleadoId: string,
    empresaId: string,
    crearSiNoExiste = true,
    tx?: Prisma.TransactionClient,
  ): Promise<string | null> {
    const db = tx ?? this.prisma;

    try {
      const empleado = await db.rrhh_empleados.findFirst({
        where: { id: empleadoId, empresa_id: empresaId },
        select: {
          id: true,
          cliente_id: true,
          cedula_identidad: true,
          ruc: true,
          nombres: true,
          apellidos: true,
        },
      });
      if (!empleado) return null;
      if (empleado.cliente_id) return empleado.cliente_id;

      const ci = EmpleadoClienteService.normalizarDocumento(empleado.cedula_identidad);
      if (!ci) {
        this.logger.warn(`Empleado ${empleadoId} sin cédula utilizable: no se vincula cliente`);
        return null;
      }

      const existente = await this.buscarClientePorDocumento(ci, empresaId, db);
      if (!existente && !crearSiNoExiste) return null;

      const clienteId = existente ?? (await this.crearCliente(empleado, ci, empresaId, db));
      if (!clienteId) return null;

      await db.rrhh_empleados.update({
        where: { id: empleadoId },
        data: { cliente_id: clienteId },
      });

      this.logger.log(
        `Empleado ${empleadoId} ${existente ? 'vinculado a cliente existente' : 'con ficha de cliente nueva'} ${clienteId}`,
      );
      return clienteId;
    } catch (e) {
      this.logger.error(`No se pudo resolver el cliente del empleado ${empleadoId}: ${e.message}`);
      return null;
    }
  }

  /**
   * Busca una ficha de cliente de la empresa cuyo documento coincida con la
   * cédula normalizada, mirando tanto `ruc` como `nro_documento`.
   *
   * Se compara normalizado de los dos lados (en SQL, con regexp_replace) porque
   * en `personas` también hay documentos cargados con puntos o guiones.
   */
  private async buscarClientePorDocumento(
    ciNormalizada: string,
    empresaId: string,
    db: Prisma.TransactionClient | PrismaService,
  ): Promise<string | null> {
    const filas = await db.$queryRaw<{ id: string }[]>`
      SELECT c.id
      FROM   clientes c
      JOIN   personas p ON p.id = c.persona_id
      WHERE  p.empresa_id = ${empresaId}::uuid
        AND  c.deleted = false
        AND  COALESCE(p.deleted, false) = false
        AND  (
               regexp_replace(COALESCE(p.ruc, ''), '\\D', '', 'g') = ${ciNormalizada}
            OR regexp_replace(COALESCE(p.nro_documento, ''), '\\D', '', 'g') = ${ciNormalizada}
             )
      ORDER  BY c.created_at ASC
      LIMIT  1
    `;
    return filas[0]?.id ?? null;
  }

  /** Crea persona + cliente mínimos para el funcionario. */
  private async crearCliente(
    empleado: { nombres: string; apellidos: string; ruc: string | null },
    ciNormalizada: string,
    empresaId: string,
    db: Prisma.TransactionClient | PrismaService,
  ): Promise<string | null> {
    // B2C: el funcionario compra como consumidor final, no como empresa.
    const tipoOperacion = await db.tipo_operacion.findFirst({
      where: { codigo: 2 },
      select: { id: true },
    });
    if (!tipoOperacion) {
      this.logger.error('No existe el tipo de operación B2C (código 2): no se crea la ficha de cliente');
      return null;
    }

    const razonSocial = `${empleado.nombres} ${empleado.apellidos}`.replace(/\s+/g, ' ').trim();

    const persona = await db.personas.create({
      data: {
        empresa_id: empresaId,
        razon_social: razonSocial,
        // La cédula va a `nro_documento`, que es donde la tienen las fichas de
        // los funcionarios ya cargados. `ruc` solo si el empleado declara uno.
        nro_documento: ciNormalizada,
        ruc: EmpleadoClienteService.normalizarDocumento(empleado.ruc) || null,
      },
      select: { id: true },
    });

    const cliente = await db.clientes.create({
      data: { persona_id: persona.id, tipo_operacion_id: tipoOperacion.id },
      select: { id: true },
    });

    return cliente.id;
  }
}
