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

/**
 * FASE 1 — Puente Rendición de Viáticos → Tesorería.
 * Genera tes_movimientos EGRESO al pagar un adelanto (Fase 1).
 * Devoluciones y reintegros se manejan en Fase 3.
 */
@Injectable()
export class RendicionViaticosTesoreriaService {
  private readonly logger = new Logger(RendicionViaticosTesoreriaService.name);

  constructor(private readonly prisma: PrismaService) {}

  /**
   * Al pagar un adelanto, crea `tes_movimientos` EGRESO en la cuenta origen.
   * Reduce el saldo actual de la cuenta.
   */
  async registrarAdelanto(adelantoId: string, empresaId: string, userId?: string) {
    const adelanto = await this.prisma.rendicion_adelanto.findFirst({
      where: { id: adelantoId, empresa_id: empresaId, anulado: false },
      include: {
        rendicion: {
          select: {
            numero_rendicion: true,
            moneda: { select: { codigo: true } },
            empleado: { select: { nombres: true, apellidos: true } },
          },
        },
        medio_pago: { select: { codigo: true, descripcion: true } },
      },
    });
    if (!adelanto) throw new BadRequestException('Adelanto no encontrado');

    // Idempotencia contra la tabla real (no contra el campo mutable
    // tes_movimiento_id, que recién se persiste si TODO el flujo — tesorería +
    // contabilidad — termina bien; si algo falla después, un reintento no debe
    // duplicar el movimiento).
    const movimientoExistente = await this.prisma.tes_movimientos.findFirst({
      where: {
        empresa_id: empresaId,
        origen_tipo: 'RENDICION_ADELANTO',
        origen_id: adelantoId,
        estado: { not: 'ANULADO' },
      },
      select: { id: true },
    });
    if (movimientoExistente) {
      this.logger.warn(`Adelanto ${adelantoId} ya tiene movimiento de tesorería`);
      return null;
    }

    const tesCuenta = await this.prisma.tes_cuentas.findFirst({
      where: { id: adelanto.tes_cuenta_id ?? undefined, empresa_id: empresaId, activo: true },
      select: { id: true },
    });
    if (!tesCuenta) {
      this.logger.warn(`Adelanto ${adelantoId} sin cuenta de tesorería válida`);
      return null;
    }

    const monto = Number(adelanto.monto);
    if (monto <= 0) return null;

    const empleadoNombre = adelanto.rendicion?.empleado
      ? `${adelanto.rendicion.empleado.nombres} ${adelanto.rendicion.empleado.apellidos}`
      : 'empleado';

    // Detección de cheque por código/descripción del medio de pago.
    const codigoMedio = Number(adelanto.medio_pago?.codigo ?? 0);
    const descMedio = (adelanto.medio_pago?.descripcion ?? '')
      .toLowerCase()
      .normalize('NFD')
      .replace(/[\u0300-\u036f]/g, '');
    const esCheque = codigoMedio === 2 || descMedio.includes('cheque');

    const mov = await this.prisma.$transaction(async (tx) => {
      const created = await tx.tes_movimientos.create({
        data: {
          empresa_id: empresaId,
          cuenta_id: tesCuenta.id,
          tipo: 'EGRESO',
          estado: 'CONFIRMADO',
          fecha: adelanto.fecha,
          monto,
          descripcion: `Adelanto viáticos ${adelanto.rendicion?.numero_rendicion ?? ''} - ${empleadoNombre}`,
          referencia: adelanto.numero_operacion ?? adelanto.numero_cheque ?? null,
          origen_tipo: 'RENDICION_ADELANTO',
          origen_id: adelantoId,
          created_by: userId ?? null,
        },
      });
      // Reducir saldo actual.
      await tx.tes_cuentas.update({
        where: { id: tesCuenta.id },
        data: { saldo_actual: { decrement: monto } },
      });

      // Si es cheque, registrar el instrumento en tes_cheques.
      if (esCheque && adelanto.numero_cheque) {
        const inicioHoy = new Date();
        inicioHoy.setHours(0, 0, 0, 0);
        const esDiferido =
          adelanto.fecha_vencimiento_cheque &&
          new Date(adelanto.fecha_vencimiento_cheque) > inicioHoy;
        const cheque = await tx.tes_cheques.create({
          data: {
            empresa_id: empresaId,
            tipo: 'EMITIDO',
            estado: esDiferido ? 'DIFERIDO' : 'EN_CARTERA',
            numero_cheque: adelanto.numero_cheque,
            banco_emisor: adelanto.banco_emisor ?? null,
            titular: adelanto.titular_cheque ?? empleadoNombre,
            monto,
            moneda: adelanto.rendicion?.moneda?.codigo ?? 'PYG',
            fecha_emision: adelanto.fecha_emision_cheque ?? adelanto.fecha,
            fecha_vencimiento: adelanto.fecha_vencimiento_cheque ?? null,
            cuenta_id: tesCuenta.id,
            movimiento_id: created.id,
            origen_tipo: 'RENDICION_ADELANTO',
            origen_id: adelantoId,
            referencia: `Adelanto viáticos ${adelanto.rendicion?.numero_rendicion ?? ''}`,
            registrado_por: userId ?? null,
          },
        });
        await tx.rendicion_adelanto.update({
          where: { id: adelantoId },
          data: { tes_cheque_id: cheque.id },
        });
      }
      return created;
    });

    this.logger.log(`Adelanto ${adelantoId} registrado en tesorería (mov ${mov.id})`);
    return mov;
  }

  /**
   * FASE 3 — Movimiento de tesorería del cierre.
   *
   * DEVOLUCION → INGRESO en la cuenta destino.
   * REINTEGRO INMEDIATO → EGRESO en la cuenta origen.
   * REINTEGRO DIFERIDO → no genera movimiento (queda como CxP).
   */
  async registrarDevolucion(devolucionId: string, empresaId: string, userId?: string) {
    const dev = await this.prisma.rendicion_devolucion.findFirst({
      where: { id: devolucionId, empresa_id: empresaId, anulado: false },
      include: {
        rendicion: {
          select: {
            numero_rendicion: true,
            moneda: { select: { codigo: true } },
            empleado: { select: { nombres: true, apellidos: true } },
          },
        },
        medio_pago: { select: { codigo: true, descripcion: true } },
      },
    });
    if (!dev) throw new BadRequestException('Devolución no encontrada');
    if (dev.diferido) return null; // diferido no toca tesorería

    // Idempotencia contra la tabla real (ver comentario en registrarAdelanto).
    const movimientoExistente = await this.prisma.tes_movimientos.findFirst({
      where: {
        empresa_id: empresaId,
        origen_tipo: 'RENDICION_DEVOLUCION',
        origen_id: devolucionId,
        estado: { not: 'ANULADO' },
      },
      select: { id: true },
    });
    if (movimientoExistente) {
      this.logger.warn(`Devolución ${devolucionId} ya tiene movimiento de tesorería`);
      return null;
    }

    const tesCuenta = await this.prisma.tes_cuentas.findFirst({
      where: { id: dev.tes_cuenta_id ?? undefined, empresa_id: empresaId, activo: true },
      select: { id: true },
    });
    if (!tesCuenta) {
      this.logger.warn(`Devolución ${devolucionId} sin cuenta de tesorería válida`);
      return null;
    }

    const monto = Number(dev.monto);
    if (monto <= 0) return null;

    const empleadoNombre = dev.rendicion?.empleado
      ? `${dev.rendicion.empleado.nombres} ${dev.rendicion.empleado.apellidos}`
      : 'empleado';

    const tipoMov = dev.tipo === 'DEVOLUCION' ? 'INGRESO' : 'EGRESO';
    const signoSaldo = dev.tipo === 'DEVOLUCION' ? { increment: monto } : { decrement: monto };

    // Detección de cheque por código/descripción del medio de pago.
    const codigoMedio = Number(dev.medio_pago?.codigo ?? 0);
    const descMedio = (dev.medio_pago?.descripcion ?? '')
      .toLowerCase()
      .normalize('NFD')
      .replace(/[\u0300-\u036f]/g, '');
    const esCheque = codigoMedio === 2 || descMedio.includes('cheque');

    const mov = await this.prisma.$transaction(async (tx) => {
      const created = await tx.tes_movimientos.create({
        data: {
          empresa_id: empresaId,
          cuenta_id: tesCuenta.id,
          tipo: tipoMov,
          estado: 'CONFIRMADO',
          fecha: dev.fecha,
          monto,
          descripcion: `${dev.tipo === 'DEVOLUCION' ? 'Devolución' : 'Reintegro'} viáticos ${dev.rendicion?.numero_rendicion ?? ''} - ${empleadoNombre}`,
          referencia: dev.numero_operacion ?? dev.numero_cheque ?? null,
          origen_tipo: 'RENDICION_DEVOLUCION',
          origen_id: devolucionId,
          created_by: userId ?? null,
        },
      });
      await tx.tes_cuentas.update({
        where: { id: tesCuenta.id },
        data: { saldo_actual: signoSaldo },
      });

      // Si es cheque, registrar el instrumento en tes_cheques.
      // DEVOLUCION: cheque RECIBIDO (el empleado devuelve un cheque).
      // REINTEGRO INMEDIATO: cheque EMITIDO (empresa paga con cheque).
      if (esCheque && dev.numero_cheque) {
        const inicioHoy = new Date();
        inicioHoy.setHours(0, 0, 0, 0);
        const esDiferido =
          dev.fecha_vencimiento_cheque && new Date(dev.fecha_vencimiento_cheque) > inicioHoy;
        const cheque = await tx.tes_cheques.create({
          data: {
            empresa_id: empresaId,
            tipo: dev.tipo === 'DEVOLUCION' ? 'RECIBIDO' : 'EMITIDO',
            estado: esDiferido ? 'DIFERIDO' : 'EN_CARTERA',
            numero_cheque: dev.numero_cheque,
            banco_emisor: dev.banco_emisor ?? null,
            titular: dev.titular_cheque ?? empleadoNombre,
            monto,
            moneda: dev.rendicion?.moneda?.codigo ?? 'PYG',
            fecha_emision: dev.fecha_emision_cheque ?? dev.fecha,
            fecha_vencimiento: dev.fecha_vencimiento_cheque ?? null,
            cuenta_id: tesCuenta.id,
            movimiento_id: created.id,
            origen_tipo: 'RENDICION_DEVOLUCION',
            origen_id: devolucionId,
            referencia: `${dev.tipo === 'DEVOLUCION' ? 'Devolución' : 'Reintegro'} viáticos ${dev.rendicion?.numero_rendicion ?? ''}`,
            registrado_por: userId ?? null,
          },
        });
        await tx.rendicion_devolucion.update({
          where: { id: devolucionId },
          data: { tes_cheque_id: cheque.id },
        });
      }
      return created;
    });

    this.logger.log(`Cierre ${devolucionId} registrado en tesorería (mov ${mov.id})`);
    return mov;
  }

  /**
   * Reversa el movimiento de devolución/reintegro.
   */
  async revertirDevolucion(devolucionId: string, empresaId: string, _userId?: string) {
    const mov = await this.prisma.tes_movimientos.findFirst({
      where: {
        empresa_id: empresaId,
        origen_tipo: 'RENDICION_DEVOLUCION',
        origen_id: devolucionId,
        estado: 'CONFIRMADO',
      },
    });
    if (!mov) return;

    // INGRESO revertido → decrement; EGRESO revertido → increment.
    const signoSaldo = mov.tipo === 'INGRESO' ? { decrement: Number(mov.monto) } : { increment: Number(mov.monto) };

    await this.prisma.$transaction(async (tx) => {
      await tx.tes_movimientos.update({
        where: { id: mov.id },
        data: { estado: 'ANULADO', updated_at: new Date() },
      });
      await tx.tes_cuentas.update({
        where: { id: mov.cuenta_id },
        data: { saldo_actual: signoSaldo },
      });
    });

    this.logger.log(`Movimiento ${mov.id} de devolución ${devolucionId} anulado`);
  }

  /**
   * Reversa un adelanto en tesorería: marca movimiento ANULADO y recompone saldo.
   */
  async revertirAdelanto(adelantoId: string, empresaId: string, userId?: string) {
    const mov = await this.prisma.tes_movimientos.findFirst({
      where: {
        empresa_id: empresaId,
        origen_tipo: 'RENDICION_ADELANTO',
        origen_id: adelantoId,
        estado: 'CONFIRMADO',
      },
    });
    if (!mov) return;

    await this.prisma.$transaction(async (tx) => {
      await tx.tes_movimientos.update({
        where: { id: mov.id },
        data: {
          estado: 'ANULADO',
          updated_at: new Date(),
        },
      });
      // Recomponer saldo (EGRESO → sumar de vuelta).
      await tx.tes_cuentas.update({
        where: { id: mov.cuenta_id },
        data: { saldo_actual: { increment: Number(mov.monto) } },
      });
    });

    this.logger.log(`Movimiento ${mov.id} de adelanto ${adelantoId} anulado`);
  }
}
