import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
import { Prisma } from '@prisma/client';
import { PrismaService } from '../../prisma/prisma.service';
import {
  AccionSolicitudDto,
  CreateSolicitudVacacionesDto,
  QuerySaldosDto,
  QuerySolicitudesDto,
  RecalcularSaldosDto,
  UpdateSolicitudVacacionesDto,
} from '../dto/vacaciones.dto';

const TRANSICIONES_SOLICITUD: Record<string, string[]> = {
  SOLICITADO: ['APROBADO', 'RECHAZADO', 'CANCELADO'],
  APROBADO: ['CANCELADO'],
  RECHAZADO: [],
  CANCELADO: [],
};

const DIA_MS = 24 * 60 * 60 * 1000;

const diferenciaAnios = (desde: Date, hasta: Date) => {
  let anios = hasta.getFullYear() - desde.getFullYear();
  const m = hasta.getMonth() - desde.getMonth();
  if (m < 0 || (m === 0 && hasta.getDate() < desde.getDate())) anios--;
  return anios;
};

/**
 * Escala legal Paraguay (Art. 219 CT):
 *   < 5 años        : 12 días corridos
 *   5 a 10 años     : 18 días corridos
 *   > 10 años       : 30 días corridos
 */
const diasCorrespondientesPorAntiguedad = (fechaIngreso: Date, fechaCorte: Date) => {
  const anios = diferenciaAnios(fechaIngreso, fechaCorte);
  if (anios < 5) return 12;
  if (anios <= 10) return 18;
  return 30;
};

const contarDiasHabiles = (desde: Date, hasta: Date) => {
  let count = 0;
  const cur = new Date(desde);
  cur.setHours(0, 0, 0, 0);
  const fin = new Date(hasta);
  fin.setHours(0, 0, 0, 0);
  while (cur.getTime() <= fin.getTime()) {
    const dia = cur.getDay();
    if (dia !== 0 && dia !== 6) count++;
    cur.setTime(cur.getTime() + DIA_MS);
  }
  return count;
};

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

  // ── Saldos ─────────────────────────────────────────────────
  async listarSaldos(empresaId: string, query: QuerySaldosDto) {
    const anio = query.anio ?? new Date().getFullYear();
    const where: Prisma.rrhh_vacaciones_saldoWhereInput = {
      empresa_id: empresaId,
      anio,
    };
    if (query.empleado_id) where.empleado_id = query.empleado_id;

    return this.prisma.rrhh_vacaciones_saldo.findMany({
      where,
      orderBy: [{ empleado: { apellidos: 'asc' } }],
      include: {
        empleado: {
          select: {
            id: true,
            numero_empleado: true,
            nombres: true,
            apellidos: true,
            fecha_ingreso: true,
            estado: true,
          },
        },
      },
    });
  }

  async recalcularSaldos(empresaId: string, dto: RecalcularSaldosDto) {
    const anio = dto.anio ?? new Date().getFullYear();
    const fechaCorte = new Date(anio, 11, 31);

    const empleadosWhere: Prisma.rrhh_empleadosWhereInput = {
      empresa_id: empresaId,
      estado: { not: 'EGRESADO' },
    };
    if (dto.empleado_id) empleadosWhere.id = dto.empleado_id;

    const empleados = await this.prisma.rrhh_empleados.findMany({ where: empleadosWhere });

    let creados = 0;
    let actualizados = 0;

    for (const emp of empleados) {
      const correspondidos = diasCorrespondientesPorAntiguedad(emp.fecha_ingreso, fechaCorte);

      // Días tomados = solicitudes APROBADAS de tipo VACACION cuyas fechas
      // caen total o parcialmente dentro del año.
      const desdeAnio = new Date(anio, 0, 1);
      const hastaAnio = new Date(anio, 11, 31);
      const tomados = await this.prisma.rrhh_vacaciones_solicitudes.findMany({
        where: {
          empresa_id: empresaId,
          empleado_id: emp.id,
          tipo: 'VACACION',
          estado: 'APROBADO',
          fecha_inicio: { lte: hastaAnio },
          fecha_fin: { gte: desdeAnio },
        },
      });
      const diasTomados = tomados.reduce((acc, s) => acc + Number(s.dias_habiles || 0), 0);
      const pendientes = Math.max(0, correspondidos - diasTomados);

      const existe = await this.prisma.rrhh_vacaciones_saldo.findFirst({
        where: { empleado_id: emp.id, anio },
      });
      if (existe) {
        if (existe.cerrado) continue;
        await this.prisma.rrhh_vacaciones_saldo.update({
          where: { id: existe.id },
          data: {
            dias_correspondidos: correspondidos,
            dias_tomados: diasTomados,
            dias_pendientes: pendientes,
            updated_at: new Date(),
          },
        });
        actualizados++;
      } else {
        await this.prisma.rrhh_vacaciones_saldo.create({
          data: {
            empresa_id: empresaId,
            empleado_id: emp.id,
            anio,
            dias_correspondidos: correspondidos,
            dias_tomados: diasTomados,
            dias_pendientes: pendientes,
          },
        });
        creados++;
      }
    }

    return {
      anio,
      empleados_procesados: empleados.length,
      saldos_creados: creados,
      saldos_actualizados: actualizados,
    };
  }

  // ── Solicitudes ────────────────────────────────────────────
  async listarSolicitudes(empresaId: string, query: QuerySolicitudesDto) {
    const page = query.page ?? 1;
    const limit = query.limit ?? 20;
    const skip = (page - 1) * limit;

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

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

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

  async crearSolicitud(empresaId: string, dto: CreateSolicitudVacacionesDto, 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 solicitudes para empleados egresados');
    }

    const desde = new Date(dto.fecha_inicio);
    const hasta = new Date(dto.fecha_fin);
    if (Number.isNaN(desde.getTime()) || Number.isNaN(hasta.getTime())) {
      throw new BadRequestException('Fechas inválidas');
    }
    if (hasta < desde) {
      throw new BadRequestException('Fecha fin no puede ser anterior a fecha inicio');
    }

    const diasHabiles = contarDiasHabiles(desde, hasta);
    if (diasHabiles <= 0) {
      throw new BadRequestException('El rango de fechas no incluye días hábiles');
    }

    return this.prisma.rrhh_vacaciones_solicitudes.create({
      data: {
        empresa_id: empresaId,
        empleado_id: dto.empleado_id,
        fecha_inicio: desde,
        fecha_fin: hasta,
        dias_habiles: diasHabiles,
        tipo: dto.tipo,
        afecta_salario: dto.afecta_salario ?? false,
        observacion: dto.observacion,
        creado_por: userId,
      },
    });
  }

  async actualizarSolicitud(id: string, empresaId: string, dto: UpdateSolicitudVacacionesDto) {
    const sol = await this.prisma.rrhh_vacaciones_solicitudes.findFirst({
      where: { id, empresa_id: empresaId },
    });
    if (!sol) throw new NotFoundException('Solicitud no encontrada');
    if (sol.estado !== 'SOLICITADO') {
      throw new BadRequestException(
        `Solo se puede editar una solicitud SOLICITADO (actual: ${sol.estado})`,
      );
    }

    const desde = dto.fecha_inicio ? new Date(dto.fecha_inicio) : sol.fecha_inicio;
    const hasta = dto.fecha_fin ? new Date(dto.fecha_fin) : sol.fecha_fin;
    if (hasta < desde) {
      throw new BadRequestException('Fecha fin no puede ser anterior a fecha inicio');
    }
    const diasHabiles = contarDiasHabiles(desde, hasta);

    return this.prisma.rrhh_vacaciones_solicitudes.update({
      where: { id },
      data: {
        fecha_inicio: desde,
        fecha_fin: hasta,
        dias_habiles: diasHabiles,
        tipo: dto.tipo ?? sol.tipo,
        afecta_salario: dto.afecta_salario ?? sol.afecta_salario,
        observacion: dto.observacion ?? sol.observacion,
        updated_at: new Date(),
      },
    });
  }

  private async transicionar(
    id: string,
    empresaId: string,
    nuevoEstado: string,
    userId?: string,
    comentario?: string,
  ) {
    const sol = await this.prisma.rrhh_vacaciones_solicitudes.findFirst({
      where: { id, empresa_id: empresaId },
    });
    if (!sol) throw new NotFoundException('Solicitud no encontrada');

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

    const data: Prisma.rrhh_vacaciones_solicitudesUpdateInput = {
      estado: nuevoEstado,
      updated_at: new Date(),
    };
    if (comentario) {
      data.observacion = sol.observacion
        ? `${sol.observacion}\n[${nuevoEstado}] ${comentario}`
        : `[${nuevoEstado}] ${comentario}`;
    }
    if (nuevoEstado === 'APROBADO') {
      data.aprobado_por = userId;
      data.fecha_aprobacion = new Date();
    }
    if (nuevoEstado === 'RECHAZADO') {
      data.rechazado_por = userId;
      data.fecha_rechazo = new Date();
    }
    if (nuevoEstado === 'CANCELADO') {
      data.cancelado_por = userId;
      data.fecha_cancelacion = new Date();
    }

    return this.prisma.rrhh_vacaciones_solicitudes.update({ where: { id }, data });
  }

  aprobar(id: string, empresaId: string, dto: AccionSolicitudDto, userId?: string) {
    return this.transicionar(id, empresaId, 'APROBADO', userId, dto.comentario);
  }

  rechazar(id: string, empresaId: string, dto: AccionSolicitudDto, userId?: string) {
    return this.transicionar(id, empresaId, 'RECHAZADO', userId, dto.comentario);
  }

  cancelar(id: string, empresaId: string, dto: AccionSolicitudDto, userId?: string) {
    return this.transicionar(id, empresaId, 'CANCELADO', userId, dto.comentario);
  }

  async eliminar(id: string, empresaId: string) {
    const sol = await this.prisma.rrhh_vacaciones_solicitudes.findFirst({
      where: { id, empresa_id: empresaId },
    });
    if (!sol) throw new NotFoundException('Solicitud no encontrada');
    if (sol.estado !== 'SOLICITADO') {
      throw new BadRequestException(
        `Solo se puede eliminar una solicitud SOLICITADO (actual: ${sol.estado}). Usá «Cancelar» en los demás casos.`,
      );
    }
    return this.prisma.rrhh_vacaciones_solicitudes.delete({ where: { id } });
  }
}
