import { BadRequestException, Injectable, NotFoundException, Logger } from '@nestjs/common';
import { AuditService } from 'src/audit/audit.service';
import { PrismaService } from 'src/prisma/prisma.service';
import { formatDateResponse, formatDateTimeResponse } from 'src/utils/utilidades';
import { CreateOfertaDto } from './dto/create-oferta.dto';
import { EvaluarOfertasDto } from './dto/evaluar-ofertas.dto';
import { UpdateOfertaDto } from './dto/update-oferta.dto';

@Injectable()
export class OfertasService {
  private readonly logger = new Logger(OfertasService.name);
  constructor(
    private prisma: PrismaService,
    private readonly auditService: AuditService,
  ) {}

  // ==================== CRUD ====================

  async create(dto: CreateOfertaDto, empresaId: string, userId: string) {
    const { aplicaciones, sucursal_ids, acumulable_lista_precios, ...ofertaData } = dto;

    if (new Date(ofertaData.fecha_fin) <= new Date(ofertaData.fecha_inicio)) {
      throw new BadRequestException('La fecha de fin debe ser posterior a la fecha de inicio');
    }

    if ((ofertaData.tipo as string) === 'descuento_porcentaje' && (ofertaData.valor <= 0 || ofertaData.valor > 100)) {
      throw new BadRequestException('El porcentaje de descuento debe estar entre 0 y 100');
    }

    if (ofertaData.valor <= 0) {
      throw new BadRequestException('El valor debe ser mayor a 0');
    }

    // Validar que si aplica a producto/categoria/marca, tenga referencia_id
    if (aplicaciones?.length) {
      for (const ap of aplicaciones) {
        if ((ap.aplica_a as string) !== 'todos' && !ap.referencia_id) {
          throw new BadRequestException(`Debe proporcionar referencia_id cuando aplica_a = '${ap.aplica_a}'`);
        }
      }
    }

    const oferta = await this.prisma.ofertas.create({
      data: {
        empresa_id: empresaId,
        created_by: userId,
        nombre: ofertaData.nombre,
        descripcion: ofertaData.descripcion,
        tipo: ofertaData.tipo,
        valor: ofertaData.valor,
        fecha_inicio: new Date(ofertaData.fecha_inicio),
        fecha_fin: new Date(ofertaData.fecha_fin),
        prioridad: ofertaData.prioridad ?? 0,
        acumulable_lista_precios: acumulable_lista_precios ?? true,
        oferta_aplicacion: {
          create: aplicaciones?.map((ap) => ({
            aplica_a: ap.aplica_a,
            referencia_id: ap.referencia_id || null,
            excluir: ap.excluir ?? false,
          })) || [{ aplica_a: 'todos', referencia_id: null, excluir: false }],
        },
        oferta_sucursales: sucursal_ids?.length
          ? {
              create: sucursal_ids.map((sid) => ({
                sucursal_id: sid,
              })),
            }
          : undefined,
      },
      include: {
        oferta_aplicacion: true,
        oferta_sucursales: {
          include: { empresas_sucursales: { select: { descripcion: true } } },
        },
      },
    });

    this.logger.log(`Oferta creada: ${oferta.nombre} (${oferta.id})`, 'OfertasService');
    await this.auditService.log({
      empresa_id: empresaId,
      user_id: userId,
      action: 'CREATE',
      entity_type: 'oferta',
      entity_id: oferta.id,
      descripcion: `Oferta creada: ${oferta.nombre} | Tipo: ${oferta.tipo} | Valor: ${oferta.valor}`,
      new_value: {
        nombre: oferta.nombre,
        tipo: oferta.tipo,
        valor: Number(oferta.valor),
        fecha_inicio: oferta.fecha_inicio,
        fecha_fin: oferta.fecha_fin,
      },
    });
    return oferta;
  }

  async findAll(
    empresaId: string,
    query?: {
      active?: string;
      tipo?: string;
      q?: string;
      page?: string;
      limit?: string;
    },
  ) {
    const take = Math.max(1, Math.min(100, Number(query?.limit) || 10));
    const currentPage = Math.max(1, Number(query?.page) || 1);
    const skip = (currentPage - 1) * take;

    const where: any = { empresa_id: empresaId };

    if (query?.active !== undefined && query.active !== '') {
      where.active = query.active === 'true';
    }
    if (query?.tipo) {
      where.tipo = query.tipo;
    }
    if (query?.q && query.q.trim().length > 0) {
      const search = query.q.trim();
      where.OR = [
        { nombre: { contains: search, mode: 'insensitive' } },
        { descripcion: { contains: search, mode: 'insensitive' } },
      ];
    }

    const include = {
      oferta_aplicacion: true,
      oferta_sucursales: {
        include: {
          empresas_sucursales: { select: { id: true, descripcion: true } },
        },
      },
      _count: { select: { oferta_usos: true } },
    };

    const [items, total] = await this.prisma.$transaction([
      this.prisma.ofertas.findMany({
        where,
        include,
        orderBy: [{ active: 'desc' }, { prioridad: 'desc' }, { created_at: 'desc' }],
        skip,
        take,
      }),
      this.prisma.ofertas.count({ where }),
    ]);

    return {
      items: items.map((oferta) => ({
        ...oferta,
        created_at: formatDateResponse(oferta.created_at),
        fecha_inicio: formatDateTimeResponse(oferta.fecha_inicio),
        fecha_fin: formatDateTimeResponse(oferta.fecha_fin),
        updated_at: formatDateTimeResponse(oferta.updated_at),
      })),
      total,
      page: currentPage,
      limit: take,
      lastPage: Math.max(1, Math.ceil(total / take) || 1),
    };
  }

  async findOne(id: string) {
    const oferta = await this.prisma.ofertas.findUnique({
      where: { id },
      include: {
        oferta_aplicacion: true,
        oferta_sucursales: {
          include: {
            empresas_sucursales: { select: { id: true, descripcion: true } },
          },
        },
        oferta_usos: {
          take: 20,
          orderBy: { created_at: 'desc' },
          include: {
            factura_cab: {
              select: {
                id: true,
                dnumdoc: true,
                dest: true,
                dpunexp: true,
                dfeemide: true,
              },
            },
            clientes: {
              select: {
                id: true,
                personas: { select: { razon_social: true } },
              },
            },
          },
        },
        _count: { select: { oferta_usos: true } },
      },
    });

    if (!oferta) {
      throw new NotFoundException('Oferta no encontrada');
    }

    return oferta;
  }

  async update(id: string, dto: UpdateOfertaDto, user_id?: string) {
    const oferta = await this.prisma.ofertas.findUnique({ where: { id } });
    if (!oferta) {
      throw new NotFoundException('Oferta no encontrada');
    }

    const { aplicaciones, sucursal_ids, acumulable_lista_precios, ...updateData } = dto;

    // Validaciones
    const fechaInicio = updateData.fecha_inicio ? new Date(updateData.fecha_inicio) : oferta.fecha_inicio;
    const fechaFin = updateData.fecha_fin ? new Date(updateData.fecha_fin) : oferta.fecha_fin;

    if (fechaFin <= fechaInicio) {
      throw new BadRequestException('La fecha de fin debe ser posterior a la fecha de inicio');
    }

    const tipo = updateData.tipo || oferta.tipo;
    const valor = updateData.valor ?? Number(oferta.valor);

    if (tipo === 'descuento_porcentaje' && (valor <= 0 || valor > 100)) {
      throw new BadRequestException('El porcentaje de descuento debe estar entre 0 y 100');
    }

    const resultado = await this.prisma.$transaction(async (tx) => {
      // Actualizar aplicaciones si se proporcionan
      if (aplicaciones !== undefined) {
        await tx.oferta_aplicacion.deleteMany({ where: { oferta_id: id } });
        if (aplicaciones.length > 0) {
          await tx.oferta_aplicacion.createMany({
            data: aplicaciones.map((ap) => ({
              oferta_id: id,
              aplica_a: ap.aplica_a,
              referencia_id: ap.referencia_id || null,
              excluir: ap.excluir ?? false,
            })),
          });
        }
      }

      // Actualizar sucursales si se proporcionan
      if (sucursal_ids !== undefined) {
        await tx.oferta_sucursales.deleteMany({ where: { oferta_id: id } });
        if (sucursal_ids.length > 0) {
          await tx.oferta_sucursales.createMany({
            data: sucursal_ids.map((sid) => ({
              oferta_id: id,
              sucursal_id: sid,
            })),
          });
        }
      }

      // Actualizar oferta
      return await tx.ofertas.update({
        where: { id },
        data: {
          ...(updateData.nombre && { nombre: updateData.nombre }),
          ...(updateData.descripcion !== undefined && {
            descripcion: updateData.descripcion,
          }),
          ...(updateData.tipo && { tipo: updateData.tipo }),
          ...(updateData.valor !== undefined && { valor: updateData.valor }),
          ...(updateData.fecha_inicio && {
            fecha_inicio: new Date(updateData.fecha_inicio),
          }),
          ...(updateData.fecha_fin && {
            fecha_fin: new Date(updateData.fecha_fin),
          }),
          ...(updateData.prioridad !== undefined && {
            prioridad: updateData.prioridad,
          }),
          ...(acumulable_lista_precios !== undefined && {
            acumulable_lista_precios,
          }),
          updated_at: new Date(),
        },
        include: {
          oferta_aplicacion: true,
          oferta_sucursales: {
            include: { empresas_sucursales: { select: { descripcion: true } } },
          },
        },
      });
    });
    this.logger.log(`Oferta actualizada: ${resultado.nombre} (${id})`, 'OfertasService');
    await this.auditService.log({
      empresa_id: oferta.empresa_id,
      user_id,
      action: 'UPDATE',
      entity_type: 'oferta',
      entity_id: id,
      descripcion: `Oferta actualizada: ${resultado.nombre}`,
      old_value: { nombre: oferta.nombre, tipo: oferta.tipo, valor: Number(oferta.valor), active: oferta.active },
      new_value: { nombre: resultado.nombre, tipo: resultado.tipo, valor: Number(resultado.valor) },
    });
    return resultado;
  }

  async toggleActive(id: string, user_id?: string) {
    const oferta = await this.prisma.ofertas.findUnique({ where: { id } });
    if (!oferta) {
      throw new NotFoundException('Oferta no encontrada');
    }

    const updated = await this.prisma.ofertas.update({
      where: { id },
      data: { active: !oferta.active, updated_at: new Date() },
    });
    const action = updated.active ? 'ACTIVATE' : 'DEACTIVATE';
    this.logger.log(`Oferta (${id}) ${updated.active ? 'activada' : 'desactivada'}`, 'OfertasService');
    await this.auditService.log({
      empresa_id: oferta.empresa_id,
      user_id,
      action,
      entity_type: 'oferta',
      entity_id: id,
      descripcion: `Oferta ${updated.active ? 'activada' : 'desactivada'}: ${oferta.nombre}`,
      old_value: { active: oferta.active },
      new_value: { active: updated.active },
    });
    return updated;
  }

  async remove(id: string, user_id?: string) {
    const oferta = await this.prisma.ofertas.findUnique({ where: { id } });
    if (!oferta) {
      throw new NotFoundException('Oferta no encontrada');
    }

    const updated = await this.prisma.ofertas.update({
      where: { id },
      data: { active: false, updated_at: new Date() },
    });
    this.logger.log(`Oferta desactivada (soft delete): ${oferta.nombre} (${id})`, 'OfertasService');
    await this.auditService.log({
      empresa_id: oferta.empresa_id,
      user_id,
      action: 'DEACTIVATE',
      entity_type: 'oferta',
      entity_id: id,
      descripcion: `Oferta eliminada (soft delete): ${oferta.nombre}`,
      old_value: { active: oferta.active },
      new_value: { active: false },
    });
    return updated;
  }

  // ==================== OFERTAS ACTIVAS (POS) ====================

  async getOfertasActivas(empresaId: string, sucursalId?: string) {
    const ahora = new Date();

    const ofertas = await this.prisma.ofertas.findMany({
      where: {
        empresa_id: empresaId,
        active: true,
        fecha_inicio: { lte: ahora },
        fecha_fin: { gte: ahora },
      },
      include: {
        oferta_aplicacion: true,
        oferta_sucursales: true,
      },
      orderBy: { prioridad: 'desc' },
    });

    // Filtrar por sucursal
    if (sucursalId) {
      return ofertas.filter((o) => {
        if (o.oferta_sucursales.length === 0) return true; // Sin restricción de sucursal
        return o.oferta_sucursales.some((s) => s.sucursal_id === sucursalId);
      });
    }

    return ofertas;
  }

  // ==================== MOTOR DE EVALUACIÓN ====================

  async evaluarOfertas(dto: EvaluarOfertasDto, empresaId: string) {
    const { items, sucursal_id } = dto;

    if (!items || items.length === 0) {
      throw new BadRequestException('Debe proporcionar al menos un item');
    }

    // 1. Obtener ofertas activas y vigentes
    const ofertasActivas = await this.getOfertasActivas(empresaId, sucursal_id);

    if (ofertasActivas.length === 0) {
      return {
        items: items.map((item) => ({
          producto_id: item.producto_id,
          cantidad: item.cantidad,
          precio_original: item.precio_unitario,
          precio_final: item.precio_unitario,
          descuento: 0,
          oferta_id: null,
          nombre_oferta: null,
        })),
        total_descuento: 0,
      };
    }

    // 2. Obtener info de productos (categoría y marca)
    const productoIds = items.map((i) => i.producto_id);
    const productos = await this.prisma.productos.findMany({
      where: { id: { in: productoIds } },
      select: { id: true, categoria_id: true, marca_id: true },
    });
    const productoMap = new Map(productos.map((p) => [p.id, p]));

    // 3. Evaluar cada item
    const resultado = items.map((item) => {
      const producto = productoMap.get(item.producto_id);
      const precioBase = item.precio_unitario;
      const precioLista = item.precio_lista ?? null;
      const tieneListaPrecios = precioLista !== null && precioLista !== precioBase;

      if (!producto) {
        return {
          producto_id: item.producto_id,
          cantidad: item.cantidad,
          precio_original: precioBase,
          precio_lista: precioLista,
          precio_final: precioLista ?? precioBase,
          descuento: 0,
          oferta_id: null,
          nombre_oferta: null,
          tipo_oferta: null,
          origen_precio: tieneListaPrecios ? 'lista_precios' : 'base',
        };
      }

      // Buscar la mejor oferta aplicable
      let mejorOferta: {
        id: string;
        nombre: string;
        tipo: string;
        valor: number;
        acumulable: boolean;
        descuento: number;
        precioFinal: number;
      } | null = null;

      for (const oferta of ofertasActivas) {
        if (!this.ofertaAplicaAProducto(oferta, producto)) {
          continue;
        }

        const valorOferta = Number(oferta.valor);
        const esAcumulable = oferta.acumulable_lista_precios !== false;

        // Determinar precio base para calcular el descuento de la oferta
        // Si es acumulable y hay lista de precios, aplicar sobre precio_lista
        // Si NO es acumulable, siempre calcular sobre precio base original
        const precioParaCalculo = esAcumulable && tieneListaPrecios ? precioLista : precioBase;

        let descuento = 0;
        let precioFinal = precioParaCalculo;

        switch (oferta.tipo) {
          case 'descuento_porcentaje':
            descuento = Math.round(precioParaCalculo * (valorOferta / 100));
            precioFinal = precioParaCalculo - descuento;
            break;
          case 'descuento_monto':
            descuento = Math.min(valorOferta, precioParaCalculo);
            precioFinal = precioParaCalculo - descuento;
            break;
          case 'precio_especial':
            if (valorOferta < precioParaCalculo) {
              descuento = precioParaCalculo - valorOferta;
              precioFinal = valorOferta;
            }
            break;
        }

        if (descuento <= 0) continue;

        // Si NO es acumulable y hay lista de precios, comparar:
        // usar el menor entre precio_lista y precio_oferta
        if (!esAcumulable && tieneListaPrecios) {
          if (precioFinal >= precioLista) {
            // La lista de precios da mejor precio, no usar esta oferta
            continue;
          }
        }

        if (
          !mejorOferta ||
          descuento > mejorOferta.descuento ||
          (descuento === mejorOferta.descuento && Number(oferta.prioridad) > 0)
        ) {
          mejorOferta = {
            id: oferta.id,
            nombre: oferta.nombre,
            tipo: oferta.tipo,
            valor: valorOferta,
            acumulable: esAcumulable,
            descuento,
            precioFinal: Math.max(0, precioFinal),
          };
        }
      }

      // Determinar precio final considerando lista de precios y oferta
      let precioFinalItem: number;
      let origenPrecio: string;

      if (mejorOferta) {
        precioFinalItem = mejorOferta.precioFinal;
        origenPrecio = mejorOferta.acumulable && tieneListaPrecios ? 'lista_precios+oferta' : 'oferta';
      } else if (tieneListaPrecios) {
        precioFinalItem = precioLista;
        origenPrecio = 'lista_precios';
      } else {
        precioFinalItem = precioBase;
        origenPrecio = 'base';
      }

      const descuentoTotal = precioBase - precioFinalItem;

      return {
        producto_id: item.producto_id,
        cantidad: item.cantidad,
        precio_original: precioBase,
        precio_lista: precioLista,
        precio_final: precioFinalItem,
        descuento: mejorOferta ? mejorOferta.descuento : 0,
        descuento_total: Math.max(0, descuentoTotal) * item.cantidad,
        oferta_id: mejorOferta?.id || null,
        nombre_oferta: mejorOferta?.nombre || null,
        tipo_oferta: mejorOferta?.tipo || null,
        origen_precio: origenPrecio,
      };
    });

    const totalDescuento = resultado.reduce((sum, item) => sum + (item.descuento_total || 0), 0);

    return {
      items: resultado,
      total_descuento: totalDescuento,
    };
  }

  // ==================== REGISTRAR USO ====================

  async registrarUso(ofertaId: string, facturaId: string, clienteId: string | null, montoDescuento: number) {
    return this.prisma.oferta_usos.create({
      data: {
        oferta_id: ofertaId,
        factura_id: facturaId,
        cliente_id: clienteId,
        monto_descuento: montoDescuento,
      },
    });
  }

  // ==================== HISTORIAL DE USOS ====================

  async getUsos(ofertaId: string) {
    return this.prisma.oferta_usos.findMany({
      where: { oferta_id: ofertaId },
      include: {
        factura_cab: {
          select: {
            id: true,
            dnumdoc: true,
            dest: true,
            dpunexp: true,
            dfeemide: true,
            total_factura: true,
          },
        },
        clientes: {
          select: {
            id: true,
            personas: { select: { razon_social: true, ruc: true } },
          },
        },
      },
      orderBy: { created_at: 'desc' },
    });
  }

  // ==================== HELPERS ====================

  private ofertaAplicaAProducto(
    oferta: {
      oferta_aplicacion: Array<{
        aplica_a: string;
        referencia_id: string | null;
        excluir: boolean | null;
      }>;
    },
    producto: {
      id: string;
      categoria_id: string | null;
      marca_id: string | null;
    },
  ): boolean {
    const aplicaciones = oferta.oferta_aplicacion;

    if (!aplicaciones || aplicaciones.length === 0) return false;

    // Verificar si está excluido
    const exclusiones = aplicaciones.filter((a) => a.excluir);
    for (const excl of exclusiones) {
      if (excl.aplica_a === 'producto' && excl.referencia_id === producto.id) return false;
      if (excl.aplica_a === 'categoria' && excl.referencia_id === producto.categoria_id) return false;
      if (excl.aplica_a === 'marca' && excl.referencia_id === producto.marca_id) return false;
    }

    // Verificar si aplica
    const inclusiones = aplicaciones.filter((a) => !a.excluir);
    for (const incl of inclusiones) {
      if (incl.aplica_a === 'todos') return true;
      if (incl.aplica_a === 'producto' && incl.referencia_id === producto.id) return true;
      if (incl.aplica_a === 'categoria' && incl.referencia_id === producto.categoria_id) return true;
      if (incl.aplica_a === 'marca' && incl.referencia_id === producto.marca_id) return true;
    }

    return false;
  }
}
