import { BadRequestException, Injectable, Logger } from '@nestjs/common';
import { PrismaService } from 'src/prisma/prisma.service';

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

  constructor(private readonly prisma: PrismaService) {}

  private formatDateTimeSifen(date: Date): string {
    return date.toISOString().slice(0, 19);
  }

  private toNumberOrNull(value: unknown): number | null {
    if (value === null || value === undefined) return null;
    const n = typeof value === 'number' ? value : Number(value);
    return Number.isFinite(n) ? n : null;
  }

  async buildFacturaPayload(facturaId: string, empresaId: string): Promise<Record<string, unknown>> {
    const factura = await this.prisma.factura_cab.findFirst({
      where: { id: facturaId, empresa_id: empresaId },
      include: {
        clientes: {
          include: {
            personas: {
              include: {
                naturaleza_receptor: true,
                tipo_contribuyente: true,
                tipo_documento_identidad: true,
                ciudades: {
                  include: {
                    distritos: {
                      include: {
                        departamentos: true,
                      },
                    },
                  },
                },
              },
            },
            tipo_operacion: true,
          },
        },
        // Dirección seleccionada del cliente (cuando tiene varias). Sin este include
        // la relación queda undefined y el payload cae al domicilio por defecto de la
        // persona, ignorando el cliente_direccion_id guardado en la factura.
        cliente_direccion: {
          include: {
            ciudades: true,
            distritos: true,
            departamentos: true,
          },
        },
        moneda: true,
        condicion_operacion: true,
        indicador_presencia: true,
        tipo_transaccion: true,
        tipo_impuesto: true,
        factura_subtotales: true,
        factura_forma_pagos: {
          include: {
            medio_pago: true,
            forma_procesamiento_pago: true,
            tarjeta: true,
          },
        },
        factura_cuotas: true,
        factura_det: {
          include: {
            productos: {
              include: {
                unidades_de_medida: true,
                afectacion_iva: true,
              },
            },
          },
        },
        // Remisiones que consolida esta factura (flujo Remisión → Factura). La
        // mercadería salió antes con estas remisiones electrónicas: van como
        // DocumentosAsociados (E7) con su CDC.
        nota_remision_factura: {
          include: {
            nota_remision_cab: {
              select: { cdc: true, dest: true, dpunexp: true, dnumdoc: true },
            },
          },
        },
      },
    });

    if (!factura) throw new Error('Factura no encontrada');

    const persona = factura.clientes.personas;
    const dirSeleccionada = factura.cliente_direccion ?? null;
    const personaCiudad = persona.ciudades;
    const ciudad = dirSeleccionada?.ciudades ?? personaCiudad;
    const distrito = dirSeleccionada?.distritos ?? personaCiudad?.distritos;
    const departamento = dirSeleccionada?.departamentos ?? personaCiudad?.distritos?.departamentos;
    const subtotales = factura.factura_subtotales[0];
    const sucursal =
      factura.empresa_id && factura.dest
        ? await this.prisma.empresas_sucursales.findFirst({
            where: {
              empresa_id: factura.empresa_id,
              punto_establecimiento: factura.dest,
              active: true,
            },
            select: { id: true },
          })
        : null;

    const payload: Record<string, unknown> = {
      empresa_id: factura.empresa_id ?? undefined,
      sucursal_id: sucursal?.id ?? null,
      cdc: factura.cdc ?? null,
      iTiDE: '1',
      dInfoEmi: factura.dinfoemi ?? null,
      dInfoFisc: factura.dinfofisc ?? null,
      dInfAdic: factura.dinfadic ?? null,
      dOrdCompra: factura.dordcompra ?? null,
      dOrdVta: factura.dordvta ?? null,
      dAsiento: factura.dasiento ?? null,
      dEst: factura.dest,
      dPunExp: factura.dpunexp,
      dNumDoc: factura.dnumdoc,
      dSerieNum: factura.dserienum ?? null,
      dFeEmiDE: this.formatDateTimeSifen(factura.dfeemide),
      dDCondOpe: factura.condicion_operacion?.descripcion ?? null,
      iTipTra:
        this.toNumberOrNull(factura.tipo_transaccion?.codigo) ?? this.toNumberOrNull(factura.tipo_transaccion_id),
      dDesTipTra: factura.tipo_transaccion?.descripcion ?? null,
      iTImp: this.toNumberOrNull(factura.tipo_impuesto?.codigo) ?? 1,
      cMoneOpe: factura.moneda?.codigo ?? null,
      dDesMoneOpe: factura.moneda?.descripcion ?? null,
      dCondTiCam: this.toNumberOrNull(factura.dcondticam) ?? null,
      dTiCam: this.toNumberOrNull(factura.dticam) ?? null,
      iCondAnt: this.toNumberOrNull(factura.icondant) ?? null,
      iTipCont: this.toNumberOrNull(persona.tipo_contribuyente?.codigo) ?? null,
      gRespDE: null,
      iNatRec: this.toNumberOrNull(persona.naturaleza_receptor?.codigo) ?? null,
      iTiOpe: this.toNumberOrNull(factura.clientes.tipo_operacion?.codigo) ?? null,
      cPaisRec: 'PRY',
      iTiContRec: this.toNumberOrNull(persona.tipo_contribuyente?.codigo) ?? null,
      dRucRec: persona.ruc ?? null,
      dDVRec: persona.dv ? this.toNumberOrNull(persona.dv) : null,
      iTipIDRec: persona.tipo_documento_identidad?.codigo ?? null,
      dDTipIDRec: persona.tipo_documento_identidad?.descripcion ?? null,
      dNumIDRec: persona.nro_documento ?? null,
      dNomRec: persona.razon_social,
      dNomFanRec: factura.clientes.nombre_fantasia ?? null,
      dDirRec: factura.ddirrec ?? dirSeleccionada?.direccion ?? persona.direccion ?? null,
      dNumCasRec: dirSeleccionada?.nro
        ? this.toNumberOrNull(dirSeleccionada.nro)
        : persona.nro_casa
          ? this.toNumberOrNull(persona.nro_casa)
          : 0,
      cDepRec: departamento?.codigo ?? null,
      cDisRec: distrito?.codigo ?? null,
      cCiuRec: ciudad?.codigo ?? null,
      dTelRec: persona.telefono ? persona.telefono.replace(/\s+/g, '') : null,
      dCelRec: persona.celular ? persona.celular.replace(/\s+/g, '') : null,
      dEmailRec: persona.email ?? null,
      dCodCliente: factura.clientes.cod_cliente ?? null,
      iIndPres: this.toNumberOrNull(factura.indicador_presencia?.codigo) ?? null,
      dDesIndPres:
        factura.indicador_presencia?.codigo === 9 ? (factura.indicador_presencia?.descripcion ?? null) : null,
      dFecEmNR: factura.dfecemnr ? this.formatDateTimeSifen(new Date(factura.dfecemnr)) : null,
      gCompPub: null,
      iCondOpe: this.toNumberOrNull(factura.condicion_operacion?.codigo) ?? null,
      dModCont: factura.dmodcont ?? null,
      dEntCont: factura.dentcont ?? null,
      dAnoCont: factura.danocont ?? null,
      dSecCont: factura.dseccont ?? null,
      dFeCodCont: factura.dfecodcont ? this.formatDateTimeSifen(new Date(factura.dfecodcont)) : null,
      iCondCred: factura.icondcred ?? null,
      dPlazoCre: factura.dplazocre ?? null,
      dCuotas: factura.dcuotas ?? null,
      dMonEnt: factura.dmonent ? this.toNumberOrNull(factura.dmonent) : null,
      dVencCuo: factura.factura_cuotas?.length
        ? this.formatDateTimeSifen(
            new Date(
              [...factura.factura_cuotas].sort(
                (a, b) => new Date(b.dvenccuo).getTime() - new Date(a.dvenccuo).getTime(),
              )[0].dvenccuo,
            ),
          )
        : null,
      link_documento: factura.enlace_qr ?? null,
    };
    if (subtotales) {
      payload.Subtotales = [
        {
          dSubExe: this.toNumberOrNull(subtotales.dsubexe) ?? 0,
          dSubExo: this.toNumberOrNull(subtotales.dsubexo) ?? 0,
          dSub5: this.toNumberOrNull(subtotales.dsub5) ?? 0,
          dSub10: this.toNumberOrNull(subtotales.dsub10) ?? 0,
          dTotOpe: this.toNumberOrNull(subtotales.dtotope) ?? 0,
          dTotDesc: this.toNumberOrNull(subtotales.dtotdesc) ?? 0,
          dTotDescGlotem: this.toNumberOrNull(subtotales.dtotdescglotem) ?? 0,
          dTotAntItem: this.toNumberOrNull(subtotales.dtotantitem) ?? 0,
          dTotAnt: this.toNumberOrNull(subtotales.dtotant) ?? 0,
          dPorcDescTotal: this.toNumberOrNull(subtotales.dporcdesctotal) ?? 0,
          dDescTotal: this.toNumberOrNull(subtotales.ddesctotal) ?? 0,
          dAnticipo: this.toNumberOrNull(subtotales.danticipo) ?? 0,
          dRedon: this.toNumberOrNull(subtotales.dredon) ?? 0,
          dComi: this.toNumberOrNull(subtotales.dcomi) ?? 0,
          dTotGralOpe: this.toNumberOrNull(subtotales.dtotgralope) ?? 0,
          dIVA5: this.toNumberOrNull(subtotales.diva5) ?? 0,
          dIVA10: this.toNumberOrNull(subtotales.diva10) ?? 0,
          dLiqTotIVA5: this.toNumberOrNull(subtotales.dliqtotiva5) ?? 0,
          dLiqTotIVA10: this.toNumberOrNull(subtotales.dliqtotiva10) ?? 0,
          dIVAComi: this.toNumberOrNull(subtotales.divacomi) ?? 0,
          dTotIVA: this.toNumberOrNull(subtotales.dtotiva) ?? 0,
          dBaseGrav5: this.toNumberOrNull(subtotales.dbasegrav5) ?? 0,
          dBaseGrav10: this.toNumberOrNull(subtotales.dbasegrav10) ?? 0,
          dTBasGraIVA: this.toNumberOrNull(subtotales.dtbasgraiva) ?? 0,
          dTotalGs: this.toNumberOrNull(subtotales.dtotalgs) ?? 0,
        },
      ];
    }

    if (factura.factura_forma_pagos.length > 0) {
      payload.FormaPago = factura.factura_forma_pagos.map((pago) => ({
        iTiPago: this.toNumberOrNull(pago.medio_pago?.codigo) ?? null,
        dDesTiPag: pago.medio_pago?.descripcion ?? null,
        iForProPa: this.toNumberOrNull(pago.forma_procesamiento_pago?.codigo) ?? null,
        dMonTiPag: this.toNumberOrNull(pago.dmontipag) ?? 0,
        cMoneTiPag: pago.cmonetipag ?? factura.moneda?.codigo ?? null,
        dTiCamTiPag: this.toNumberOrNull(pago.dticamtipag) ?? this.toNumberOrNull(factura.dticam),
        iDenTarj: this.toNumberOrNull(pago.tarjeta?.codigo) ?? null,
        dDesDenTarj: pago.ddesdentarj ?? null,
        dRSProTar: pago.drsprotar ?? null,
        dRUCProTar: pago.drucprotar ?? null,
        dDVProTar: pago.ddvprotar ? this.toNumberOrNull(pago.ddvprotar) : null,
        dCodAuOpe: pago.dcodauope ? this.toNumberOrNull(pago.dcodauope) : null,
        dNomTit: pago.dnomtit ?? null,
        dNumTarj: pago.dnumtarj ?? null,
        dNumCheq: pago.dnumcheq ?? null,
        dBcoEmi: pago.dbcoemi ?? null,
      }));
    }

    if (factura.factura_cuotas.length > 0) {
      payload.Cuotas = factura.factura_cuotas.map((cuota) => ({
        cMoneCuo: cuota.cmonecuo,
        dMonCuota: this.toNumberOrNull(cuota.dmoncuota) ?? 0,
        dVencCuo: cuota.dvenccuo.toISOString().slice(0, 10),
      }));
    }

    if (factura.factura_det.length > 0) {
      payload.Detalles = factura.factura_det.map((item) => {
        const prod = item.productos;
        return {
          dCodInt: prod.cod_producto ?? prod.id?.replace(/-/g, '').slice(0, 5) ?? null,
          dDncpG: prod.ddncpg ?? null,
          dDncpE: prod.ddncpe ?? null,
          dParAranc: null,
          dNCM: null,
          dGtin: prod.codigo_barra ?? null,
          dGtinPq: null,
          dDesProSer: item.ddesproser,
          cUniMed: prod.unidades_de_medida?.codigo ?? item.cunimed,
          dDesUniMed: prod.unidades_de_medida?.representacion ?? null,
          dCantProSer: this.toNumberOrNull(item.dcantproser) ?? 0,
          cPaisOrig: item.cpaisorig ?? null,
          dInfItem: item.dinfitem ?? null,
          cRelMerc: null,
          dCanQuiMer: null,
          dPorQuiMer: null,
          dCDCAnticipo: item.dcdcanticipo ?? null,
          dPUniProSer: this.toNumberOrNull(item.duniproser) ?? 0,
          dTiCamIt: this.toNumberOrNull(item.dticamit) ?? null,
          dTotBruOpeItem: this.toNumberOrNull(item.dtotbruopeitem) ?? 0,
          dDescItem: this.toNumberOrNull(item.ddescitem) ?? 0,
          dPorcDesIt: this.toNumberOrNull(item.dporcdesit) ?? 0,
          dDescGloItem: this.toNumberOrNull(item.ddescgloitem) ?? 0,
          dAntPreUniIt: this.toNumberOrNull(item.dantpreuniit) ?? 0,
          dAntGloPreUniIt: this.toNumberOrNull(item.dantglopreuniit) ?? 0,
          dTotOpeItem: this.toNumberOrNull(item.dtotopeitem) ?? 0,
          dTotOpeGs: this.toNumberOrNull(item.dtotopegs) ?? null,
          iAfecIVA: this.toNumberOrNull(prod.afectacion_iva?.codigo) ?? this.toNumberOrNull(item.iafeciva) ?? null,
          dPropIVA: this.toNumberOrNull(item.dpropiva) ?? null,
          dTasaIVA: this.toNumberOrNull(item.dtasiva) ?? null,
          dBasGravIVA: this.toNumberOrNull(item.dbasgraviva) ?? null,
          dLiqIVAItem: this.toNumberOrNull(item.dliqivaitem) ?? null,
          gRasMerc: null,
          gVehNuevo: null,
          dBasExe: this.toNumberOrNull(item.dbasexe) ?? null,
        };
      });
    }

    // Documentos asociados: remisiones electrónicas que esta factura consolida.
    // Cada remisión aporta un DocumentoAsociado electrónico (iTipDocAso='1') con
    // su CDC (dCdCDERef). Se deduplica por CDC.
    const documentosAsociados: Record<string, unknown>[] = [];
    const cdcsIncluidos = new Set<string>();
    for (const nrf of factura.nota_remision_factura ?? []) {
      const cdc = nrf.nota_remision_cab?.cdc;
      if (cdc && !cdcsIncluidos.has(cdc)) {
        cdcsIncluidos.add(cdc);
        documentosAsociados.push({ iTipDocAso: 1, dCdCDERef: cdc });
      }
    }
    if (documentosAsociados.length > 0) {
      payload.DocumentosAsociados = documentosAsociados;
    }

    return payload;
  }

  async buildBatchPayload(facturaIds: string[], empresaId: string): Promise<Record<string, unknown>> {
    const comprobantes: Record<string, unknown>[] = [];
    const fallidas: string[] = [];

    for (const facturaId of facturaIds) {
      try {
        const payload = await this.buildFacturaPayload(facturaId, empresaId);
        comprobantes.push(payload);
      } catch (error) {
        // NO descartar en silencio: registrar el fallo y acumular el id para alertar.
        const m = error instanceof Error ? error.message : String(error);
        this.logger.error(`Error armando payload SIFEN para factura ${facturaId}: ${m}`);
        fallidas.push(facturaId);
      }
    }

    if (fallidas.length > 0) {
      this.logger.warn(
        `Lote SIFEN: ${fallidas.length}/${facturaIds.length} factura(s) excluida(s) por error de payload: ${fallidas.join(', ')}`,
      );
    }
    // Si TODAS fallaron, no enviar un lote vacío en silencio: fallar explícitamente.
    if (comprobantes.length === 0 && facturaIds.length > 0) {
      throw new BadRequestException('No se pudo construir el payload de ninguna factura del lote SIFEN');
    }

    return {
      nro_lote: `facvoice-batch-${Date.now()}`,
      tipOpe: '1',
      iTiDE: '1',
      comprobantes,
      facturas_excluidas: fallidas,
    };
  }
}
