import { ConflictException, Injectable, NotFoundException } from '@nestjs/common';
import type { Prisma, producto_imagenes_sugeridas } from '@prisma/client';
import { AuditService } from 'src/audit/audit.service';
import { PrismaService } from 'src/prisma/prisma.service';
import type { AprobarMasivoDto } from './dto/aprobar-masivo.dto';
import type { ListarSugerenciasDto } from './dto/listar-sugerencias.dto';
import { esAprobable, MAX_IMAGENES_POR_PRODUCTO } from './pipeline/puntaje';
import { StorageSugeridasService } from './storage-sugeridas.service';

export interface SugerenciaVista extends producto_imagenes_sugeridas {
  url: string;
  aprobable: boolean;
}
export interface ProductoConSugerencias {
  producto: { id: string; descripcion: string; cod_producto: string | null; marca: string | null; categoria: string | null; imagenes_actuales: number };
  sugerencias: SugerenciaVista[];
}

const num = (d: Prisma.Decimal | number) => Number(d);

@Injectable()
export class SugerenciasService {
  constructor(
    private readonly prisma: PrismaService,
    private readonly storage: StorageSugeridasService,
    private readonly audit: AuditService,
  ) {}

  private vista(s: producto_imagenes_sugeridas): SugerenciaVista {
    return {
      ...s,
      url: this.storage.urlPublica(s.path),
      aprobable: esAprobable({ coincide: num(s.coincide), fondo_blanco: s.fondo_blanco, marca_agua: s.marca_agua, calidad: num(s.calidad) }),
    };
  }

  async listar(empresaId: string, dto: ListarSugerenciasDto) {
    const page = dto.page ?? 1;
    const limit = dto.limit ?? 20;
    const where: Prisma.producto_imagenes_sugeridasWhereInput = {
      empresa_id: empresaId,
      estado: dto.estado ?? 'SUGERIDA',
      ...(dto.busqueda_id ? { busqueda_id: dto.busqueda_id } : {}),
      productos: {
        OR: [{ deleted: false }, { deleted: null }],
        ...(dto.categoria_id?.length ? { categoria_id: { in: dto.categoria_id } } : {}),
        ...(dto.marca_id?.length ? { marca_id: { in: dto.marca_id } } : {}),
        ...(dto.q ? { AND: [{ OR: [{ descripcion: { contains: dto.q, mode: 'insensitive' } }, { cod_producto: { contains: dto.q, mode: 'insensitive' } }] }] } : {}),
      },
    };
    // Paginación por producto: primero los ids de producto (ordenados por la sugerencia más reciente).
    const grupos = await this.prisma.producto_imagenes_sugeridas.groupBy({
      by: ['producto_id'],
      where,
      _max: { created_at: true },
      orderBy: { _max: { created_at: 'desc' } },
      skip: (page - 1) * limit,
      take: limit,
    });
    const totalGrupos = await this.prisma.producto_imagenes_sugeridas.groupBy({ by: ['producto_id'], where });
    const ids = grupos.map((g) => g.producto_id);
    if (!ids.length) return { items: [] as ProductoConSugerencias[], total: totalGrupos.length, page, limit };

    const [productos, sugerencias] = await Promise.all([
      this.prisma.productos.findMany({
        where: { id: { in: ids } },
        select: {
          id: true,
          descripcion: true,
          cod_producto: true,
          marcas: { select: { descripcion: true } },
          categoria: { select: { descripcion: true } },
          _count: { select: { producto_imagenes: { where: { active: true } } } },
        },
      }),
      this.prisma.producto_imagenes_sugeridas.findMany({ where: { ...where, producto_id: { in: ids } }, orderBy: { score: 'desc' } }),
    ]);
    const porProducto = new Map(productos.map((p) => [p.id, p]));
    const items: ProductoConSugerencias[] = ids
      .filter((id) => porProducto.has(id))
      .map((id) => {
        const p = porProducto.get(id)!;
        return {
          producto: {
            id: p.id,
            descripcion: p.descripcion,
            cod_producto: p.cod_producto,
            marca: p.marcas?.descripcion ?? null,
            categoria: p.categoria?.descripcion ?? null,
            imagenes_actuales: p._count.producto_imagenes,
          },
          sugerencias: sugerencias.filter((s) => s.producto_id === id).map((s) => this.vista(s)),
        };
      });
    return { items, total: totalGrupos.length, page, limit };
  }

  private async obtenerPendiente(empresaId: string, id: string): Promise<producto_imagenes_sugeridas> {
    const s = await this.prisma.producto_imagenes_sugeridas.findFirst({ where: { id, empresa_id: empresaId } });
    if (!s) throw new NotFoundException('Sugerencia no encontrada');
    if (s.estado !== 'SUGERIDA') throw new ConflictException(`La sugerencia ya fue ${s.estado === 'APROBADA' ? 'aprobada' : 'rechazada'}.`);
    return s;
  }

  async aprobar(empresaId: string, usuarioId: string, id: string): Promise<producto_imagenes_sugeridas> {
    const s = await this.obtenerPendiente(empresaId, id);
    return this.aprobarSugerencia(empresaId, usuarioId, s);
  }

  /** Núcleo de aprobación, sin re-leer la fila (la usa también aprobarMasivo). */
  private async aprobarSugerencia(empresaId: string, usuarioId: string, s: producto_imagenes_sugeridas): Promise<producto_imagenes_sugeridas> {
    const actuales = await this.prisma.producto_imagenes.count({ where: { producto_id: s.producto_id, active: true } });
    if (actuales >= MAX_IMAGENES_POR_PRODUCTO) throw new ConflictException(`El producto ya tiene el máximo de ${MAX_IMAGENES_POR_PRODUCTO} imágenes.`);

    let copia: { path: string; tamanhoKb: number };
    try {
      copia = await this.storage.promoverAProducto(s.path, s.producto_id);
    } catch (e) {
      if ((e as Error).name === 'NotFound' || /not ?found/i.test((e as Error).message)) {
        await this.prisma.producto_imagenes_sugeridas.update({
          where: { id: s.id },
          data: { estado: 'RECHAZADA', revisado_por: usuarioId, revisado_en: new Date() },
        });
        throw new ConflictException('La imagen ya no está disponible; se marcó como rechazada.');
      }
      throw e;
    }

    const ahora = new Date();
    return this.prisma.$transaction(async (tx) => {
      const img = await tx.producto_imagenes.create({
        data: {
          producto_id: s.producto_id,
          nombre: `ia-${s.fuente_dominio ?? 'referencia'}.webp`,
          path: copia.path,
          extension: 'webp',
          tamanho_kb: String(copia.tamanhoKb),
          orden: actuales + 1,
          es_principal: actuales === 0,
          active: true,
          created_at: ahora,
          updated_at: ahora,
          usuario: usuarioId,
        },
      });
      const aprobada = await tx.producto_imagenes_sugeridas.update({
        where: { id: s.id },
        data: { estado: 'APROBADA', revisado_por: usuarioId, revisado_en: ahora, producto_imagen_id: img.id },
      });
      await this.audit.log({
        empresa_id: empresaId,
        user_id: usuarioId,
        action: 'CREATE',
        entity_type: 'producto_imagen',
        entity_id: img.id,
        descripcion: `Imagen sugerida por IA aprobada para el producto ${s.producto_id} (fuente ${s.fuente_dominio ?? '?'})`,
        new_value: { producto_id: s.producto_id, key: copia.path, fuente_url: s.fuente_url, score: num(s.score) },
      });
      return aprobada;
    });
  }

  async rechazar(empresaId: string, usuarioId: string, id: string): Promise<producto_imagenes_sugeridas> {
    const s = await this.obtenerPendiente(empresaId, id);
    await this.storage.borrar(s.path);
    return this.prisma.producto_imagenes_sugeridas.update({
      where: { id },
      data: { estado: 'RECHAZADA', revisado_por: usuarioId, revisado_en: new Date() },
    });
  }

  async aprobarMasivo(empresaId: string, usuarioId: string, dto: AprobarMasivoDto) {
    const scoreMin = dto.score_min ?? 0.9;
    const pendientes = await this.prisma.producto_imagenes_sugeridas.findMany({
      where: { empresa_id: empresaId, estado: 'SUGERIDA', score: { gte: scoreMin }, ...(dto.busqueda_id ? { busqueda_id: dto.busqueda_id } : {}) },
      orderBy: [{ producto_id: 'asc' }, { score: 'desc' }],
    });
    const porProducto = new Map<string, producto_imagenes_sugeridas[]>();
    for (const s of pendientes) {
      if (!esAprobable({ coincide: num(s.coincide), fondo_blanco: s.fondo_blanco, marca_agua: s.marca_agua, calidad: num(s.calidad) })) continue;
      porProducto.set(s.producto_id, [...(porProducto.get(s.producto_id) ?? []), s]);
    }
    let aprobadas = 0;
    let omitidasPorLimite = 0;
    let productos = 0;
    for (const [productoId, lista] of porProducto) {
      const actuales = await this.prisma.producto_imagenes.count({ where: { producto_id: productoId, active: true } });
      const cupo = Math.max(0, MAX_IMAGENES_POR_PRODUCTO - actuales);
      let aprobadasProducto = 0;
      for (const s of lista.slice(0, cupo)) {
        try {
          await this.aprobarSugerencia(empresaId, usuarioId, s);
          aprobadasProducto++;
        } catch {
          // objeto faltante u otro error puntual: no frena el lote
        }
      }
      omitidasPorLimite += Math.max(0, lista.length - cupo);
      aprobadas += aprobadasProducto;
      if (aprobadasProducto) productos++;
    }
    return { aprobadas, productos, omitidas_por_limite: omitidasPorLimite };
  }

  async rechazarRestantes(empresaId: string, usuarioId: string, productoId: string) {
    const pendientes = await this.prisma.producto_imagenes_sugeridas.findMany({
      where: { empresa_id: empresaId, producto_id: productoId, estado: 'SUGERIDA' },
    });
    await Promise.all(pendientes.map((s) => this.storage.borrar(s.path)));
    if (pendientes.length) {
      await this.prisma.producto_imagenes_sugeridas.updateMany({
        where: { id: { in: pendientes.map((s) => s.id) } },
        data: { estado: 'RECHAZADA', revisado_por: usuarioId, revisado_en: new Date() },
      });
    }
    return { rechazadas: pendientes.length };
  }
}
