import { Injectable, Logger, NotFoundException } from '@nestjs/common';
import { AuditService } from '../../audit/audit.service';
import { PrismaService } from '../../prisma/prisma.service';
import { UpdateConsumoFuncionarioConfigDto } from '../dto/consumo-funcionario.dto';
import { CONCEPTOS_SISTEMA } from '../seeds/rrhh-defaults';
import { EmpleadoClienteService } from './empleado-cliente.service';

const CODIGO_CONCEPTO = 'CONSUMO_FUNCIONARIO';
const CONCEPTO_CUENTA_PUENTE = 'CONSUMO_FUNCIONARIO_PUENTE';

/**
 * Configuración del consumo de funcionarios (RRHH → Configuración).
 *
 * Además de los tres valores de `config_rrhh`, devuelve los requisitos para que
 * la función ande de punta a punta. Sin ellos el switch queda prendido pero no
 * pasa nada, y en silencio:
 *   - sin el concepto CONSUMO_FUNCIONARIO, el motor no propone descuentos;
 *   - sin la cuenta puente mapeada, el recibo del cierre no se contabiliza;
 *   - un funcionario sin ficha de cliente no tiene facturas que proponerle.
 */
@Injectable()
export class ConsumoFuncionarioConfigService {
  private readonly logger = new Logger(ConsumoFuncionarioConfigService.name);

  constructor(
    private readonly prisma: PrismaService,
    private readonly audit: AuditService,
    private readonly empleadoCliente: EmpleadoClienteService,
  ) {}

  async obtener(empresaId: string) {
    const config = await this.prisma.config_rrhh.findUnique({
      where: { empresa_id: empresaId },
      select: {
        consumo_funcionario_habilitado: true,
        consumo_funcionario_crear_cliente: true,
        consumo_funcionario_tope_mensual: true,
      },
    });

    return {
      habilitado: config?.consumo_funcionario_habilitado ?? false,
      crear_cliente: config?.consumo_funcionario_crear_cliente ?? false,
      tope_mensual: Number(config?.consumo_funcionario_tope_mensual ?? 0),
      requisitos: await this.requisitos(empresaId),
    };
  }

  async actualizar(empresaId: string, dto: UpdateConsumoFuncionarioConfigDto, userId?: string) {
    const data = {
      ...(dto.habilitado !== undefined && { consumo_funcionario_habilitado: dto.habilitado }),
      ...(dto.crear_cliente !== undefined && { consumo_funcionario_crear_cliente: dto.crear_cliente }),
      ...(dto.tope_mensual !== undefined && { consumo_funcionario_tope_mensual: dto.tope_mensual }),
    };

    const config = await this.prisma.config_rrhh.upsert({
      where: { empresa_id: empresaId },
      create: { empresa_id: empresaId, ...data },
      update: { ...data, updated_at: new Date() },
      select: { id: true },
    });

    // Al activarlo se crea el concepto si falta: las empresas que ya existían
    // sembraron sus conceptos antes de que éste existiera, y sin él el motor
    // ignora el consumo sin avisar.
    if (dto.habilitado) await this.asegurarConcepto(empresaId);

    await this.audit.log({
      empresa_id: empresaId,
      user_id: userId,
      action: 'UPDATE',
      entity_type: 'config_rrhh',
      entity_id: config.id,
      descripcion: 'Configuración de consumo de funcionarios actualizada',
      new_value: { ...dto },
    });

    return this.obtener(empresaId);
  }

  /**
   * Vincula a los funcionarios activos sin ficha de cliente: con la ficha que
   * ya tengan (por cédula) y, si `crear`, dándoles una nueva. Es lo mismo que
   * el script de backfill, para que cualquier empresa lo haga sin soporte.
   */
  async vincularFichas(empresaId: string, crear: boolean, userId?: string) {
    const pendientes = await this.prisma.rrhh_empleados.findMany({
      where: { empresa_id: empresaId, estado: { not: 'EGRESADO' }, cliente_id: null },
      select: { id: true, cedula_identidad: true },
    });

    let vinculados = 0;
    let salteados = 0;
    for (const e of pendientes) {
      const ci = EmpleadoClienteService.normalizarDocumento(e.cedula_identidad);
      if (!ci || EmpleadoClienteService.CEDULAS_DE_PRUEBA.has(ci)) {
        salteados++;
        continue;
      }
      const clienteId = await this.empleadoCliente.resolverParaEmpleado(e.id, empresaId, crear);
      if (clienteId) vinculados++;
      else salteados++;
    }

    if (vinculados > 0) {
      await this.audit.log({
        empresa_id: empresaId,
        user_id: userId,
        action: 'UPDATE',
        entity_type: 'rrhh_empleados',
        entity_id: empresaId,
        descripcion: `Consumo de funcionarios: ${vinculados} funcionarios vinculados con su ficha de cliente`,
        new_value: { crear, vinculados, salteados },
      });
    }

    return { vinculados, salteados, requisitos: await this.requisitos(empresaId) };
  }

  /**
   * Facturas de funcionarios marcadas para no descontar por planilla que
   * todavía tienen saldo. Es la lista desde donde se deshace la marca.
   */
  async facturasExcluidas(empresaId: string) {
    const empleados = await this.prisma.rrhh_empleados.findMany({
      where: { empresa_id: empresaId, cliente_id: { not: null } },
      select: { id: true, nombres: true, apellidos: true, cliente_id: true },
    });
    const porCliente = new Map(empleados.map((e) => [e.cliente_id, e]));
    if (!porCliente.size) return [];

    const cuentas = await this.prisma.cuentas_cobrar.findMany({
      where: {
        empresa_id: empresaId,
        cliente_id: { in: [...porCliente.keys()] },
        excluir_descuento_planilla: true,
        saldo_pendiente: { gt: 0 },
      },
      select: {
        id: true,
        cliente_id: true,
        fecha_emision: true,
        monto_total: true,
        saldo_pendiente: true,
        factura_cab: { select: { dest: true, dpunexp: true, dnumdoc: true } },
      },
      orderBy: { fecha_emision: 'asc' },
    });

    return cuentas.map((c) => {
      const e = porCliente.get(c.cliente_id);
      const f = c.factura_cab;
      return {
        cuenta_cobrar_id: c.id,
        empleado_id: e?.id ?? null,
        empleado: e ? `${e.nombres} ${e.apellidos}`.replace(/\s+/g, ' ').trim() : null,
        numero: f ? `${f.dest}-${f.dpunexp}-${f.dnumdoc}` : null,
        fecha_emision: c.fecha_emision,
        monto_total: Number(c.monto_total ?? 0),
        saldo_pendiente: Number(c.saldo_pendiente ?? 0),
      };
    });
  }

  /**
   * Marca o desmarca una factura de funcionario para no descontarla por
   * planilla. Solo facturas de clientes vinculados a un funcionario: no es una
   * puerta para tocar cuentas por cobrar de cualquier cliente.
   */
  async cambiarExclusion(empresaId: string, cuentaCobrarId: string, excluir: boolean, userId?: string) {
    const cuenta = await this.prisma.cuentas_cobrar.findFirst({
      where: { id: cuentaCobrarId, empresa_id: empresaId },
      select: { id: true, cliente_id: true, factura_cab: { select: { dnumdoc: true } } },
    });
    const esDeFuncionario =
      cuenta?.cliente_id &&
      (await this.prisma.rrhh_empleados.count({
        where: { empresa_id: empresaId, cliente_id: cuenta.cliente_id },
      })) > 0;
    if (!cuenta || !esDeFuncionario) throw new NotFoundException('Factura de funcionario no encontrada');

    await this.prisma.cuentas_cobrar.update({
      where: { id: cuenta.id },
      data: { excluir_descuento_planilla: excluir, updated_at: new Date() },
    });

    await this.audit.log({
      empresa_id: empresaId,
      user_id: userId,
      action: 'UPDATE',
      entity_type: 'cuentas_cobrar',
      entity_id: cuenta.id,
      descripcion: excluir
        ? `Factura ${cuenta.factura_cab?.dnumdoc ?? ''} excluida del descuento por planilla`
        : `Factura ${cuenta.factura_cab?.dnumdoc ?? ''} vuelve a proponerse para descuento por planilla`,
      new_value: { excluir_descuento_planilla: excluir },
    });

    return { cuenta_cobrar_id: cuenta.id, excluir_descuento_planilla: excluir };
  }

  private async requisitos(empresaId: string) {
    const [concepto, mapeo, activos, sinFicha] = await Promise.all([
      this.prisma.rrhh_conceptos_liquidacion.findFirst({
        where: { empresa_id: empresaId, codigo: CODIGO_CONCEPTO },
        select: { id: true, activo: true },
      }),
      this.prisma.cont_mapeo_cuentas.findUnique({
        where: { empresa_id_concepto: { empresa_id: empresaId, concepto: CONCEPTO_CUENTA_PUENTE } },
        select: { cuenta: { select: { codigo: true, descripcion: true, active: true, acepta_movimientos: true } } },
      }),
      this.prisma.rrhh_empleados.count({
        where: { empresa_id: empresaId, estado: { not: 'EGRESADO' } },
      }),
      this.prisma.rrhh_empleados.count({
        where: { empresa_id: empresaId, estado: { not: 'EGRESADO' }, cliente_id: null },
      }),
    ]);

    // Mismo criterio que MapeoCuentasService.getCuentaPorConcepto: una cuenta
    // dada de baja o de agrupación hace fallar el asiento igual que si no
    // estuviera mapeada.
    const cuenta = mapeo?.cuenta ?? null;
    const cuentaOk = !!cuenta && cuenta.active && cuenta.acepta_movimientos;
    let cuentaProblema: string | null = null;
    if (!cuenta) cuentaProblema = 'SIN_MAPEO';
    else if (!cuenta.active) cuentaProblema = 'CUENTA_INACTIVA';
    else if (!cuenta.acepta_movimientos) cuentaProblema = 'CUENTA_NO_IMPUTABLE';

    return {
      concepto: { existe: !!concepto, activo: concepto?.activo ?? false },
      cuenta_puente: {
        ok: cuentaOk,
        problema: cuentaProblema,
        codigo: cuenta?.codigo ?? null,
        descripcion: cuenta?.descripcion ?? null,
      },
      empleados_activos: activos,
      empleados_sin_ficha: sinFicha,
    };
  }

  private async asegurarConcepto(empresaId: string) {
    const existe = await this.prisma.rrhh_conceptos_liquidacion.findFirst({
      where: { empresa_id: empresaId, codigo: CODIGO_CONCEPTO },
      select: { id: true },
    });
    if (existe) return;

    const def = CONCEPTOS_SISTEMA.find((c) => c.codigo === CODIGO_CONCEPTO);
    if (!def) return;

    await this.prisma.rrhh_conceptos_liquidacion.create({
      data: {
        empresa_id: empresaId,
        codigo: def.codigo,
        nombre: def.nombre,
        tipo: def.tipo,
        categoria: def.categoria,
        afecta_base_ips: def.afecta_base_ips,
        afecta_base_aguinaldo: def.afecta_base_aguinaldo,
        es_porcentaje: def.es_porcentaje,
        porcentaje: def.porcentaje ?? null,
        es_fijo: def.es_fijo,
        es_del_sistema: true,
        aplica_a: def.aplica_a,
        orden_visualizacion: def.orden_visualizacion,
        activo: true,
      },
    });
    this.logger.log(`Concepto ${CODIGO_CONCEPTO} creado para la empresa ${empresaId}`);
  }
}
