import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
import { Prisma, type codigos_verificacion } from '@prisma/client';
import { NotificacionesService } from 'src/notificaciones/notificaciones.service';
import { PrismaService } from 'src/prisma/prisma.service';
import { CreateCodigoVerificacionDto } from './dto/create-codigo-verificacion.dto';
import { EnviarCodigoDto } from './dto/reenviar-codigo.dto';
import { VerificarCodigoDto } from './dto/verificar-codigo.dto';

// Máximo de intentos fallidos antes de invalidar un código OTP.
const MAX_INTENTOS_OTP = 5;

@Injectable()
export class CodigosVerificacionService {
  constructor(
    private readonly prisma: PrismaService,
    private readonly notificacionesService: NotificacionesService,
  ) {}

  async create(dto: CreateCodigoVerificacionDto): Promise<codigos_verificacion> {
    const code = Math.floor(100000 + Math.random() * 900000).toString();

    const data: Record<string, unknown> = {
      codigo: code,
      tipo: dto.tipo,
      expiracion: new Date(Date.now() + 10 * 60 * 1000),
    };
    if (dto.usuario_id) data.usuario_id = dto.usuario_id;
    if (dto.celular) data.celular = dto.celular;
    if (dto.email) data.email = dto.email;

    const codigoVerificacion: codigos_verificacion = await this.prisma.codigos_verificacion.create({
      data: data as unknown as Prisma.codigos_verificacionUncheckedCreateInput,
    });
    return codigoVerificacion;
  }

  async verificar(dto: VerificarCodigoDto): Promise<{ message: string; data: any[] }> {
    try {
      /*
      if (dto.usuario_id) {
        const codigoVerificacion = await this.prisma.codigos_verificacion.findFirst({
          where: {
            tipo: dto.tipo,
            usado: false,
          },
          orderBy: {
            created_at: 'desc',
          },
        });
        const usuario = await this.prisma.usuario.findUnique({
          where: {
            id: dto.usuario_id,
          },
        });

        if (!usuario) throw new NotFoundException('Usuario no encontrado');
        if (!codigoVerificacion) throw new NotFoundException('No se encontró un código de verificación válido');
        if (codigoVerificacion.codigo !== dto.codigo)
          throw new BadRequestException('El código ingresado es incorrecto');

        if (new Date() > codigoVerificacion.expiracion)
          throw new BadRequestException('El código ha expirado. Solicita uno nuevo');
        if (!usuario?.empresa_id) throw new NotFoundException('Empresa no encontrada');

        const empresa = await this.prisma.empresas.findUnique({
          where: {
            id: usuario?.empresa_id,
          },
        });

        if (!empresa) throw new NotFoundException('Empresa no encontrada');
        await this.prisma.codigos_verificacion.update({
          where: {
            id: codigoVerificacion.id,
          },
          data: {
            usado: true,
          },
        });

        await this.prisma.usuario.update({
          where: {
            id: dto.usuario_id,
          },
          data: {
            verificado: true,
          },
        });

        const dataUpdate: { email_validado?: boolean; celular_validado?: boolean } = {};
        if (dto.tipo === 'email') {
          dataUpdate.email_validado = true;
        }
        if (dto.tipo === 'celular') {
          dataUpdate.celular_validado = true;
        }
        await this.prisma.empresas.update({
          where: {
            id: usuario?.empresa_id,
          },
          data: dataUpdate,
        });

        return {
          message: 'Código verificado exitosamente',
          data: [],
        };
      }
      */

      if (!dto.celular) throw new BadRequestException('Celular es obligatorio');
      const codigoVerificacion = await this.prisma.codigos_verificacion.findFirst({
        where: {
          celular: dto.celular,
          tipo: dto.tipo,
          usado: false,
        },
        orderBy: {
          created_at: 'desc',
        },
      });

      if (!codigoVerificacion) throw new NotFoundException('No se encontró un código de verificación válido');

      if (new Date() > codigoVerificacion.expiracion)
        throw new BadRequestException('El código ha expirado. Solicita uno nuevo');

      if (codigoVerificacion.codigo !== dto.codigo) {
        // Anti fuerza-bruta: contar el intento fallido e invalidar el código al
        // superar el máximo (independiente de la IP; complementa el rate-limit).
        const intentos = (codigoVerificacion.intentos ?? 0) + 1;
        const agotado = intentos >= MAX_INTENTOS_OTP;
        await this.prisma.codigos_verificacion.update({
          where: { id: codigoVerificacion.id },
          data: { intentos, usado: agotado ? true : undefined },
        });
        if (agotado) {
          throw new BadRequestException(
            'Demasiados intentos incorrectos. El código fue invalidado, solicitá uno nuevo.',
          );
        }
        throw new BadRequestException('El código ingresado es incorrecto');
      }

      await this.prisma.codigos_verificacion.update({
        where: {
          id: codigoVerificacion.id,
        },
        data: {
          usado: true,
        },
      });

      return {
        message: 'Código verificado exitosamente',
        data: [],
      };
    } catch (error) {
      if (error instanceof NotFoundException || error instanceof BadRequestException) {
        throw error;
      }
      throw new BadRequestException('Error al verificar el código');
    }
  }

  async enviar(dto: EnviarCodigoDto): Promise<{ message: string; expiracion: string; data: any }> {
    try {
      if (dto.tipo === 'celular') {
        if (!dto.celular) throw new BadRequestException('Celular es obligatorio');

        const existingEmpresa = await this.prisma.usuario.findFirst({
          where: {
            username: dto.celular,
            verificado: true,
          },
        });
        if (existingEmpresa) throw new BadRequestException('Celular ya verificado');
        // Invalidar códigos anteriores no usados del mismo tipo
        await this.prisma.codigos_verificacion.updateMany({
          where: {
            celular: dto.celular,
            tipo: dto.tipo,
            usado: false,
          },
          data: {
            usado: true,
          },
        });

        // Crear nuevo código
        const nuevoCodigo = await this.create({
          tipo: dto.tipo,
          celular: dto.celular,
        });

        // Enviar email con el nuevo código
        await this.notificacionesService.enviarNotificacion(dto.tipo, dto.email ?? '', dto.celular, nuevoCodigo.codigo);
        return {
          message: 'Código enviado exitosamente',
          expiracion:
            nuevoCodigo.expiracion.toISOString().split('T')[0] +
            ' ' +
            nuevoCodigo.expiracion.toTimeString().split(' ')[0],
          data: [],
        };
      }

      if (dto.tipo === 'email') {
        if (!dto.email) throw new BadRequestException('Email es obligatorio');

        await this.prisma.codigos_verificacion.updateMany({
          where: {
            email: dto.email,
            tipo: dto.tipo,
            usado: false,
          },
          data: {
            usado: true,
          },
        });

        const nuevoCodigo = await this.create({
          tipo: dto.tipo,
          email: dto.email,
          celular: dto.celular ?? '',
        });

        await this.notificacionesService.enviarNotificacion(dto.tipo, dto.email, dto.celular ?? '', nuevoCodigo.codigo);
        return {
          message: 'Código enviado exitosamente',
          expiracion:
            nuevoCodigo.expiracion.toISOString().split('T')[0] +
            ' ' +
            nuevoCodigo.expiracion.toTimeString().split(' ')[0],
          data: [],
        };
      }
      return {
        message: 'Código enviado exitosamente',
        expiracion: '',
        data: [],
      };
    } catch (error) {
      console.log(error);
      if (error instanceof NotFoundException || error instanceof BadRequestException) {
        throw error;
      }
      throw new BadRequestException('Error al enviar el código');
    }
  }
}
