import { Injectable, Logger, NotFoundException } from '@nestjs/common';
import { Prisma } from '@prisma/client';
import { PrismaService } from 'src/prisma/prisma.service';
import { alcanceSucursalUsuario } from 'src/utils/alcance-sucursal';
import { ORDEN_CRITICIDAD, RegistrarAlertaInput, ResolucionAlerta } from './alertas-empresa.types';
import { contextoPermisosUsuario, puedeVerAlerta } from './permisos-usuario';

export type AlertaParaUsuario = Prisma.ai_alertasGetPayload<object> & { leida: boolean; mostrada_popup: boolean };

/**
 * Bandeja única de alertas de la empresa. Ver docs/plan-alertas-empresa.md.
 *
 * Es el único punto por el que se escriben alertas nuevas: hoy las inconsistencias
 * de eventos SIFEN; después las reglas de cobranzas/stock y las que genere la IA.
 * `ai_alertas.leida` (por empresa) no se usa acá: la lectura es por usuario en
 * `ai_alertas_lecturas`.
 */
@Injectable()
export class AlertasEmpresaService {
  private readonly logger = new Logger(AlertasEmpresaService.name);

  constructor(private readonly prisma: PrismaService) {}

  /**
   * Crea la alerta o, si ya hay una abierta con la misma clave, la actualiza.
   * Si la criticidad sube, se borran las lecturas: vuelve a aparecer como nueva
   * para todos, que es justamente cuando más importa que la vean.
   */
  async registrar(input: RegistrarAlertaInput, reintento = false): Promise<{ id: string; creada: boolean }> {
    const existente = await this.prisma.ai_alertas.findFirst({
      where: { empresa_id: input.empresaId, clave: input.clave, resuelta: false },
      select: { id: true, criticidad: true },
    });

    const campos = {
      tipo: input.tipo,
      criticidad: input.criticidad,
      titulo: input.titulo.slice(0, 200),
      descripcion: input.descripcion,
      accion_sugerida: input.accionSugerida ?? null,
      modulo: input.modulo,
      privilegio_requerido: input.privilegioRequerido ?? null,
      ruta: input.ruta ?? null,
      sucursal_id: input.sucursalId ?? null,
      entidad_tipo: input.entidadTipo ?? null,
      entidad_id: input.entidadId ?? null,
      datos: (input.datos ?? Prisma.JsonNull) as Prisma.InputJsonValue,
      origen: input.origen,
      updated_at: new Date(),
    };

    if (existente) {
      const sube = (ORDEN_CRITICIDAD[input.criticidad] ?? 9) < (ORDEN_CRITICIDAD[existente.criticidad] ?? 9);
      await this.prisma.$transaction(async (tx) => {
        await tx.ai_alertas.update({ where: { id: existente.id }, data: campos });
        if (sube) await tx.ai_alertas_lecturas.deleteMany({ where: { alerta_id: existente.id } });
      });
      return { id: existente.id, creada: false };
    }

    try {
      const creada = await this.prisma.ai_alertas.create({
        data: {
          ...campos,
          empresa_id: input.empresaId,
          clave: input.clave,
          resuelta: false,
          leida: false,
          canales_enviados: { dashboard: true, email: false, whatsapp: false },
        },
        select: { id: true },
      });
      return { id: creada.id, creada: true };
    } catch (error) {
      // El índice parcial (empresa_id, clave) cortó una creación paralela: la
      // otra ya existe, así que esta pasa a ser una actualización.
      if (!reintento && (error as { code?: string })?.code === 'P2002') {
        return this.registrar(input, true);
      }
      throw error;
    }
  }

  async resolver(
    empresaId: string,
    clave: string,
    resolucion: ResolucionAlerta,
    usuarioId?: string | null,
  ): Promise<boolean> {
    const { count } = await this.prisma.ai_alertas.updateMany({
      where: { empresa_id: empresaId, clave, resuelta: false },
      data: {
        resuelta: true,
        resuelta_at: new Date(),
        resuelta_por: usuarioId ?? null,
        resolucion,
        updated_at: new Date(),
      },
    });
    return count > 0;
  }

  /**
   * Alertas abiertas que le corresponden al usuario: módulo activo en el plan,
   * privilegio requerido y sucursal dentro de su alcance (una alerta sin
   * sucursal es de toda la empresa). Altas primero.
   */
  async listarParaUsuario(
    usuarioId: string,
    empresaId: string,
    filtros: { modulo?: string; entidadTipo?: string } = {},
  ): Promise<AlertaParaUsuario[]> {
    const [alertas, permisos, alcance] = await Promise.all([
      this.prisma.ai_alertas.findMany({
        where: {
          empresa_id: empresaId,
          resuelta: false,
          // Sólo la bandeja nueva: las reglas viejas del Dashboard IA no tienen
          // módulo ni clave, y hasta migrarlas (Fase 2) siguen allá.
          modulo: filtros.modulo ?? { not: null },
          ...(filtros.entidadTipo ? { entidad_tipo: filtros.entidadTipo } : {}),
        },
      }),
      contextoPermisosUsuario(this.prisma, usuarioId, empresaId),
      alcanceSucursalUsuario(this.prisma, usuarioId, empresaId),
    ]);

    const visibles = alertas.filter(
      (a) =>
        puedeVerAlerta(permisos, a) &&
        (!a.sucursal_id || !alcance || alcance.sucursalIds.includes(a.sucursal_id)),
    );
    if (visibles.length === 0) return [];

    const lecturas = await this.prisma.ai_alertas_lecturas.findMany({
      where: { usuario_id: usuarioId, alerta_id: { in: visibles.map((a) => a.id) } },
      select: { alerta_id: true, leida_at: true, mostrada_popup_at: true },
    });
    const lecturaPorAlerta = new Map(lecturas.map((l) => [l.alerta_id, l]));

    return visibles
      .map((a) => ({
        ...a,
        leida: !!lecturaPorAlerta.get(a.id)?.leida_at,
        mostrada_popup: !!lecturaPorAlerta.get(a.id)?.mostrada_popup_at,
      }))
      .sort(
        (x, y) =>
          (ORDEN_CRITICIDAD[x.criticidad] ?? 9) - (ORDEN_CRITICIDAD[y.criticidad] ?? 9) ||
          new Date(y.updated_at).getTime() - new Date(x.updated_at).getTime(),
      );
  }

  async marcarLeida(alertaId: string, usuarioId: string, empresaId: string): Promise<void> {
    await this.obtenerDeEmpresa(alertaId, empresaId);
    const ahora = new Date();
    await this.prisma.ai_alertas_lecturas.upsert({
      where: { alerta_id_usuario_id: { alerta_id: alertaId, usuario_id: usuarioId } },
      create: { alerta_id: alertaId, usuario_id: usuarioId, leida_at: ahora },
      update: { leida_at: ahora },
    });
  }

  async marcarMostradaPopup(ids: string[], usuarioId: string, empresaId: string): Promise<void> {
    const unicos = [...new Set(ids ?? [])];
    if (unicos.length === 0) return;
    const propias = await this.prisma.ai_alertas.findMany({
      where: { id: { in: unicos }, empresa_id: empresaId },
      select: { id: true },
    });
    const ahora = new Date();
    for (const { id } of propias) {
      await this.prisma.ai_alertas_lecturas.upsert({
        where: { alerta_id_usuario_id: { alerta_id: id, usuario_id: usuarioId } },
        create: { alerta_id: id, usuario_id: usuarioId, mostrada_popup_at: ahora },
        update: { mostrada_popup_at: ahora },
      });
    }
  }

  private async obtenerDeEmpresa(alertaId: string, empresaId: string) {
    const alerta = await this.prisma.ai_alertas.findFirst({
      where: { id: alertaId, empresa_id: empresaId },
      select: { id: true },
    });
    if (!alerta) throw new NotFoundException('Alerta no encontrada');
    return alerta;
  }
}
