import { BadRequestException, Injectable, Logger, NotFoundException } from '@nestjs/common';
import { randomUUID } from 'crypto';
import { AuditService } from '../../audit/audit.service';
import { ContabilidadIntegracionService } from '../../contabilidad/services/integracion.service';
import { PrismaService } from '../../prisma/prisma.service';
import { AnularTesTransferenciaDto, CreateTesTransferenciaDto, FilterTesTransferenciaDto } from '../dto/tes-transferencia.dto';

@Injectable()
export class TesTransferenciasService {
  private readonly logger = new Logger(TesTransferenciasService.name);

  constructor(
    private readonly prisma: PrismaService,
    private readonly contabilidad: ContabilidadIntegracionService,
    private readonly audit: AuditService,
  ) {}

  async findAll(empresaId: string, filters: FilterTesTransferenciaDto = {}) {
    const where: any = {
      empresa_id: empresaId,
      tipo: 'TRANSFERENCIA',
      // Solo devolver el movimiento origen (EGRESO) para no duplicar
      origen_tipo: 'TRANSFERENCIA_ORIGEN',
    };
    if (filters.cuenta_id) where.cuenta_id = filters.cuenta_id;
    if (filters.estado) where.estado = filters.estado;
    if (filters.desde || filters.hasta) {
      where.fecha = {};
      if (filters.desde) where.fecha.gte = new Date(filters.desde);
      if (filters.hasta) where.fecha.lte = new Date(filters.hasta);
    }

    const origenes = await this.prisma.tes_movimientos.findMany({
      where,
      include: {
        cuenta: { select: { id: true, nombre: true, moneda: true } },
      },
      orderBy: [{ fecha: 'desc' }, { created_at: 'desc' }],
    });

    // Cargar los movimientos destino vinculados
    const transferIds = origenes.map((o) => o.transferencia_id).filter(Boolean) as string[];
    const destinos = transferIds.length > 0
      ? await this.prisma.tes_movimientos.findMany({
          where: { transferencia_id: { in: transferIds }, origen_tipo: 'TRANSFERENCIA_DESTINO' },
          include: { cuenta: { select: { id: true, nombre: true, moneda: true } } },
        })
      : [];

    const destinoMap = new Map(destinos.map((d) => [d.transferencia_id, d]));

    return origenes.map((origen) => ({
      id: origen.transferencia_id,
      estado: origen.estado,
      fecha: origen.fecha,
      descripcion: origen.descripcion,
      monto_origen: Number(origen.monto),
      cuenta_origen: origen.cuenta,
      cuenta_destino: destinoMap.get(origen.transferencia_id!)?.cuenta ?? null,
      monto_destino: Number(destinoMap.get(origen.transferencia_id!)?.monto ?? origen.monto),
      tipo_cambio: (origen as any).tipo_cambio ?? null,
      movimiento_origen_id: origen.id,
      movimiento_destino_id: destinoMap.get(origen.transferencia_id!)?.id ?? null,
      created_by: origen.created_by,
      confirmado_por: origen.confirmado_por,
      confirmado_en: origen.confirmado_en,
      anulado_por: origen.anulado_por,
      anulado_en: origen.anulado_en,
      motivo_anulacion: origen.motivo_anulacion,
    }));
  }

  async create(dto: CreateTesTransferenciaDto, empresaId: string, userId: string) {
    if (dto.cuenta_origen_id === dto.cuenta_destino_id) {
      throw new BadRequestException('La cuenta de origen y destino no pueden ser la misma');
    }

    const [cuentaOrigen, cuentaDestino] = await Promise.all([
      this.prisma.tes_cuentas.findFirst({ where: { id: dto.cuenta_origen_id, empresa_id: empresaId, activo: true } }),
      this.prisma.tes_cuentas.findFirst({ where: { id: dto.cuenta_destino_id, empresa_id: empresaId, activo: true } }),
    ]);
    if (!cuentaOrigen) throw new NotFoundException('Cuenta de origen no encontrada');
    if (!cuentaDestino) throw new NotFoundException('Cuenta de destino no encontrada');

    // Validar tipo de cambio si las monedas son distintas
    const monedaDistinta = cuentaOrigen.moneda !== cuentaDestino.moneda;
    if (monedaDistinta && (!dto.tipo_cambio || dto.tipo_cambio <= 0)) {
      throw new BadRequestException('Debe ingresar el tipo de cambio cuando las cuentas tienen monedas distintas');
    }

    // TC siempre expresado como "1 moneda_extranjera = X PYG" (convención estándar Paraguay)
    // PYG → extranjera: dividir.  extranjera → PYG: multiplicar.
    let montoDestino: number;
    if (!monedaDistinta) {
      montoDestino = dto.monto;
    } else if (cuentaOrigen.moneda === 'PYG') {
      montoDestino = dto.monto / dto.tipo_cambio!;
    } else {
      montoDestino = dto.monto * dto.tipo_cambio!;
    }
    const transferencia_id = randomUUID();
    const fechaTransferencia = new Date(dto.fecha + 'T00:00:00');
    const descripcion = dto.descripcion ?? `Transferencia ${cuentaOrigen.nombre} → ${cuentaDestino.nombre}`;

    let movOrigenId: string;
    let movDestinoId: string;

    await this.prisma.$transaction(async (tx) => {
      const movOrigen = await tx.tes_movimientos.create({
        data: {
          empresa_id: empresaId,
          cuenta_id: dto.cuenta_origen_id,
          tipo: 'TRANSFERENCIA',
          estado: 'CONFIRMADO',
          fecha: fechaTransferencia,
          monto: dto.monto,
          descripcion,
          transferencia_id,
          origen_tipo: 'TRANSFERENCIA_ORIGEN',
          created_by: userId,
          confirmado_por: userId,
          confirmado_en: new Date(),
        },
      });
      movOrigenId = movOrigen.id;

      const movDestino = await tx.tes_movimientos.create({
        data: {
          empresa_id: empresaId,
          cuenta_id: dto.cuenta_destino_id,
          tipo: 'TRANSFERENCIA',
          estado: 'CONFIRMADO',
          fecha: fechaTransferencia,
          monto: montoDestino,
          descripcion,
          transferencia_id,
          origen_tipo: 'TRANSFERENCIA_DESTINO',
          created_by: userId,
          confirmado_por: userId,
          confirmado_en: new Date(),
        },
      });
      movDestinoId = movDestino.id;

      // Actualizar saldos
      await tx.tes_cuentas.update({
        where: { id: dto.cuenta_origen_id },
        data: { saldo_actual: { increment: -dto.monto } },
      });
      await tx.tes_cuentas.update({
        where: { id: dto.cuenta_destino_id },
        data: { saldo_actual: { increment: montoDestino } },
      });
    });

    // Integración contable: asiento HABER cuenta_origen / DEBE cuenta_destino
    this.contabilidad.integrarTransferenciaTes(movOrigenId!, movDestinoId!, transferencia_id, empresaId).catch((e) =>
      this.logger.error(`Error al contabilizar transferencia ${transferencia_id}: ${e.message}`),
    );

    await this.audit.log({
      empresa_id: empresaId,
      user_id: userId,
      action: 'TES_TRANSFERENCIA_CREADA',
      entity_type: 'tes_transferencias',
      entity_id: transferencia_id,
      new_value: { cuenta_origen: cuentaOrigen.nombre, cuenta_destino: cuentaDestino.nombre, monto: dto.monto },
      descripcion: `Transferencia ${cuentaOrigen.nombre} → ${cuentaDestino.nombre} por ${dto.monto.toLocaleString()} ${cuentaOrigen.moneda}`,
    });

    return this.findByTransferenciaId(transferencia_id, empresaId);
  }

  async anular(transferenciaId: string, dto: AnularTesTransferenciaDto, empresaId: string, userId: string) {
    const movimientos = await this.prisma.tes_movimientos.findMany({
      where: { transferencia_id: transferenciaId, empresa_id: empresaId },
      include: { cuenta: { select: { id: true, nombre: true, moneda: true } } },
    });
    if (movimientos.length === 0) throw new NotFoundException('Transferencia no encontrada');
    if (movimientos.some((m) => m.estado === 'ANULADO')) {
      throw new BadRequestException('La transferencia ya está anulada');
    }

    const origen = movimientos.find((m) => m.origen_tipo === 'TRANSFERENCIA_ORIGEN');
    const destino = movimientos.find((m) => m.origen_tipo === 'TRANSFERENCIA_DESTINO');

    await this.prisma.$transaction(async (tx) => {
      await tx.tes_movimientos.updateMany({
        where: { transferencia_id: transferenciaId, empresa_id: empresaId },
        data: { estado: 'ANULADO', motivo_anulacion: dto.motivo, anulado_por: userId, anulado_en: new Date() },
      });

      // Revertir saldos
      if (origen) {
        await tx.tes_cuentas.update({
          where: { id: origen.cuenta_id },
          data: { saldo_actual: { increment: Number(origen.monto) } },
        });
      }
      if (destino) {
        await tx.tes_cuentas.update({
          where: { id: destino.cuenta_id },
          data: { saldo_actual: { increment: -Number(destino.monto) } },
        });
      }
    });

    // Reversión contable — omite si no hay asiento previo
    this.contabilidad.revertirDocumento('tes_transferencias', transferenciaId, userId).catch((e) =>
      this.logger.error(`Error al revertir asiento de transferencia ${transferenciaId}: ${e.message}`),
    );

    await this.audit.log({
      empresa_id: empresaId,
      user_id: userId,
      action: 'TES_TRANSFERENCIA_ANULADA',
      entity_type: 'tes_transferencias',
      entity_id: transferenciaId,
      new_value: { motivo: dto.motivo },
      descripcion: `Transferencia anulada. Motivo: ${dto.motivo}`,
    });

    return this.findByTransferenciaId(transferenciaId, empresaId);
  }

  private async findByTransferenciaId(transferenciaId: string, empresaId: string) {
    const movimientos = await this.prisma.tes_movimientos.findMany({
      where: { transferencia_id: transferenciaId, empresa_id: empresaId },
      include: { cuenta: { select: { id: true, nombre: true, moneda: true } } },
    });
    const origen = movimientos.find((m) => m.origen_tipo === 'TRANSFERENCIA_ORIGEN');
    const destino = movimientos.find((m) => m.origen_tipo === 'TRANSFERENCIA_DESTINO');
    return {
      id: transferenciaId,
      estado: origen?.estado,
      fecha: origen?.fecha,
      descripcion: origen?.descripcion,
      monto_origen: Number(origen?.monto ?? 0),
      monto_destino: Number(destino?.monto ?? 0),
      cuenta_origen: origen?.cuenta ?? null,
      cuenta_destino: destino?.cuenta ?? null,
      movimiento_origen_id: origen?.id,
      movimiento_destino_id: destino?.id,
      motivo_anulacion: origen?.motivo_anulacion,
    };
  }

}
