import { BadRequestException, ForbiddenException, Injectable, Logger } from '@nestjs/common';
import { PrismaService } from '../../prisma/prisma.service';
import { WebhookService } from '../webhooks/webhook.service';
import {
  NotificarCobroDto,
  RegistrarGestionExternaDto,
  SyncCarteraDto,
} from './api-publica.dto';
import type { ApiKeyContext } from './api-key.guard';

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

  constructor(
    private readonly prisma: PrismaService,
    private readonly webhooks: WebhookService,
  ) {}

  // ─── /sync ───────────────────────────────────────────────────────────────

  async sync(ctx: ApiKeyContext, dto: SyncCarteraDto) {
    this.requireScope(ctx, 'sync');
    const modo = dto.modo ?? 'delta';
    const empresa_id = ctx.empresa_id;

    let creados = 0;
    let actualizados = 0;
    let descartados = 0;

    for (const d of dto.documentos) {
      if (!d.cliente_externo_id || !d.documento_numero) {
        descartados++;
        continue;
      }
      try {
        const existing = await this.prisma.cartera_importada_documentos.findUnique({
          where: {
            empresa_id_cliente_externo_id_documento_numero: {
              empresa_id,
              cliente_externo_id: d.cliente_externo_id,
              documento_numero: d.documento_numero,
            },
          },
        });
        await this.prisma.cartera_importada_documentos.upsert({
          where: {
            empresa_id_cliente_externo_id_documento_numero: {
              empresa_id,
              cliente_externo_id: d.cliente_externo_id,
              documento_numero: d.documento_numero,
            },
          },
          create: {
            empresa_id,
            cliente_externo_id: d.cliente_externo_id,
            cliente_nombre: d.cliente_nombre,
            cliente_documento: d.cliente_documento ?? null,
            cliente_telefono: d.cliente_telefono ?? null,
            cliente_email: d.cliente_email ?? null,
            documento_numero: d.documento_numero,
            documento_tipo: d.documento_tipo ?? 'factura',
            moneda_codigo: (d.moneda_codigo ?? 'PYG').toUpperCase().slice(0, 3),
            monto_total: d.monto_total,
            saldo_pendiente: d.saldo_pendiente,
            fecha_emision: d.fecha_emision,
            fecha_vencimiento: d.fecha_vencimiento ?? null,
            active: true,
          },
          update: {
            cliente_nombre: d.cliente_nombre,
            cliente_documento: d.cliente_documento ?? undefined,
            cliente_telefono: d.cliente_telefono ?? undefined,
            cliente_email: d.cliente_email ?? undefined,
            documento_tipo: d.documento_tipo ?? undefined,
            moneda_codigo: d.moneda_codigo
              ? d.moneda_codigo.toUpperCase().slice(0, 3)
              : undefined,
            monto_total: d.monto_total,
            saldo_pendiente: d.saldo_pendiente,
            fecha_emision: d.fecha_emision,
            fecha_vencimiento: d.fecha_vencimiento ?? null,
            active: true,
          },
        });
        if (existing) actualizados++;
        else creados++;
      } catch (err: any) {
        this.logger.warn(`Sync falló doc ${d.documento_numero}: ${err?.message ?? err}`);
        descartados++;
      }
    }

    // Modo snapshot: marcar como inactivos los documentos no incluidos.
    let desactivados = 0;
    if (modo === 'snapshot') {
      const ids = dto.documentos.map((d) => `${d.cliente_externo_id}::${d.documento_numero}`);
      const res = await this.prisma.cartera_importada_documentos.updateMany({
        where: { empresa_id, active: true },
        data: {},
      });
      // Volver a iterar y marcar los que no están en el snapshot.
      const todos = await this.prisma.cartera_importada_documentos.findMany({
        where: { empresa_id, active: true },
        select: { id: true, cliente_externo_id: true, documento_numero: true },
      });
      const aDesactivar = todos
        .filter((t) => !ids.includes(`${t.cliente_externo_id}::${t.documento_numero}`))
        .map((t) => t.id);
      if (aDesactivar.length > 0) {
        const r = await this.prisma.cartera_importada_documentos.updateMany({
          where: { id: { in: aDesactivar } },
          data: { active: false },
        });
        desactivados = r.count;
      }
      void res;
    }

    return {
      modo,
      total_enviados: dto.documentos.length,
      creados,
      actualizados,
      descartados,
      desactivados,
    };
  }

  // ─── /cobros ─────────────────────────────────────────────────────────────

  async notificarCobro(ctx: ApiKeyContext, dto: NotificarCobroDto) {
    this.requireScope(ctx, 'cobros');
    const empresa_id = ctx.empresa_id;

    // Restar del saldo pendiente del documento si existe.
    const doc = await this.prisma.cartera_importada_documentos.findFirst({
      where: {
        empresa_id,
        cliente_externo_id: dto.cliente_externo_id,
        documento_numero: dto.documento_numero,
      },
    });
    if (doc) {
      const nuevoSaldo = Math.max(0, Number(doc.saldo_pendiente) - dto.monto);
      await this.prisma.cartera_importada_documentos.update({
        where: { id: doc.id },
        data: { saldo_pendiente: nuevoSaldo, active: nuevoSaldo > 0 },
      });
    }

    void this.webhooks
      .publicar(empresa_id, 'cobro.registrado', {
        origen: 'api-externa',
        cliente_id: dto.cliente_externo_id,
        documento_numero: dto.documento_numero,
        monto: dto.monto,
        moneda: dto.moneda_codigo ?? 'PYG',
        fecha: dto.fecha,
        medio_pago: dto.medio_pago ?? null,
        referencia_externa: dto.referencia_externa ?? null,
        metadata: dto.metadata ?? {},
      })
      .catch(() => {});

    return {
      ok: true,
      documento_encontrado: !!doc,
      saldo_actualizado: doc ? Math.max(0, Number(doc.saldo_pendiente) - dto.monto) : null,
    };
  }

  // ─── /gestiones ──────────────────────────────────────────────────────────

  async registrarGestion(ctx: ApiKeyContext, dto: RegistrarGestionExternaDto) {
    this.requireScope(ctx, 'gestiones');
    const empresa_id = ctx.empresa_id;

    let documentoId: string | null = null;
    if (dto.documento_numero) {
      const doc = await this.prisma.cartera_importada_documentos.findFirst({
        where: {
          empresa_id,
          cliente_externo_id: dto.cliente_externo_id,
          documento_numero: dto.documento_numero,
        },
        select: { id: true },
      });
      documentoId = doc?.id ?? null;
    }
    if (!documentoId) {
      // Tomamos el primer documento del cliente como contexto.
      const any = await this.prisma.cartera_importada_documentos.findFirst({
        where: { empresa_id, cliente_externo_id: dto.cliente_externo_id },
        select: { id: true },
      });
      documentoId = any?.id ?? null;
    }
    if (!documentoId) {
      throw new BadRequestException(
        `No existe documento previo para cliente_externo_id=${dto.cliente_externo_id}. Hacé sync primero.`,
      );
    }

    const gestion = await this.prisma.cob_gestion_cartera.create({
      data: {
        empresa_id,
        cartera_importada_documento_id: documentoId,
        cliente_externo_id: dto.cliente_externo_id,
        usuario_id: '00000000-0000-0000-0000-000000000000', // sentinel api-externa
        tipo_gestion: dto.tipo_gestion,
        resultado: dto.resultado,
        observacion: dto.observacion ?? null,
        proxima_accion: dto.proxima_accion ?? null,
        proxima_fecha: dto.proxima_fecha ?? null,
      },
    });

    void this.webhooks
      .publicar(empresa_id, 'gestion.registrada', {
        origen: 'api-externa',
        gestion_id: gestion.id,
        cliente_id: dto.cliente_externo_id,
        documento_numero: dto.documento_numero ?? null,
        tipo_gestion: dto.tipo_gestion,
        resultado: dto.resultado,
        observacion: dto.observacion ?? null,
        proxima_accion: dto.proxima_accion ?? null,
        proxima_fecha: dto.proxima_fecha ?? null,
        fecha: gestion.fecha,
      })
      .catch(() => {});

    return { ok: true, gestion_id: gestion.id };
  }

  private requireScope(ctx: ApiKeyContext, scope: string) {
    if (!ctx.scopes?.includes(scope)) {
      throw new ForbiddenException(
        `La API key no tiene scope "${scope}". Scopes actuales: ${(ctx.scopes ?? []).join(', ')}`,
      );
    }
  }
}
