import { ConflictException, Injectable, NotFoundException } from '@nestjs/common';
import { inm_indice_tipo, Prisma } from '@prisma/client';
import { AuditService } from 'src/audit/audit.service';
import { PrismaService } from 'src/prisma/prisma.service';
import { periodoDe } from '../common/ajuste-indice';
import { CreateIndiceDto, UpdateIndiceDto } from './dto/indice.dto';

const aPeriodo = (aaaaMm: string) => new Date(`${aaaaMm}-01T00:00:00.000Z`);

/**
 * Índices de ajuste (IPC del BCP), un valor por mes y por empresa.
 * `vigente()` es lo que va a usar el cobro de cuotas en Gs. (F1): el último
 * índice cargado cuyo período no es posterior al mes de la fecha.
 */
@Injectable()
export class IndicesInmService {
  constructor(
    private readonly prisma: PrismaService,
    private readonly audit: AuditService,
  ) {}

  listar(empresa_id: string, tipo: inm_indice_tipo = inm_indice_tipo.IPC_BCP) {
    return this.prisma.inm_indices.findMany({ where: { empresa_id, tipo }, orderBy: { periodo: 'desc' } });
  }

  async vigente(empresa_id: string, fecha: Date | string, tipo: inm_indice_tipo = inm_indice_tipo.IPC_BCP) {
    return this.prisma.inm_indices.findFirst({
      where: { empresa_id, tipo, periodo: { lte: periodoDe(fecha) } },
      orderBy: { periodo: 'desc' },
    });
  }

  async crear(dto: CreateIndiceDto, empresa_id: string, user_id?: string) {
    const tipo = dto.tipo ?? inm_indice_tipo.IPC_BCP;
    try {
      const indice = await this.prisma.inm_indices.create({
        data: {
          empresa_id,
          tipo,
          periodo: aPeriodo(dto.periodo),
          valor: dto.valor,
          fuente: dto.fuente?.trim() || null,
          created_by: user_id ?? null,
        },
      });
      await this.audit.log({
        empresa_id,
        user_id,
        action: 'CREAR',
        entity_type: 'inm_indices',
        entity_id: indice.id,
        new_value: indice,
      });
      return indice;
    } catch (e) {
      if (e instanceof Prisma.PrismaClientKnownRequestError && e.code === 'P2002') {
        throw new ConflictException(
          `Ya hay un valor cargado para ${dto.periodo}. Editalo en vez de cargarlo de nuevo.`,
        );
      }
      throw e;
    }
  }

  async actualizar(id: string, dto: UpdateIndiceDto, empresa_id: string, user_id?: string) {
    const anterior = await this.deEmpresa(id, empresa_id);
    try {
      const indice = await this.prisma.inm_indices.update({
        where: { id },
        data: {
          ...(dto.tipo && { tipo: dto.tipo }),
          ...(dto.periodo && { periodo: aPeriodo(dto.periodo) }),
          ...(dto.valor !== undefined && { valor: dto.valor }),
          ...(dto.fuente !== undefined && { fuente: dto.fuente?.trim() || null }),
          updated_at: new Date(),
        },
      });
      await this.audit.log({
        empresa_id,
        user_id,
        action: 'EDITAR',
        entity_type: 'inm_indices',
        entity_id: id,
        old_value: anterior,
        new_value: indice,
      });
      return indice;
    } catch (e) {
      if (e instanceof Prisma.PrismaClientKnownRequestError && e.code === 'P2002') {
        throw new ConflictException(`Ya hay un valor cargado para ${dto.periodo}.`);
      }
      throw e;
    }
  }

  async eliminar(id: string, empresa_id: string, user_id?: string) {
    const anterior = await this.deEmpresa(id, empresa_id);
    await this.prisma.inm_indices.delete({ where: { id } });
    await this.audit.log({
      empresa_id,
      user_id,
      action: 'ELIMINAR',
      entity_type: 'inm_indices',
      entity_id: id,
      old_value: anterior,
    });
    return { ok: true };
  }

  private async deEmpresa(id: string, empresa_id: string) {
    const indice = await this.prisma.inm_indices.findFirst({ where: { id, empresa_id } });
    if (!indice) throw new NotFoundException('Índice no encontrado');
    return indice;
  }
}
