import { BadRequestException, ForbiddenException, Injectable, NotFoundException, Logger } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import * as bcrypt from 'bcrypt';
import { AuditService } from 'src/audit/audit.service';
import { PosGateway } from 'src/pos-gateway/pos-gateway.gateway';
import { PrismaService } from 'src/prisma/prisma.service';
import { CrearAutorizacionDto, EstadoAutorizacionCaja } from './dto/crear-autorizacion.dto';

@Injectable()
export class AutorizacionesCajaService {
  private readonly logger = new Logger(AutorizacionesCajaService.name);
  constructor(
    private readonly prisma: PrismaService,
    private readonly jwtService: JwtService,
    private readonly auditService: AuditService,
    private readonly posGateway: PosGateway,
  ) {}

  async solicitar(dto: CrearAutorizacionDto, empresa_id: string, solicitante_id: string) {
    const expiresAt = new Date(Date.now() + 5 * 60 * 1000); // 5 minutos

    const autorizacion = await this.prisma.autorizaciones_caja.create({
      data: {
        empresa_id,
        sesion_caja_id: dto.sesion_caja_id ?? null,
        tipo: dto.tipo,
        solicitante_id,
        estado: EstadoAutorizacionCaja.PENDIENTE,
        dispositivo_fingerprint: dto.dispositivo_fingerprint ?? null,
        ip_solicitante: dto.ip_solicitante ?? null,
        motivo: dto.motivo ?? null,
        contexto: (dto.contexto as any) ?? undefined,
        expires_at: expiresAt,
      },
      include: {
        solicitante: {
          select: { id: true, username: true, nombres: true, apellidos: true },
        },
      },
    });

    this.logger.log(
      `Autorización solicitada: ${dto.tipo} por usuario ${solicitante_id} | ID: ${autorizacion.id}`,
      'AutorizacionesCajaService',
    );

    await this.auditService.log({
      empresa_id,
      user_id: solicitante_id,
      action: 'CREATE',
      entity_type: 'autorizacion_caja',
      entity_id: autorizacion.id,
      descripcion: `Autorización solicitada: ${dto.tipo}`,
      new_value: { tipo: dto.tipo, motivo: dto.motivo, sesion_caja_id: dto.sesion_caja_id },
    });

    // Notificar a supervisores en tiempo real
    this.posGateway.notificarSolicitudAutorizacion(empresa_id, {
      id: autorizacion.id,
      tipo: dto.tipo,
      solicitante: autorizacion.solicitante as any,
      motivo: dto.motivo,
      sesion_caja_id: dto.sesion_caja_id,
      contexto: dto.contexto,
      expires_at: autorizacion.expires_at,
    });

    return {
      message: 'Autorización solicitada',
      data: autorizacion,
    };
  }

  async aprobar(autorizacion_id: string, autorizador_id: string, pin: string, empresa_id: string) {
    const autorizacion = await this.prisma.autorizaciones_caja.findUnique({
      where: { id: autorizacion_id },
    });

    if (!autorizacion) throw new NotFoundException('Autorización no encontrada');
    if (autorizacion.estado !== EstadoAutorizacionCaja.PENDIENTE)
      throw new BadRequestException('La autorización ya fue resuelta');
    if (autorizacion.expires_at && new Date() > autorizacion.expires_at) {
      await this.prisma.autorizaciones_caja.update({
        where: { id: autorizacion_id },
        data: { estado: EstadoAutorizacionCaja.EXPIRADA, resolved_at: new Date() },
      });
      throw new BadRequestException('La autorización ha expirado');
    }

    // Verificar supervisor y PIN
    const supervisor = await this.prisma.usuario.findUnique({
      where: { id: autorizador_id },
      include: {
        usuario_perfiles: {
          include: {
            perfiles: {
              include: {
                perfiles_privilegios: {
                  include: { privilegios: true },
                },
              },
            },
          },
        },
      },
    });

    if (!supervisor) throw new NotFoundException('Supervisor no encontrado');
    if (!supervisor.pin_supervisor) throw new BadRequestException('El supervisor no tiene PIN configurado');

    const pinValido = await bcrypt.compare(pin, supervisor.pin_supervisor);
    if (!pinValido) throw new ForbiddenException('PIN incorrecto');

    // Verificar privilegio POS_SUPERVISOR
    if (!supervisor.is_superadmin) {
      const tienePrivilegio = supervisor.usuario_perfiles.some((up) =>
        up.perfiles?.perfiles_privilegios?.some((pp) => pp.privilegios?.codigo === 'POS_SUPERVISOR'),
      );
      if (!tienePrivilegio) throw new ForbiddenException('El usuario no tiene privilegio de supervisor POS');
    }

    // Aprobar
    const updated = await this.prisma.autorizaciones_caja.update({
      where: { id: autorizacion_id },
      data: {
        autorizador_id,
        estado: EstadoAutorizacionCaja.APROBADA,
        resolved_at: new Date(),
      },
      include: {
        solicitante: { select: { id: true, username: true, nombres: true } },
        autorizador: { select: { id: true, username: true, nombres: true } },
      },
    });

    // Generar token temporal
    const token = await this.jwtService.signAsync(
      {
        sub: autorizador_id,
        tipo: 'autorizacion_caja',
        autorizacion_id: updated.id,
        operacion: updated.tipo,
        empresa_id,
      },
      { expiresIn: '30s', secret: process.env.JWT_SECRET },
    );

    this.logger.log(
      `Autorización aprobada: ${updated.id} por supervisor ${supervisor.username}`,
      'AutorizacionesCajaService',
    );

    await this.auditService.log({
      empresa_id,
      user_id: autorizador_id,
      action: 'AUTHORIZE',
      entity_type: 'autorizacion_caja',
      entity_id: updated.id,
      descripcion: `Autorización aprobada: ${updated.tipo} por ${supervisor.username}`,
      new_value: { estado: EstadoAutorizacionCaja.APROBADA, autorizador_id },
    });

    // Notificar en tiempo real
    this.posGateway.notificarAutorizacionAprobada(empresa_id, {
      autorizacion_id: updated.id,
      tipo: updated.tipo,
      token,
      supervisor: updated.autorizador as any,
      sesion_caja_id: updated.sesion_caja_id,
    });

    return {
      message: 'Autorización aprobada',
      data: updated,
      token,
      expires_in: 30,
    };
  }

  async rechazar(autorizacion_id: string, autorizador_id: string, motivo?: string, empresa_id?: string) {
    const autorizacion = await this.prisma.autorizaciones_caja.findUnique({
      where: { id: autorizacion_id },
    });

    if (!autorizacion) throw new NotFoundException('Autorización no encontrada');
    if (autorizacion.estado !== EstadoAutorizacionCaja.PENDIENTE)
      throw new BadRequestException('La autorización ya fue resuelta');

    const updated = await this.prisma.autorizaciones_caja.update({
      where: { id: autorizacion_id },
      data: {
        autorizador_id,
        estado: EstadoAutorizacionCaja.RECHAZADA,
        motivo: motivo ?? autorizacion.motivo,
        resolved_at: new Date(),
      },
      include: {
        solicitante: { select: { id: true, username: true, nombres: true } },
        autorizador: { select: { id: true, username: true, nombres: true } },
      },
    });

    this.logger.log(`Autorización rechazada: ${updated.id}`, 'AutorizacionesCajaService');

    await this.auditService.log({
      empresa_id: empresa_id ?? autorizacion.empresa_id,
      user_id: autorizador_id,
      action: 'REJECT',
      entity_type: 'autorizacion_caja',
      entity_id: updated.id,
      descripcion: `Autorización rechazada: ${updated.tipo} | Motivo: ${motivo ?? 'Sin motivo'}`,
      new_value: { estado: EstadoAutorizacionCaja.RECHAZADA, motivo },
    });

    // Notificar en tiempo real
    this.posGateway.notificarAutorizacionRechazada(empresa_id ?? autorizacion.empresa_id, {
      autorizacion_id: updated.id,
      tipo: updated.tipo,
      supervisor: updated.autorizador as any,
      motivo_rechazo: motivo,
      sesion_caja_id: updated.sesion_caja_id,
    });

    return { message: 'Autorización rechazada', data: updated };
  }

  async getPendientes(empresa_id: string) {
    const pendientes = await this.prisma.autorizaciones_caja.findMany({
      where: {
        empresa_id,
        estado: EstadoAutorizacionCaja.PENDIENTE,
        OR: [{ expires_at: null }, { expires_at: { gte: new Date() } }],
      },
      include: {
        solicitante: { select: { id: true, username: true, nombres: true, apellidos: true } },
        sesiones_caja: {
          select: {
            id: true,
            caja_id: true,
            cajas: { select: { id: true, descripcion: true } },
          },
        },
      },
      orderBy: { created_at: 'desc' },
    });

    return { data: pendientes, total: pendientes.length };
  }

  async getHistorial(empresa_id: string, page = 1, limit = 20) {
    const take = Math.max(1, Math.min(100, limit));
    const skip = (Math.max(1, page) - 1) * take;

    const [items, total] = await this.prisma.$transaction([
      this.prisma.autorizaciones_caja.findMany({
        where: { empresa_id },
        include: {
          solicitante: { select: { id: true, username: true, nombres: true } },
          autorizador: { select: { id: true, username: true, nombres: true } },
        },
        orderBy: { created_at: 'desc' },
        skip,
        take,
      }),
      this.prisma.autorizaciones_caja.count({ where: { empresa_id } }),
    ]);

    return {
      data: items,
      total,
      page,
      limit: take,
      lastPage: Math.max(1, Math.ceil(total / take)),
    };
  }

  async validarToken(token: string) {
    try {
      const payload = await this.jwtService.verifyAsync(token, {
        secret: process.env.JWT_SECRET,
      });
      if (payload.tipo !== 'autorizacion_caja') {
        throw new BadRequestException('Token no es de autorización de caja');
      }
      return { valid: true, payload };
    } catch (error) {
      if (error instanceof BadRequestException) throw error;
      throw new BadRequestException('Token de autorización inválido o expirado');
    }
  }
}
