import { InjectQueue } from '@nestjs/bullmq';
import { ConflictException, Injectable, NotFoundException, ServiceUnavailableException } from '@nestjs/common';
import type { producto_imagen_busquedas } from '@prisma/client';
import { Queue } from 'bullmq';
import { AiConfigService } from 'src/ai-dashboard/ai-config/ai-config.service';
import { PrismaService } from 'src/prisma/prisma.service';
import { BuscadorSerperService } from './buscador-serper.service';
import type { CrearBusquedaDto } from './dto/crear-busqueda.dto';

export const IMAGENES_IA_QUEUE = 'imagenes-ia-queue';
export const IMAGENES_IA_JOB = 'procesar-busqueda';
export interface ImagenesIaJob {
  busqueda_id: string;
}

const ESTADOS_ACTIVOS = ['PENDIENTE', 'EN_PROCESO'] as const;
// Estimación por producto (spec §6.3): 2 queries; ~400 tokens de texto + 10 imágenes × 250 + 300 de salida; ~4 s con concurrencia 3.
const QUERIES_POR_PRODUCTO = 2;
const TOKENS_IN_POR_PRODUCTO = 400 + 10 * 250;
const TOKENS_OUT_POR_PRODUCTO = 300;
const SEGUNDOS_POR_PRODUCTO = 4 / 3;

@Injectable()
export class BusquedasService {
  constructor(
    private readonly prisma: PrismaService,
    private readonly aiConfig: AiConfigService,
    private readonly serper: BuscadorSerperService,
    @InjectQueue(IMAGENES_IA_QUEUE) private readonly queue: Queue<ImagenesIaJob>,
  ) {}

  private async activa(empresaId: string) {
    return this.prisma.producto_imagen_busquedas.findFirst({
      where: { empresa_id: empresaId, estado: { in: [...ESTADOS_ACTIVOS] } },
      orderBy: { created_at: 'desc' },
    });
  }

  async estado(empresaId: string) {
    const cfg = await this.aiConfig.getConfigEfectiva(empresaId);
    const activa = await this.activa(empresaId);
    return {
      ia_configurada: !!cfg.api_key,
      proveedor: cfg.api_key ? cfg.proveedor : null,
      modelo: cfg.api_key ? cfg.modelo : null,
      busqueda_disponible: this.serper.disponible(),
      busqueda_activa: activa ? { id: activa.id, estado: activa.estado, procesados: activa.procesados, total_productos: activa.total_productos } : null,
    };
  }

  /** Ids válidos de la empresa (no eliminados), quitando los que ya tienen imagen o no tienen stock si corresponde. */
  async resolverAProcesar(
    empresaId: string,
    productoIds: string[],
    soloSinImagen: boolean,
    soloConStock = false,
  ): Promise<{ ids: string[]; omitidos: number }> {
    const unicos = [...new Set(productoIds)];
    const existentes = await this.prisma.productos.findMany({
      where: { id: { in: unicos }, empresa_id: empresaId, OR: [{ deleted: false }, { deleted: null }] },
      select: { id: true },
    });
    let ids = existentes.map((p) => p.id);
    if (soloConStock && ids.length) {
      // Con existencias en cualquier depósito: no vale la pena gastar IA en lo que no se vende.
      const conStock = await this.prisma.stock_deposito.groupBy({
        by: ['producto_id'],
        where: { producto_id: { in: ids } },
        _sum: { cantidad_disponible: true },
      });
      const set = new Set(conStock.filter((s) => Number(s._sum.cantidad_disponible ?? 0) > 0).map((s) => s.producto_id));
      ids = ids.filter((id) => set.has(id));
    }
    if (soloSinImagen && ids.length) {
      const conImagen = await this.prisma.producto_imagenes.findMany({
        where: { producto_id: { in: ids }, active: true },
        select: { producto_id: true },
        distinct: ['producto_id'],
      });
      const set = new Set(conImagen.map((i) => i.producto_id));
      ids = ids.filter((id) => !set.has(id));
    }
    return { ids, omitidos: unicos.length - ids.length };
  }

  async estimar(empresaId: string, dto: CrearBusquedaDto) {
    const { ids, omitidos } = await this.resolverAProcesar(
      empresaId,
      dto.producto_ids,
      dto.solo_sin_imagen ?? true,
      dto.solo_con_stock ?? false,
    );
    const cfg = await this.aiConfig.getConfigEfectiva(empresaId);
    const tarifa = await this.prisma.ai_tarifas.findFirst({ where: { proveedor: cfg.proveedor, modelo: cfg.modelo, activo: true } });
    const n = ids.length;
    const costo = tarifa
      ? (n * TOKENS_IN_POR_PRODUCTO * Number(tarifa.precio_in_por_1m) + n * TOKENS_OUT_POR_PRODUCTO * Number(tarifa.precio_out_por_1m)) / 1e6
      : 0;
    return {
      a_procesar: n,
      omitidos,
      queries_estimadas: n * QUERIES_POR_PRODUCTO,
      costo_usd_estimado: Math.round(costo * 10000) / 10000,
      minutos_estimados: Math.ceil((n * SEGUNDOS_POR_PRODUCTO) / 60),
    };
  }

  async crear(empresaId: string, usuarioId: string, dto: CrearBusquedaDto): Promise<producto_imagen_busquedas> {
    if (!this.serper.disponible()) throw new ServiceUnavailableException('La búsqueda de imágenes no está disponible en esta instancia (falta SERPER_API_KEY).');
    const cfg = await this.aiConfig.getConfigEfectiva(empresaId);
    if (!cfg.api_key) throw new ConflictException('Configurá la IA de la empresa en Configuración → IA antes de buscar imágenes.');
    if (await this.activa(empresaId)) throw new ConflictException('Ya hay una búsqueda de imágenes en curso. Esperá a que termine o cancelala.');

    const soloSinImagen = dto.solo_sin_imagen ?? true;
    const { ids, omitidos } = await this.resolverAProcesar(empresaId, dto.producto_ids, soloSinImagen, dto.solo_con_stock ?? false);
    if (!ids.length)
      throw new ConflictException('Ningún producto de la selección necesita imágenes (ya tienen, no existen o quedaron fuera por el filtro de stock).');

    const busqueda = await this.prisma.producto_imagen_busquedas.create({
      data: { empresa_id: empresaId, usuario_id: usuarioId, solo_sin_imagen: soloSinImagen, producto_ids: ids, total_productos: ids.length, omitidos },
    });
    const job = await this.queue.add(
      IMAGENES_IA_JOB,
      { busqueda_id: busqueda.id },
      { attempts: 2, backoff: { type: 'fixed', delay: 30_000 }, removeOnComplete: 100, removeOnFail: 100 },
    );
    await this.prisma.producto_imagen_busquedas.update({ where: { id: busqueda.id }, data: { job_id: String(job.id) } });
    return busqueda;
  }

  async listar(empresaId: string, page = 1, limit = 10) {
    const skip = (Math.max(1, page) - 1) * limit;
    const where = { empresa_id: empresaId };
    const [items, total] = await Promise.all([
      this.prisma.producto_imagen_busquedas.findMany({ where, orderBy: { created_at: 'desc' }, skip, take: limit }),
      this.prisma.producto_imagen_busquedas.count({ where }),
    ]);
    return { items, total, page, limit };
  }

  async obtener(empresaId: string, id: string): Promise<producto_imagen_busquedas> {
    const b = await this.prisma.producto_imagen_busquedas.findFirst({ where: { id, empresa_id: empresaId } });
    if (!b) throw new NotFoundException('Búsqueda no encontrada');
    return b;
  }

  async cancelar(empresaId: string, id: string): Promise<producto_imagen_busquedas> {
    const b = await this.obtener(empresaId, id);
    if (!(ESTADOS_ACTIVOS as readonly string[]).includes(b.estado)) throw new ConflictException(`La búsqueda ya está ${b.estado.toLowerCase()}.`);
    return this.prisma.producto_imagen_busquedas.update({
      where: { id },
      data: { estado: 'CANCELADA', finalizado_en: new Date(), updated_at: new Date() },
    });
  }
}
