import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
import { Prisma } from '@prisma/client';
import { PrismaService } from '../../prisma/prisma.service';
import {
  CambiarEstadoPrestamoDto,
  CreatePrestamoDto,
  MarcarCuotaDescontadaDto,
  QueryPrestamosDto,
  UpdatePrestamoDto,
} from '../dto/prestamos.dto';

const TRANSICIONES_VALIDAS: Record<string, string[]> = {
  ACTIVO: ['SUSPENDIDO', 'CANCELADO'],
  SUSPENDIDO: ['ACTIVO', 'CANCELADO'],
  CANCELADO: [],
};

const round2 = (n: number) => Math.round(n * 100) / 100;

@Injectable()
export class PrestamosService {
  constructor(private readonly prisma: PrismaService) {}

  async listar(empresaId: string, query: QueryPrestamosDto) {
    const page = query.page ?? 1;
    const limit = query.limit ?? 20;
    const skip = (page - 1) * limit;

    const where: Prisma.rrhh_prestamosWhereInput = { empresa_id: empresaId };
    if (query.estado) where.estado = query.estado;
    if (query.empleado_id) where.empleado_id = query.empleado_id;

    const [data, total] = await this.prisma.$transaction([
      this.prisma.rrhh_prestamos.findMany({
        where,
        skip,
        take: limit,
        orderBy: [{ fecha_otorgamiento: 'desc' }, { created_at: 'desc' }],
        include: {
          empleado: {
            select: {
              id: true,
              numero_empleado: true,
              nombres: true,
              apellidos: true,
              cedula_identidad: true,
            },
          },
          _count: { select: { cuotas: true } },
        },
      }),
      this.prisma.rrhh_prestamos.count({ where }),
    ]);

    return { data, total, page, limit };
  }

  async obtener(id: string, empresaId: string) {
    const prestamo = await this.prisma.rrhh_prestamos.findFirst({
      where: { id, empresa_id: empresaId },
      include: {
        empleado: {
          select: {
            id: true,
            numero_empleado: true,
            nombres: true,
            apellidos: true,
            cedula_identidad: true,
            salario_base: true,
          },
        },
        cuotas: { orderBy: { numero_cuota: 'asc' } },
      },
    });
    if (!prestamo) throw new NotFoundException('Préstamo no encontrado');
    return prestamo;
  }

  private generarCronograma(
    cantidadCuotas: number,
    montoCuota: number,
    montoTotal: number,
    primerDescuento: Date,
    tipoDescuento: 'MENSUAL' | 'QUINCENAL',
  ) {
    const cuotas: {
      numero_cuota: number;
      periodo_anio: number;
      periodo_mes: number;
      periodo_tipo: string;
      monto: number;
    }[] = [];

    let acumulado = 0;
    for (let i = 0; i < cantidadCuotas; i++) {
      const fecha = new Date(primerDescuento);
      let periodoTipo: string;

      if (tipoDescuento === 'MENSUAL') {
        fecha.setMonth(fecha.getMonth() + i);
        periodoTipo = 'MENSUAL';
      } else {
        // Quincenal: alterna QUINCENAL_1 y QUINCENAL_2 cada media cuota.
        // Para simplificar, cada 2 iteraciones avanza 1 mes.
        const offsetMeses = Math.floor(i / 2);
        fecha.setMonth(fecha.getMonth() + offsetMeses);
        periodoTipo = i % 2 === 0 ? 'QUINCENAL_1' : 'QUINCENAL_2';
      }

      // Última cuota absorbe el redondeo para que la suma cuadre exactamente.
      const esUltima = i === cantidadCuotas - 1;
      const monto = esUltima ? round2(montoTotal - acumulado) : round2(montoCuota);
      acumulado = round2(acumulado + monto);

      cuotas.push({
        numero_cuota: i + 1,
        periodo_anio: fecha.getFullYear(),
        periodo_mes: fecha.getMonth() + 1,
        periodo_tipo: periodoTipo,
        monto,
      });
    }
    return cuotas;
  }

  async crear(empresaId: string, dto: CreatePrestamoDto, userId?: string) {
    const empleado = await this.prisma.rrhh_empleados.findFirst({
      where: { id: dto.empleado_id, empresa_id: empresaId },
    });
    if (!empleado) throw new BadRequestException('Empleado inválido para esta empresa');
    if (empleado.estado === 'EGRESADO') {
      throw new BadRequestException('No se pueden crear préstamos para empleados egresados');
    }

    const montoCuota = round2(dto.monto_total / dto.cantidad_cuotas);
    if (montoCuota <= 0) {
      throw new BadRequestException('Monto de cuota debe ser mayor a cero');
    }

    const cronograma = this.generarCronograma(
      dto.cantidad_cuotas,
      montoCuota,
      dto.monto_total,
      new Date(dto.primer_descuento_en),
      dto.tipo_descuento,
    );

    return this.prisma.rrhh_prestamos.create({
      data: {
        empresa_id: empresaId,
        empleado_id: dto.empleado_id,
        fecha_otorgamiento: new Date(dto.fecha_otorgamiento),
        monto_total: dto.monto_total,
        cantidad_cuotas: dto.cantidad_cuotas,
        monto_cuota: montoCuota,
        tipo_descuento: dto.tipo_descuento,
        primer_descuento_en: new Date(dto.primer_descuento_en),
        saldo_pendiente: dto.monto_total,
        observacion: dto.observacion,
        creado_por: userId,
        cuotas: {
          createMany: { data: cronograma },
        },
      },
      include: { cuotas: { orderBy: { numero_cuota: 'asc' } } },
    });
  }

  async actualizar(id: string, empresaId: string, dto: UpdatePrestamoDto) {
    const prestamo = await this.prisma.rrhh_prestamos.findFirst({
      where: { id, empresa_id: empresaId },
    });
    if (!prestamo) throw new NotFoundException('Préstamo no encontrado');

    return this.prisma.rrhh_prestamos.update({
      where: { id },
      data: { ...dto, updated_at: new Date() },
    });
  }

  async cambiarEstado(
    id: string,
    empresaId: string,
    dto: CambiarEstadoPrestamoDto,
    _userId?: string,
  ) {
    const prestamo = await this.prisma.rrhh_prestamos.findFirst({
      where: { id, empresa_id: empresaId },
    });
    if (!prestamo) throw new NotFoundException('Préstamo no encontrado');

    const permitidas = TRANSICIONES_VALIDAS[prestamo.estado] ?? [];
    if (!permitidas.includes(dto.estado)) {
      throw new BadRequestException(
        `Transición no permitida: ${prestamo.estado} → ${dto.estado}`,
      );
    }

    const observacion = dto.comentario
      ? prestamo.observacion
        ? `${prestamo.observacion}\n[${dto.estado}] ${dto.comentario}`
        : `[${dto.estado}] ${dto.comentario}`
      : prestamo.observacion;

    return this.prisma.rrhh_prestamos.update({
      where: { id },
      data: { estado: dto.estado, observacion, updated_at: new Date() },
    });
  }

  async eliminar(id: string, empresaId: string) {
    const prestamo = await this.prisma.rrhh_prestamos.findFirst({
      where: { id, empresa_id: empresaId },
      include: { cuotas: { where: { estado: 'DESCONTADO' }, take: 1 } },
    });
    if (!prestamo) throw new NotFoundException('Préstamo no encontrado');
    if (prestamo.cuotas.length > 0) {
      throw new BadRequestException(
        'El préstamo tiene cuotas ya descontadas y no puede eliminarse. Usá CANCELADO en su lugar.',
      );
    }
    // ON DELETE CASCADE limpia las cuotas pendientes.
    return this.prisma.rrhh_prestamos.delete({ where: { id } });
  }

  // ── Cuotas ─────────────────────────────────────────────
  async listarCuotas(prestamoId: string, empresaId: string) {
    const prestamo = await this.prisma.rrhh_prestamos.findFirst({
      where: { id: prestamoId, empresa_id: empresaId },
      select: { id: true },
    });
    if (!prestamo) throw new NotFoundException('Préstamo no encontrado');

    return this.prisma.rrhh_prestamos_cuotas.findMany({
      where: { prestamo_id: prestamoId },
      orderBy: { numero_cuota: 'asc' },
    });
  }

  async marcarCuotaDescontada(
    prestamoId: string,
    cuotaId: string,
    empresaId: string,
    dto: MarcarCuotaDescontadaDto,
  ) {
    const prestamo = await this.prisma.rrhh_prestamos.findFirst({
      where: { id: prestamoId, empresa_id: empresaId },
    });
    if (!prestamo) throw new NotFoundException('Préstamo no encontrado');

    const cuota = await this.prisma.rrhh_prestamos_cuotas.findFirst({
      where: { id: cuotaId, prestamo_id: prestamoId },
    });
    if (!cuota) throw new NotFoundException('Cuota no encontrada');
    if (cuota.estado !== 'PENDIENTE') {
      throw new BadRequestException(
        `Solo se pueden marcar cuotas PENDIENTE como descontadas (actual: ${cuota.estado})`,
      );
    }

    const fecha = dto.fecha_descuento ? new Date(dto.fecha_descuento) : new Date();

    return this.prisma.$transaction(async (tx) => {
      const cuotaActualizada = await tx.rrhh_prestamos_cuotas.update({
        where: { id: cuotaId },
        data: {
          estado: 'DESCONTADO',
          liquidacion_id: dto.liquidacion_id,
          fecha_descuento: fecha,
          updated_at: new Date(),
        },
      });

      const nuevoSaldo = round2(
        Number(prestamo.saldo_pendiente) - Number(cuotaActualizada.monto),
      );

      const cuotasPendientes = await tx.rrhh_prestamos_cuotas.count({
        where: { prestamo_id: prestamoId, estado: 'PENDIENTE' },
      });

      const nuevoEstado = cuotasPendientes === 0 && nuevoSaldo <= 0 ? 'CANCELADO' : prestamo.estado;

      const prestamoActualizado = await tx.rrhh_prestamos.update({
        where: { id: prestamoId },
        data: {
          saldo_pendiente: Math.max(nuevoSaldo, 0),
          estado: nuevoEstado,
          updated_at: new Date(),
        },
      });

      return { cuota: cuotaActualizada, prestamo: prestamoActualizado };
    });
  }
}
