import { Processor, WorkerHost } from '@nestjs/bullmq';
import { Logger } from '@nestjs/common';
import { Job } from 'bullmq';
import { PrismaService } from 'src/prisma/prisma.service';
import { GastosService } from '../gastos.service';
import { GASTOS_IMPORT_QUEUE } from './gastos-importador.service';
import { buildObservacionAnticipoHistorico, construirFilaEstructurada } from './normalizadores/libro-compras-set.normalizador';
import { ProveedorResolverService } from './services/proveedor-resolver.service';

interface GastosImportJobData {
  jobId: string;
}

interface GastoItemPayload {
  tipo_gasto_id?: string;
  descripcion: string;
  cantidad: number;
  precio_unitario: number;
  porcentaje_iva: number;
}

@Processor(GASTOS_IMPORT_QUEUE)
export class GastosImportadorProcessor extends WorkerHost {
  private readonly logger = new Logger(GastosImportadorProcessor.name);

  constructor(
    private readonly prisma: PrismaService,
    private readonly gastosService: GastosService,
    private readonly proveedorResolver: ProveedorResolverService,
  ) {
    super();
  }

  async process(job: Job<GastosImportJobData>): Promise<void> {
    const { jobId } = job.data;
    const importJob = await this.prisma.gasto_importacion_jobs.findUnique({ where: { id: jobId } });
    if (!importJob) {
      this.logger.warn(`Job de importación ${jobId} no existe, descarto`);
      return;
    }

    const filas = await this.prisma.gasto_importacion_filas.findMany({
      where: { job_id: jobId, estado: 'valida' },
      orderBy: { fila_numero: 'asc' },
    });

    const mapeoColumnas = (importJob.mapeo_columnas as Record<string, string | null>) ?? {};
    const configDefaults = (importJob.config_defaults as { sucursal_id?: string } | null) ?? null;

    let importadas = 0;
    let conError = 0;

    for (const fila of filas) {
      try {
        if (!fila.ruc && !fila.razon_social) {
          throw new Error('Fila sin RUC ni razón social, no se puede resolver proveedor');
        }

        const estructurada = construirFilaEstructurada(fila.raw as Record<string, unknown>, mapeoColumnas);
        const { tipoEntidad, esExtranjero } = this.proveedorResolver.resolverTipoEntidad(estructurada.contrapartidaPasiva);
        const { proveedorId, creado } = await this.proveedorResolver.findOrCreateProveedor(
          importJob.empresa_id,
          fila.ruc ?? '',
          fila.razon_social ?? '',
          { tipoEntidad, esExtranjero, origen: 'importación de Gastos desde Excel' },
        );

        const descripcionPartes = [`Importado desde Libro de Compras SET (${importJob.nombre_archivo})`];
        if (estructurada.esAnticipoViatico) descripcionPartes.push(buildObservacionAnticipoHistorico());

        const gasto = await this.gastosService.create(
          {
            cabecera: {
              proveedor_id: proveedorId,
              sucursal_id: configDefaults?.sucursal_id,
              // No se envía `ruc`: el proveedor ya se resolvió de forma determinística por RUC en
              // ProveedorResolverService. Reenviarlo dispara validateProveedorRefAndRuc en
              // GastosService, que compara contra el RUC+DV compuesto del proveedor — si el
              // proveedor ya existía en el sistema con DV real (caso común al reimportar contra
              // datos ya cargados), el RUC plano del Libro de Compras (sin DV) no matchea y la
              // fila se rechaza aunque el proveedor sea el correcto.
              timbrado: fila.timbrado ?? undefined,
              establecimiento: fila.establecimiento ?? undefined,
              punto_expedicion: fila.punto_expedicion ?? undefined,
              numero_factura: fila.numero_factura ?? undefined,
              fecha_emision: fila.fecha_emision ? fila.fecha_emision.toISOString().slice(0, 10) : undefined,
              forma_pago: (fila.forma_pago as 'CONTADO' | 'CREDITO') ?? 'CONTADO',
              descripcion: descripcionPartes.join(' — '),
            },
            items: this.construirItems(fila),
          },
          importJob.empresa_id,
          importJob.usuario_id,
        );

        await this.prisma.gasto_importacion_filas.update({
          where: { id: fila.id },
          data: {
            estado: 'importada',
            gasto_id: gasto.id,
            proveedor_id: proveedorId,
            proveedor_es_nuevo: creado,
            errores: undefined,
          },
        });
        importadas++;
      } catch (error) {
        const mensaje = error instanceof Error ? error.message : 'Error desconocido al crear el gasto';
        this.logger.error(`Fila ${fila.fila_numero} del job ${jobId} falló: ${mensaje}`);
        await this.prisma.gasto_importacion_filas.update({
          where: { id: fila.id },
          data: { estado: 'error', errores: [mensaje] },
        });
        conError++;
      }
    }

    await this.prisma.gasto_importacion_jobs.update({
      where: { id: jobId },
      data: {
        estado: conError > 0 ? 'completado_con_errores' : 'completado',
        filas_importadas: importadas,
        finished_at: new Date(),
      },
    });
  }

  /** Una fila del Libro de Compras puede tener montos a la vez en 10%, 5% y exento — genera un ítem por cada tramo no nulo. */
  private construirItems(fila: {
    tipo_gasto_id: string | null;
    tipo_gasto_texto: string | null;
    monto_gravado_10: unknown;
    monto_gravado_5: unknown;
    monto_no_gravado: unknown;
    total_comprobante: unknown;
  }): GastoItemPayload[] {
    const descripcion = fila.tipo_gasto_texto || 'Gasto importado';
    const items: GastoItemPayload[] = [];
    const agregar = (monto: number, iva: number) => {
      if (monto > 0) {
        items.push({ tipo_gasto_id: fila.tipo_gasto_id ?? undefined, descripcion, cantidad: 1, precio_unitario: monto, porcentaje_iva: iva });
      }
    };
    agregar(Number(fila.monto_gravado_10 ?? 0), 10);
    agregar(Number(fila.monto_gravado_5 ?? 0), 5);
    agregar(Number(fila.monto_no_gravado ?? 0), 0);
    if (!items.length) {
      items.push({
        tipo_gasto_id: fila.tipo_gasto_id ?? undefined,
        descripcion,
        cantidad: 1,
        precio_unitario: Number(fila.total_comprobante ?? 0),
        porcentaje_iva: 0,
      });
    }
    return items;
  }
}
