import { BadRequestException, Injectable, Logger, NotFoundException } from '@nestjs/common';
import { PrismaService } from 'src/prisma/prisma.service';
import { CreateProductoEspecificacionDto } from './dto/create-producto-especificacion.dto';
import { ReordenarProductoEspecificacionesDto } from './dto/reordenar-producto-especificaciones.dto';
import { UpdateProductoEspecificacionDto } from './dto/update-producto-especificacion.dto';

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

  constructor(private prisma: PrismaService) {}

  // ==================== CREAR ====================

  async create(dto: CreateProductoEspecificacionDto, empresaId: string, usuario?: string) {
    const producto = await this.getProductoDeEmpresa(dto.producto_id, empresaId);

    const clave = dto.clave.trim();
    const valor = dto.valor.trim();
    if (!clave || !valor) {
      throw new BadRequestException('La clave y el valor de la especificación no pueden estar vacíos');
    }

    const duplicada = await this.prisma.producto_especificaciones.findFirst({
      where: { producto_id: dto.producto_id, clave: { equals: clave, mode: 'insensitive' } },
      select: { id: true },
    });
    if (duplicada) {
      throw new BadRequestException(`El producto ya tiene una especificación "${clave}"`);
    }

    // Si no viene orden, la nueva va al final de la lista.
    const orden = dto.orden ?? (await this.siguienteOrden(dto.producto_id));

    const registro = await this.prisma.producto_especificaciones.create({
      data: {
        empresa_id: empresaId,
        producto_id: dto.producto_id,
        clave,
        valor,
        orden,
        activo: dto.activo ?? true,
        usuario: usuario ?? null,
      },
    });

    this.logger.log(
      `Especificación creada: "${clave}: ${valor}" para producto ${producto.descripcion} (${registro.id})`,
      'ProductoEspecificacionesService',
    );

    return registro;
  }

  // ==================== LISTAR POR PRODUCTO ====================

  async findByProducto(productoId: string, empresaId: string) {
    return this.prisma.producto_especificaciones.findMany({
      where: { producto_id: productoId, empresa_id: empresaId },
      orderBy: [{ orden: 'asc' }, { created_at: 'asc' }],
    });
  }

  // ==================== OBTENER UNA ====================

  async findOne(id: string, empresaId: string) {
    const registro = await this.prisma.producto_especificaciones.findFirst({
      where: { id, empresa_id: empresaId },
      include: { producto: { select: { id: true, descripcion: true, cod_producto: true } } },
    });

    if (!registro) {
      throw new NotFoundException('Especificación no encontrada');
    }

    return registro;
  }

  // ==================== ACTUALIZAR ====================

  async update(id: string, dto: UpdateProductoEspecificacionDto, empresaId: string, usuario?: string) {
    const registro = await this.getEspecificacionDeEmpresa(id, empresaId);

    const clave = dto.clave !== undefined ? dto.clave.trim() : undefined;
    const valor = dto.valor !== undefined ? dto.valor.trim() : undefined;
    if (clave === '' || valor === '') {
      throw new BadRequestException('La clave y el valor de la especificación no pueden estar vacíos');
    }

    if (clave && clave.toLowerCase() !== registro.clave.toLowerCase()) {
      const duplicada = await this.prisma.producto_especificaciones.findFirst({
        where: {
          producto_id: registro.producto_id,
          clave: { equals: clave, mode: 'insensitive' },
          id: { not: id },
        },
        select: { id: true },
      });
      if (duplicada) {
        throw new BadRequestException(`El producto ya tiene una especificación "${clave}"`);
      }
    }

    const actualizado = await this.prisma.producto_especificaciones.update({
      where: { id },
      data: {
        ...(clave !== undefined && { clave }),
        ...(valor !== undefined && { valor }),
        ...(dto.orden !== undefined && { orden: dto.orden }),
        ...(dto.activo !== undefined && { activo: dto.activo }),
        updated_at: new Date(),
        ...(usuario && { usuario }),
      },
    });

    this.logger.log(
      `Especificación actualizada: "${actualizado.clave}: ${actualizado.valor}" (${id})`,
      'ProductoEspecificacionesService',
    );

    return actualizado;
  }

  // ==================== REORDENAR ====================

  /** Reasigna `orden` según la posición de cada id en el array recibido. */
  async reordenar(productoId: string, dto: ReordenarProductoEspecificacionesDto, empresaId: string) {
    await this.getProductoDeEmpresa(productoId, empresaId);

    const existentes = await this.prisma.producto_especificaciones.findMany({
      where: { producto_id: productoId, empresa_id: empresaId },
      select: { id: true },
    });
    const idsValidos = new Set(existentes.map((e) => e.id));

    const desconocido = dto.ids.find((id) => !idsValidos.has(id));
    if (desconocido) {
      throw new BadRequestException('Una de las especificaciones no pertenece a este producto');
    }

    await this.prisma.$transaction(
      dto.ids.map((id, index) =>
        this.prisma.producto_especificaciones.update({
          where: { id },
          data: { orden: index, updated_at: new Date() },
        }),
      ),
    );

    return this.findByProducto(productoId, empresaId);
  }

  // ==================== ELIMINAR ====================

  async remove(id: string, empresaId: string) {
    const registro = await this.getEspecificacionDeEmpresa(id, empresaId);

    await this.prisma.producto_especificaciones.delete({ where: { id } });

    this.logger.log(
      `Especificación eliminada: "${registro.clave}: ${registro.valor}" (${id})`,
      'ProductoEspecificacionesService',
    );

    return { message: 'Especificación eliminada correctamente' };
  }

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

  private async getProductoDeEmpresa(productoId: string, empresaId: string) {
    const producto = await this.prisma.productos.findFirst({
      where: { id: productoId, empresa_id: empresaId },
      select: { id: true, descripcion: true },
    });

    if (!producto) {
      throw new NotFoundException('Producto no encontrado o no pertenece a la empresa');
    }

    return producto;
  }

  private async getEspecificacionDeEmpresa(id: string, empresaId: string) {
    const registro = await this.prisma.producto_especificaciones.findFirst({
      where: { id, empresa_id: empresaId },
    });

    if (!registro) {
      throw new NotFoundException('Especificación no encontrada');
    }

    return registro;
  }

  private async siguienteOrden(productoId: string) {
    const ultima = await this.prisma.producto_especificaciones.aggregate({
      where: { producto_id: productoId },
      _max: { orden: true },
    });

    return (ultima._max.orden ?? -1) + 1;
  }
}
