import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
import { Prisma } from '@prisma/client';
import { PrismaService } from '../../prisma/prisma.service';
import { CreateParametroDto, QueryParametrosDto, UpdateParametroDto } from '../dto/parametros.dto';

const isVigenteAt = (date: Date) => ({
  vigencia_desde: { lte: date },
  OR: [{ vigencia_hasta: null }, { vigencia_hasta: { gte: date } }],
});

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

  listar(empresaId: string, query: QueryParametrosDto) {
    const where: Prisma.rrhh_parametros_sistemaWhereInput = { empresa_id: empresaId };
    if (query.clave) where.clave = query.clave;
    if (query.categoria) where.categoria = query.categoria;

    const wantsOnlyVigente =
      query.vigente === true || query.vigente === 'true' || query.vigente === '1';
    if (wantsOnlyVigente) {
      Object.assign(where, isVigenteAt(new Date()));
    }

    return this.prisma.rrhh_parametros_sistema.findMany({
      where,
      orderBy: [{ categoria: 'asc' }, { clave: 'asc' }, { vigencia_desde: 'desc' }],
    });
  }

  async obtenerVigente(empresaId: string, clave: string, fecha = new Date()) {
    return this.prisma.rrhh_parametros_sistema.findFirst({
      where: { empresa_id: empresaId, clave, ...isVigenteAt(fecha) },
      orderBy: { vigencia_desde: 'desc' },
    });
  }

  async crear(empresaId: string, dto: CreateParametroDto, userId?: string) {
    const desde = new Date(dto.vigencia_desde);
    if (Number.isNaN(desde.getTime())) {
      throw new BadRequestException('vigencia_desde inválida');
    }
    const hasta = dto.vigencia_hasta ? new Date(dto.vigencia_hasta) : null;
    if (hasta && hasta < desde) {
      throw new BadRequestException('vigencia_hasta no puede ser anterior a vigencia_desde');
    }

    const dup = await this.prisma.rrhh_parametros_sistema.findFirst({
      where: { empresa_id: empresaId, clave: dto.clave, vigencia_desde: desde },
    });
    if (dup) {
      throw new BadRequestException(
        `Ya existe un parámetro "${dto.clave}" con vigencia desde ${dto.vigencia_desde}`,
      );
    }

    // Versionado temporal: la nueva vigencia sustituye a la anterior. Cerramos
    // las versiones abiertas (o que se solapan) de la misma clave que empiecen
    // antes, poniéndoles vigencia_hasta = el día previo a la nueva. Así queda
    // una sola versión vigente a la vez y no se solapan rangos. Es una acción
    // del sistema (no un "edit" del usuario), por eso no aplica la regla de
    // `editable` — cerrar una versión legal superada es correcto.
    const previaHasta = new Date(desde.getTime() - 24 * 60 * 60 * 1000);
    return this.prisma.$transaction(async (tx) => {
      await tx.rrhh_parametros_sistema.updateMany({
        where: {
          empresa_id: empresaId,
          clave: dto.clave,
          vigencia_desde: { lt: desde },
          OR: [{ vigencia_hasta: null }, { vigencia_hasta: { gte: desde } }],
        },
        data: { vigencia_hasta: previaHasta, modificado_por: userId, modificado_en: new Date() },
      });
      return tx.rrhh_parametros_sistema.create({
        data: {
          empresa_id: empresaId,
          clave: dto.clave,
          nombre: dto.nombre,
          valor: dto.valor,
          tipo_dato: dto.tipo_dato,
          categoria: dto.categoria,
          editable: dto.editable ?? true,
          vigencia_desde: desde,
          vigencia_hasta: hasta,
          modificado_por: userId,
        },
      });
    });
  }

  async actualizar(id: string, empresaId: string, dto: UpdateParametroDto, userId?: string) {
    const existe = await this.prisma.rrhh_parametros_sistema.findFirst({
      where: { id, empresa_id: empresaId },
    });
    if (!existe) throw new NotFoundException('Parámetro no encontrado');

    if (!existe.editable && (dto.valor !== undefined || dto.vigencia_desde || dto.vigencia_hasta !== undefined)) {
      throw new BadRequestException(
        'Este parámetro no es editable. Para registrar un cambio normativo, creá una nueva versión con vigencia_desde futura.',
      );
    }

    const desde = dto.vigencia_desde ? new Date(dto.vigencia_desde) : existe.vigencia_desde;
    const hasta =
      dto.vigencia_hasta === undefined
        ? existe.vigencia_hasta
        : dto.vigencia_hasta
          ? new Date(dto.vigencia_hasta)
          : null;
    if (hasta && hasta < desde) {
      throw new BadRequestException('vigencia_hasta no puede ser anterior a vigencia_desde');
    }

    return this.prisma.rrhh_parametros_sistema.update({
      where: { id },
      data: {
        nombre: dto.nombre ?? existe.nombre,
        valor: dto.valor ?? existe.valor,
        tipo_dato: dto.tipo_dato ?? existe.tipo_dato,
        categoria: dto.categoria ?? existe.categoria,
        vigencia_desde: desde,
        vigencia_hasta: hasta,
        editable: dto.editable ?? existe.editable,
        modificado_por: userId,
        modificado_en: new Date(),
      },
    });
  }

  async eliminar(id: string, empresaId: string) {
    const existe = await this.prisma.rrhh_parametros_sistema.findFirst({
      where: { id, empresa_id: empresaId },
    });
    if (!existe) throw new NotFoundException('Parámetro no encontrado');
    if (!existe.editable) {
      throw new BadRequestException('Los parámetros legales no editables no pueden eliminarse');
    }
    return this.prisma.rrhh_parametros_sistema.delete({ where: { id } });
  }
}
