import {
  BadRequestException, ForbiddenException, Injectable, Logger, NotFoundException,
} from '@nestjs/common';
import * as bcrypt from 'bcrypt';
import { Prisma } from '@prisma/client';
import { PrismaService } from 'src/prisma/prisma.service';

/** El privilegio que habilita a autorizar un cierre por encima del límite. */
export const PRIVILEGIO_AUTORIZAR = 'CRM_CIE_CREDITO_AUTORIZAR';

/**
 * Tipo reutilizado de las autorizaciones del POS. Un cierre a crédito por encima
 * del límite es la misma decisión comercial que una venta a crédito en caja, así
 * que comparte el tipo y aparece en el mismo historial.
 */
const TIPO_AUTORIZACION = 'venta_credito';

export interface ContextoOverride {
  oportunidad_id: string;
  cliente_id: string;
  total: number;
  motivo?: string | null;
}

/**
 * Validación del PIN de supervisor para pasar por encima del límite de crédito
 * (cierra la deuda que CRM-14 dejó anotada y lo último de PRE-03).
 *
 * El PIN se compara con bcrypt contra `usuario.pin_supervisor`, igual que en
 * `AutorizacionesCajaService.aprobar`. **Exige saber quién autoriza**: sin
 * `supervisor_id` habría que probar el PIN contra todos los usuarios de la
 * empresa, que además de lento es una invitación a adivinar PINs de cuatro
 * dígitos.
 *
 * Cada override aprobado deja una fila en `autorizaciones_caja`. Es lo que
 * permite responder después quién autorizó vender por encima del límite —antes
 * sólo quedaba una línea en el log, que nadie mira.
 */
@Injectable()
export class OverrideCreditoService {
  private readonly logger = new Logger(OverrideCreditoService.name);

  constructor(private readonly prisma: PrismaService) {}

  /**
   * Verifica el PIN y devuelve al supervisor. No persiste nada: el preview tiene
   * que poder validar sin dejar rastro, y el registro va en `registrar()`.
   */
  async verificar(empresa_id: string, supervisor_id: string | undefined, pin: string) {
    if (!supervisor_id) {
      throw new BadRequestException({
        message: 'Indicá qué supervisor autoriza el cierre, además del PIN',
        requiere: 'supervisor_id',
      });
    }

    const supervisor = await this.prisma.usuario.findFirst({
      where: { id: supervisor_id, empresa_id },
      select: {
        id: true,
        username: true,
        nombres: true,
        apellidos: true,
        pin_supervisor: true,
        is_superadmin: true,
        usuario_perfiles: {
          select: {
            perfiles: {
              select: { perfiles_privilegios: { select: { privilegios: { select: { codigo: true } } } } },
            },
          },
        },
      },
    });
    if (!supervisor) throw new NotFoundException('El supervisor no existe en esta empresa');
    if (!supervisor.pin_supervisor) {
      throw new BadRequestException(`${supervisor.username} no tiene PIN de supervisor configurado`);
    }

    if (!(await bcrypt.compare(pin, supervisor.pin_supervisor))) {
      // Mismo mensaje para PIN incorrecto que para supervisor sin privilegio
      // sería más cómodo, pero acá el 403 ayuda: el cajero necesita saber si
      // tiene que volver a tipear o llamar a otra persona.
      throw new ForbiddenException('PIN incorrecto');
    }

    if (!supervisor.is_superadmin && !this.puedeAutorizar(supervisor)) {
      throw new ForbiddenException(
        `${supervisor.username} no tiene el privilegio para autorizar un cierre por encima del límite`,
      );
    }

    return supervisor;
  }

  /** Deja la autorización registrada y aprobada, con el contexto de lo que se autorizó. */
  async registrar(
    empresa_id: string,
    solicitante_id: string,
    supervisor_id: string,
    contexto: ContextoOverride,
  ) {
    const fila = await this.prisma.autorizaciones_caja.create({
      data: {
        empresa_id,
        tipo: TIPO_AUTORIZACION,
        solicitante_id,
        autorizador_id: supervisor_id,
        estado: 'aprobada',
        motivo: contexto.motivo ?? 'Cierre de oportunidad por encima del límite de crédito',
        contexto: contexto as unknown as Prisma.InputJsonValue,
        resolved_at: new Date(),
      },
      select: { id: true },
    });

    this.logger.warn(
      `Override de crédito autorizado: oportunidad=${contexto.oportunidad_id} ` +
        `cliente=${contexto.cliente_id} total=${contexto.total} ` +
        `supervisor=${supervisor_id} autorizacion=${fila.id}`,
    );
    return fila.id;
  }

  /** Los que pueden autorizar, para que el wizard los ofrezca en una lista. */
  async supervisores(empresa_id: string) {
    const usuarios = await this.prisma.usuario.findMany({
      where: {
        empresa_id,
        active: true,
        // Sin PIN configurado no puede autorizar nada: ofrecerlo sería mandar al
        // cajero a buscar a alguien que después no va a poder firmar.
        pin_supervisor: { not: null },
        OR: [
          { is_superadmin: true },
          {
            usuario_perfiles: {
              some: {
                perfiles: {
                  perfiles_privilegios: { some: { privilegios: { codigo: PRIVILEGIO_AUTORIZAR } } },
                },
              },
            },
          },
        ],
      },
      select: { id: true, username: true, nombres: true, apellidos: true },
      orderBy: { username: 'asc' },
    });

    return usuarios.map((u) => ({
      id: u.id,
      username: u.username,
      nombre: [u.nombres, u.apellidos].filter(Boolean).join(' ') || u.username,
    }));
  }

  private puedeAutorizar(supervisor: {
    usuario_perfiles: { perfiles: { perfiles_privilegios: { privilegios: { codigo: string } | null }[] } | null }[];
  }): boolean {
    return supervisor.usuario_perfiles.some((up) =>
      up.perfiles?.perfiles_privilegios?.some((pp) => pp.privilegios?.codigo === PRIVILEGIO_AUTORIZAR),
    );
  }
}
