/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
import { CreateUserDto, UpdateUserDto } from '@dtos/create-user.dto';
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { User, UserDocument } from '@schemas/user.schema';
import { MultimediaService } from '@services/multimedia.service';
import { isValidObjectId, Model, Types } from 'mongoose';
import * as bcrypt from 'bcryptjs';
import { UserLogs, UserLogsDocument } from '@schemas/user-logs.schema';
import { Request } from 'express';
import { DIR } from '@common/constants/game-types.constant';

@Injectable()
export class UsersService {
  private dir = DIR + '/users/';

  constructor(
    @InjectModel('users') private userModel: Model<UserDocument>,
    @InjectModel('userlogs') private userlogsModel: Model<UserLogsDocument>,
    private readonly mediaService: MultimediaService,
  ) {}

  async findAll(
    userId: string,
    page: number = 1,
    limit: number = 20,
  ): Promise<User[]> {
    const user = await this.userModel.findById(userId).exec();
    if (!user) {
      throw new HttpException('Datos incorrectos', HttpStatus.NOT_FOUND);
    }

    if (page < 0) {
      return await this.userModel
        .find({ businessId: user.businessId })
        .sort({ createdAt: -1 })
        .populate('businessId')
        .exec();
    } else {
      const skip = (page - 1) * limit;
      return await this.userModel
        .find({ businessId: user.businessId })
        .sort({ createdAt: -1 })
        .populate('businessId')
        .skip(skip)
        .limit(limit)
        .exec();
    }
  }

  async findAllAdmins(): Promise<User[]> {
    return await this.userModel
      .find({ role: 'super' })
      .sort({ createdAt: -1 })
      .populate('businessId')
      .exec();
  }

  async findAllForCron(businessId: any): Promise<any[]> {
    return await this.userModel
      .find({
        businessId: businessId,
        role: 'admin',
        token: { $ne: '' },
        active: true,
      })
      .exec();
  }

  async findAllLogs(
    userId: string,
    startDate: string | null,
    endDate: string | null,
    page: number = 1,
    limit: number = 20,
  ): Promise<UserLogs[]> {
    const user = await this.userModel.findById(userId).exec();
    if (!user) {
      throw new HttpException('Datos incorrectos', HttpStatus.NOT_FOUND);
    }

    const today = new Date();
    const start = startDate
      ? new Date(startDate)
      : new Date(today.setHours(0, 0, 0, 0));
    const end = endDate
      ? new Date(endDate)
      : new Date(today.setHours(23, 59, 59, 999));

    const query: any = {
      businessId: user.businessId,
      createdAt: {
        $gte: start,
        $lte: end,
      },
    };

    if (page < 0) {
      return await this.userlogsModel
        .find(query)
        .sort({ createdAt: -1 })
        .populate('userId')
        .exec();
    } else {
      const skip = (page - 1) * limit;
      return await this.userlogsModel
        .find(query)
        .sort({ createdAt: -1 })
        .populate('userId')
        .skip(skip)
        .limit(limit)
        .exec();
    }
  }

  async findOne(userId: string): Promise<User> {
    const user = await this.userModel
      .findById(userId)
      .populate('businessId')
      .exec();
    if (!user) {
      throw new HttpException('El  usuario no existe', HttpStatus.NOT_FOUND);
    }
    return user;
  }

  async updateToken(id: string, token: { token: string }): Promise<User> {
    const updatedUser = await this.userModel
      .findOneAndUpdate(
        { _id: id },
        { $set: { token: token.token } },
        { new: true },
      )
      .exec();

    if (!updatedUser) {
      throw new HttpException('El usuario no existe', HttpStatus.NOT_FOUND);
    }
    return updatedUser;
  }

  async create(
    createUser: CreateUserDto,
    photo: Express.Multer.File,
    userId: string,
    admin: boolean = false,
  ): Promise<User> {
    let businessId: any;

    if (!admin) {
      const userData = await this.userModel.findById(userId).exec();
      if (!userData) {
        throw new HttpException('Error creando usuario', HttpStatus.CONFLICT);
      }

      businessId = userData.businessId;
    }

    if (!userId) {
      throw new HttpException('Error creando usuario', HttpStatus.CONFLICT);
    }

    const email = createUser.email.toLowerCase().trim();
    const password = createUser.password;

    const existingUser = await this.userModel.findOne({ email });
    if (existingUser) {
      throw new HttpException('El usuario ya existe', HttpStatus.BAD_REQUEST);
    }

    let result = 'default.jpg';
    if (photo) {
      result = await this.mediaService.saveImage(photo, this.dir);
    }

    const hashedPassword = await bcrypt.hash(password, 10);
    const user = new this.userModel({
      email,
      password: hashedPassword,
      photo: result,
      role: createUser.role ?? 'user',
      businessId: !admin ? businessId : new Types.ObjectId(userId),
    });
    return await user.save();
  }

  async update(
    updateUser: UpdateUserDto,
    photo: Express.Multer.File,
  ): Promise<User> {
    let result = 'default.jpg';
    if (photo) {
      result = await this.mediaService.saveImage(
        photo,
        this.dir,
        updateUser.photo,
      );

      updateUser = {
        ...updateUser,
        photo: result,
      };
    }

    if (updateUser.password && updateUser.password !== '') {
      const hashedPassword = await bcrypt.hash(updateUser.password, 10);
      updateUser = {
        ...updateUser,
        password: hashedPassword,
      };
    }

    const updatedUser = await this.userModel
      .findByIdAndUpdate(updateUser._id, updateUser, { new: true })
      .exec();
    if (!updatedUser) {
      throw new HttpException('El usuario no existe', HttpStatus.NOT_FOUND);
    }
    return updatedUser;
  }

  async remove(id: string): Promise<void> {
    if (!isValidObjectId(id)) {
      throw new HttpException('ID de usuario inválido', HttpStatus.BAD_REQUEST);
    }

    const deleted = await this.userModel.findById(id).exec();
    if (!deleted) {
      throw new HttpException('El usuario no existe', HttpStatus.NOT_FOUND);
    }

    await this.mediaService.removeImage(this.dir, deleted.photo);
    await this.userModel.findByIdAndDelete(id).exec();
  }

  async logout(userId: string, req: Request): Promise<void> {
    const userData = await this.userModel.findById(userId).exec();
    if (!userData) {
      throw new HttpException('Error creando logs', HttpStatus.CONFLICT);
    }

    if (userId !== '' && req) {
      await this.logAccess(
        userData.businessId as unknown as string,
        userId,
        'logout',
        req,
      );
    }
  }

  private async logAccess(
    businessId: string,
    userId: string,
    access: 'login' | 'logout',
    req: Request,
  ): Promise<UserLogs> {
    const ip = this.getClientIp(req);
    const log = new this.userlogsModel({ businessId, userId, ip, access });
    return log.save();
  }

  private getClientIp(req: Request): string {
    const forwarded = req.headers['x-forwarded-for'];
    return typeof forwarded === 'string'
      ? forwarded.split(',')[0].trim()
      : req.socket?.remoteAddress || '0.0.0.0';
  }
}
