import { BadRequestException, ForbiddenException, Injectable, Logger, NotFoundException } from '@nestjs/common';
import { presupuesto_estado } from '@prisma/client';
import { CreateOrdenVentaDto } from 'src/pedidos/dto/create-orden-venta.dto';
import { PedidosService } from 'src/pedidos/pedidos.service';
import { PrismaService } from 'src/prisma/prisma.service';

/** Tipos de línea que NO se convierten (informativas). */
const TIPOS_INFORMATIVOS = ['SUBTITULO', 'TEXTO_LIBRE', 'DESCUENTO_GLOBAL'];
const PRESUPUESTO_ESTADO_FACTURADO = 'FACTURADO' as unknown as presupuesto_estado;

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

  constructor(
    private readonly prisma: PrismaService,
    private readonly pedidosService: PedidosService,
  ) {}

  /** Presupuestos ACEPTADOS aún no convertidos (para el selector en Orden de Venta). */
  async getAceptados(empresa_id: string, cliente_id?: string) {
    const items = await this.prisma.presupuesto_cab.findMany({
      where: {
        empresa_id,
        deleted: false,
        estado: presupuesto_estado.ACCEPTED,
        pedido_id: null,
        ...(cliente_id ? { cliente_id } : {}),
      },
      orderBy: { fecha_respuesta_cliente: 'desc' },
      select: {
        id: true,
        numero: true,
        version: true,
        titulo: true,
        total: true,
        fecha_emision: true,
        fecha_respuesta_cliente: true,
        moneda: { select: { codigo: true, simbolo: true } },
        clientes: { include: { personas: { select: { razon_social: true } } } },
        _count: { select: { items: true } },
      },
    });
    return items.map((p) => ({
      id: p.id,
      numero: p.numero,
      version: p.version,
      titulo: p.titulo,
      total: Number(p.total),
      moneda: p.moneda?.codigo ?? 'PYG',
      simbolo: p.moneda?.simbolo ?? '₲',
      fecha_emision: p.fecha_emision,
      cliente: p.clientes?.personas?.razon_social ?? null,
      items: p._count.items,
    }));
  }

  /**
   * Convierte un presupuesto ACEPTADO en una Orden de Venta confirmada.
   * Sólo se copian las líneas con `producto_id`; las informativas se ignoran.
   * El precio del presupuesto (IVA incluido) se mapea a `precio_negociado`.
   */
  async convertToOrder(id: string, empresa_id: string, user_id: string) {
    const presupuesto = await this.prisma.presupuesto_cab.findFirst({
      where: { id, empresa_id, deleted: false },
      include: { items: { orderBy: { orden: 'asc' } } },
    });
    if (!presupuesto) throw new NotFoundException('Presupuesto no encontrado');
    if (
      presupuesto.estado === presupuesto_estado.CONVERTED ||
      presupuesto.estado === PRESUPUESTO_ESTADO_FACTURADO ||
      presupuesto.pedido_id
    ) {
      throw new BadRequestException('Este presupuesto ya fue convertido');
    }
    if (presupuesto.estado !== presupuesto_estado.ACCEPTED) {
      throw new BadRequestException('Sólo se pueden convertir presupuestos en estado Aceptado');
    }

    const convertibles = presupuesto.items.filter((it) => it.producto_id);
    if (convertibles.length === 0) {
      throw new BadRequestException('El presupuesto no tiene líneas con producto para convertir en Orden de Venta');
    }

    // Líneas facturables (no informativas) sin producto → no se copian, se advierte.
    const omitidasSinProducto = presupuesto.items.filter(
      (it) => !it.producto_id && !TIPOS_INFORMATIVOS.includes(it.tipo),
    );
    const warnings: string[] = [];
    if (omitidasSinProducto.length > 0) {
      warnings.push(
        `${omitidasSinProducto.length} línea(s) sin producto no se copiaron a la Orden de Venta (sólo informativas o sin producto asociado).`,
      );
    }

    const dto: CreateOrdenVentaDto = {
      cliente_id: presupuesto.cliente_id,
      // La sucursal del presupuesto tiene que viajar a la orden: sin esto la OV
      // nacía sin sucursal (los dos pedidos que existían quedaron en NULL) y
      // después no aparecía en los listados filtrados por sucursal.
      sucursal_id: presupuesto.sucursal_id ?? undefined,
      vendedor_id: presupuesto.vendedor_id ?? undefined,
      moneda_id: presupuesto.moneda_id ?? undefined,
      tipo_cambio: presupuesto.tipo_cambio_usd ? Number(presupuesto.tipo_cambio_usd) : undefined,
      condicion_pago_id: presupuesto.condicion_pago_id ?? undefined,
      // El descuento global se mantiene separado (no se pliega): la OV lo aplica sobre el
      // NETO de cada ítem (recalcularTotales), igual que el POS y el presupuesto, así los
      // totales coinciden y el desglose ítem/global se ve por separado.
      descuento_global_porcentaje: Number(presupuesto.descuento_global_pct ?? 0) || 0,
      observaciones: presupuesto.observaciones_internas ?? undefined,
      referencia_interna: `Presupuesto ${presupuesto.numero}`,
      tipo_facturacion: 'total',
      items: convertibles.map((it) => {
        // La OV maneja el descuento del ítem como % sobre el bruto. Convertimos el descuento
        // propio del ítem (sea % o monto, ya reflejado en subtotal_con_iva) a su % equivalente.
        const bruto = (Number(it.precio_unitario) || 0) * (Number(it.cantidad) || 0);
        const netoItem = Number(it.subtotal_con_iva) || 0; // bruto − descuento del ítem
        const descItemPct =
          bruto > 0 ? Math.min(Math.max((1 - netoItem / bruto) * 100, 0), 100) : 0;
        return {
          producto_id: it.producto_id,
          cantidad: Number(it.cantidad),
          precio_negociado: Number(it.precio_unitario),
          descuento_porcentaje: descItemPct,
          observacion: it.notas_item ?? undefined,
        };
      }),
    };

    // 1) Crear la OV en borrador (delegando al módulo de pedidos)
    const creada = await this.pedidosService.crearOrden(dto, empresa_id, user_id, {
      tipo: 'presupuesto',
      id,
    });
    const ordenId = creada?.data?.id;
    if (!ordenId) throw new BadRequestException('No se pudo crear la Orden de Venta');

    // 2) Confirmarla (queda CONFIRMADA/APROBADA, bloqueada). Si falla, limpiar la OV huérfana.
    try {
      await this.pedidosService.confirmarOrden(ordenId, empresa_id, user_id);
    } catch (error) {
      await this.prisma.pedidos.delete({ where: { id: ordenId } }).catch(() => undefined);
      const detalle = error instanceof Error ? error.message : 'Error desconocido';
      this.logger.error(`Conversión fallida al confirmar OV del presupuesto ${presupuesto.numero}: ${detalle}`);
      throw new BadRequestException(`No se pudo confirmar la Orden de Venta: ${detalle}`);
    }

    // 3) Enlazar ambos lados y marcar el presupuesto CONVERTED
    const orden = await this.prisma.pedidos.findUnique({
      where: { id: ordenId },
      select: { numero_pedido: true, estado_orden: true },
    });

    await this.prisma.$transaction(async (tx) => {
      await tx.presupuesto_cab.update({
        where: { id },
        data: {
          estado: presupuesto_estado.CONVERTED,
          pedido_id: ordenId,
          fecha_conversion: new Date(),
          usuario_conversion_id: user_id,
          token_acceso_publico: null,
          updated_at: new Date(),
        },
      });
      await tx.presupuesto_historial.create({
        data: {
          presupuesto_id: id,
          estado_anterior: presupuesto_estado.ACCEPTED,
          estado_nuevo: presupuesto_estado.CONVERTED,
          usuario_id: user_id,
          comentario: `Convertido en Orden de Venta ${orden?.numero_pedido ?? ordenId}`,
        },
      });
    });

    return {
      message: 'Presupuesto convertido en Orden de Venta',
      pedido_id: ordenId,
      numero_pedido: orden?.numero_pedido ?? null,
      estado_orden: orden?.estado_orden ?? null,
      warnings,
    };
  }

  /**
   * Factura un presupuesto ACEPTADO directamente, sin Orden de Venta intermedia.
   * Requiere que la empresa tenga habilitado `presupuesto_facturar_sin_ov`: por
   * defecto el circuito sigue exigiendo la OV. La factura ya fue emitida por el
   * POS; acá sólo se enlaza y se cierra el presupuesto.
   */
  async facturarDirecto(id: string, empresa_id: string, user_id: string, factura_id: string) {
    const empresa = await this.prisma.empresas.findUnique({
      where: { id: empresa_id },
      select: { presupuesto_facturar_sin_ov: true },
    });
    if (!empresa?.presupuesto_facturar_sin_ov) {
      throw new ForbiddenException(
        'La facturación directa de presupuestos no está habilitada. Activala en Órdenes de Venta → Configuración Global, o convertí el presupuesto en Orden de Venta.',
      );
    }

    const presupuesto = await this.prisma.presupuesto_cab.findFirst({
      where: { id, empresa_id, deleted: false },
      select: { id: true, estado: true, numero: true, pedido_id: true },
    });
    if (!presupuesto) throw new NotFoundException('Presupuesto no encontrado');

    if (
      presupuesto.estado === presupuesto_estado.CONVERTED ||
      presupuesto.estado === PRESUPUESTO_ESTADO_FACTURADO ||
      presupuesto.pedido_id
    ) {
      throw new BadRequestException('Este presupuesto ya fue convertido o facturado');
    }
    if (presupuesto.estado !== presupuesto_estado.ACCEPTED) {
      throw new BadRequestException('Sólo se pueden facturar presupuestos en estado Aceptado');
    }

    const factura = await this.prisma.factura_cab.findFirst({
      where: { id: factura_id, empresa_id },
      select: { id: true, dest: true, dpunexp: true, dnumdoc: true },
    });
    if (!factura) throw new NotFoundException('Factura no encontrada');
    const facturaNumero = `${factura.dest}-${factura.dpunexp}-${factura.dnumdoc}`;

    await this.prisma.$transaction(async (tx) => {
      await tx.presupuesto_cab.update({
        where: { id },
        data: {
          estado: PRESUPUESTO_ESTADO_FACTURADO,
          factura_id,
          fecha_conversion: new Date(),
          usuario_conversion_id: user_id,
          token_acceso_publico: null,
          updated_at: new Date(),
        },
      });
      // El enlace va en los dos sentidos. Con sólo `presupuesto_cab.factura_id`
      // desde la factura no había forma de saber de qué presupuesto salió sin
      // recorrer toda la tabla de presupuestos al revés.
      await tx.factura_cab.update({
        where: { id: factura_id },
        data: { presupuesto_id: id },
      });
      await tx.presupuesto_historial.create({
        data: {
          presupuesto_id: id,
          estado_anterior: presupuesto_estado.ACCEPTED,
          estado_nuevo: PRESUPUESTO_ESTADO_FACTURADO,
          usuario_id: user_id,
          comentario: `Facturado directamente (sin Orden de Venta) · Factura ${facturaNumero}`,
        },
      });
    });

    return {
      message: 'Presupuesto facturado',
      factura_id,
      factura_numero: facturaNumero,
    };
  }

}
