import { Inject, Injectable, forwardRef } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import * as moment from 'jalali-moment';
import { ErrorService } from 'src/error/error.service';
import {
  applyFiltersToBuilder,
  applySortingToBuilder,
  paginationResult,
} from 'src/utils';
import { Between, MoreThan, Repository, Not } from 'typeorm';
import { LogAction, LogType } from '../log/log.interface';
import { LogService } from '../log/log.service';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateAvatarDto } from './dto/update-avatar.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { UpdateWalletUserDto } from './dto/update-wallet-user.dto';
import { RoleIds, Roles, UserEntity } from './entities/user.entity';

import { Workbook } from 'exceljs';
import * as tmp from 'tmp';
import * as xlsx from 'xlsx';
import { CarService } from '../car/car.service';
import { CarType } from '../car/entities/car.entity';

@Injectable()
export class UserService {
  constructor(
    @InjectRepository(UserEntity)
    private userRepository: Repository<UserEntity>,
    private logService: LogService,
    private error: ErrorService,

    @Inject(forwardRef(() => CarService))
    private readonly carService: CarService,
  ) {}

  /**
   * -------------------------------------------------------
   * POST /users/import/excel/admin
   */
  async importExcel(file: Express.Multer.File, operatorUserId: string) {
    const file_path = file.path;

    const workbook = xlsx.readFile(file_path);

    const sheet_name = workbook.SheetNames[0];
    const sheet = workbook.Sheets[sheet_name];

    const range = 'A1:K1000000';
    const data = xlsx.utils.sheet_to_json(sheet, { range, defval: null });

    const results = [];

    for (let i = 0; i < data.length; i++) {
      const row = data[i];
      const columns: any = Object.values(row);

      try {
        let targetUser: any = await this.userRepository.findOne({
          mobile: columns[4].trim(),
        });
        if (!targetUser) {
          targetUser = await this.addUser(
            {
              ...(columns[1] !== '.' && { name: (columns[1] || '').trim() }),
              surName: (columns[2] || '').trim(),
              mobile: columns[4].trim(),
              nationalCode: columns[3].trim(),
              address: columns[10] || null,
              isActive: true,
              roleId: RoleIds.User,
              password: null,
              email: null,
              isImported: true,
            },
            operatorUserId,
          );

          const cleanPlateNumber = columns[7]
            .trim()
            .replace('-', 'ایران')
            .replace(/ /g, '***');

          const makerBrand = await this.carService.findMakerBrandByName(
            columns[6].trim(),
          );

          if (makerBrand) {
            await this.carService.addCar(
              {
                userId: targetUser.id,
                makerBrandId: makerBrand.id,
                number: cleanPlateNumber,
                type: CarType.private,
              },
              operatorUserId,
            );
            results.push([...columns, 'خودروی موردنظر ثبت شد', 'success']);
          } else {
            results.push([...columns, 'مدل خودرو یافت نشد', 'error']);
          }
        }
      } catch (e) {
        results.push([...columns, e?.message || 'Error', 'error']);
      }
    }

    return results;
  }

  /**
   * -------------------------------------------------------
   */
  async exportExcel(filters = null, customFilters = null) {
    let builder = this.userRepository.createQueryBuilder('user');

    builder.leftJoin('user.role', 'role');
    builder.leftJoin('user.customer', 'customer');

    builder
      .select([
        'user.id',
        'user.name',
        'user.surName',
        'user.gender',
        'user.birthday',
        'user.mobile',
        'user.phone',
        'user.nationalCode',
        'user.isActive',
        'user.virtualWallet',
        'user.realWallet',
        'user.lastActivityAt',
        'user.createdAt',

        'role.id',
        'role.name',

        'customer.id',
        'customer.name',
      ])
      .orderBy('user.createdAt', 'DESC');

    builder = applyFiltersToBuilder(builder, filters);

    if (customFilters?.mobilePhone) {
      builder.andWhere(
        '(user.mobile LIKE :mobile OR user.phone LIKE :mobile)',
        {
          mobile: `${customFilters.mobilePhone}%`,
        },
      );
    }
    if (customFilters?.fullName) {
      builder.andWhere(`CONCAT(user.name, ' ', user.surName) LIKE :name`, {
        name: `%${customFilters.fullName}%`,
      });
    }
    if (customFilters?.hasWallet) {
      builder.andWhere('user.realWallet + user.virtualWallet > 0');
    }

    const items = await builder.getMany();

    // Create excel file
    const workbook = new Workbook();
    const worksheet = workbook.addWorksheet(`sheet1`);
    worksheet.views = [{ rightToLeft: true }];

    worksheet.addRow([
      'نام',
      'نام خانوادگی',
      'جنسیت',
      'تاریخ تولد',
      'شماره همراه',
      'کد ملی',
      'نقش',
      'وضعیت',
      'کیف پول حقیقی',
      'کیف پول مجازی',
      'شرکت',
      'آخرین فعالیت',
      'تاریخ ثبت',
    ]);

    items.forEach((item) => {
      worksheet.addRow([
        item.name,
        item.surName,
        item.gender,
        item.birthday,
        item.mobile,
        item.nationalCode,
        item.role?.name,
        item.isActive ? 'فعال' : 'غیرفعال',
        item.realWallet,
        item.virtualWallet,
        item.customer?.name || '',
        item.lastActivityAt
          ? moment(item.lastActivityAt).format('jYYYY-jMM-jDD')
          : '',
        item.createdAt ? moment(item.createdAt).format('jYYYY-jMM-jDD') : '',
      ]);
    });

    worksheet.getRow(1).fill = {
      type: 'pattern',
      pattern: 'solid',
      fgColor: { argb: 'FFBFBFBF' },
    };

    worksheet.getColumn(1).width = 15;
    worksheet.getColumn(2).width = 15;
    worksheet.getColumn(3).width = 14;
    worksheet.getColumn(4).width = 15;
    worksheet.getColumn(5).width = 10;
    worksheet.getColumn(6).width = 9;
    worksheet.getColumn(7).width = 13;
    worksheet.getColumn(8).width = 13;
    worksheet.getColumn(9).width = 20;
    worksheet.getColumn(10).width = 12;
    worksheet.getColumn(11).width = 12;

    // Save on tmp and export excel file
    try {
      const tmpobj = tmp.fileSync({
        mode: 0o644,
        prefix: `discount_${moment().format('YYYY-MM-DD')}`,
        postfix: '.xlsx',
        discardDescriptor: true,
      });
      await workbook.xlsx.writeFile(tmpobj.name);
      return tmpobj.name;
    } catch (err) {
      console.log(err);
      this.error.internalServerError(['در تولید فایل اکسل خطایی رخ داده است']);
    }
  }

  /**
   * -------------------------------------------------------
   * Admin
   * Get user list
   */
  async getAll(
    page = 1,
    limit = 20,
    sorts = null,
    filters = null,
    customFilters = null,
  ) {
    let builder = this.userRepository.createQueryBuilder('user');

    builder.leftJoin('user.role', 'role');
    builder.leftJoin('user.customer', 'customer');

    builder.select([
      'user.id',
      'user.name',
      'user.surName',
      'user.gender',
      'user.birthday',
      'user.mobile',
      'user.avatar',
      'user.phone',
      'user.nationalCode',
      'user.isActive',
      'user.virtualWallet',
      'user.realWallet',
      'user.customerId',
      'user.lastActivityAt',
      'user.createdAt',

      'role.id',
      'role.name',

      'customer.id',
      'customer.name',
    ]);

    builder = applyFiltersToBuilder(builder, filters);

    if (customFilters?.mobilePhone) {
      builder.andWhere(
        '(user.mobile LIKE :mobile OR user.phone LIKE :mobile)',
        {
          mobile: `${customFilters.mobilePhone}%`,
        },
      );
    }
    if (customFilters?.fullName) {
      builder.andWhere(`CONCAT(user.name, ' ', user.surName) LIKE :name`, {
        name: `%${customFilters.fullName}%`,
      });
    }
    if (customFilters?.hasWallet) {
      builder.andWhere('user.realWallet + user.virtualWallet > 0');
    }

    if (sorts) {
      builder = applySortingToBuilder(builder, sorts);
    } else {
      builder.orderBy('user.createdAt', 'DESC');
    }

    const [items, totalItems] = await builder
      .take(limit) // LIMIT
      .skip((page - 1) * limit) // OFFSET
      .getManyAndCount();

    return {
      items,
      pagination: paginationResult(page, limit, totalItems),
    };
  }

  /**
   * -------------------------------------------------------
   */
  async getUserPanelAccessByEmailOrMobile(emailOrMobile: string) {
    return await this._findUserPanelAccessBuilder()
      .andWhere('(user.email = :eom OR user.mobile = :eom)', {
        eom: emailOrMobile,
      })
      .getOne();
  }

  /**
   * -------------------------------------------------------
   */
  async findPanelAccessById(userId: string) {
    return await this._findUserActiveBuilder()
      .andWhere({ id: userId })
      .getOne();
  }

  /**
   * -------------------------------------------------------
   */
  async findById(userId: string) {
    return await this.userRepository.findOne({
      where: { id: userId },
      relations: ['customer'],
    });
  }

  /**
   * -------------------------------------------------------
   */
  async findByUserKey(userKey: string) {
    return await this.userRepository.findOne({ userKey });
  }

  /**
   * -------------------------------------------------------
   * Admin
   * Get user by id
   */
  async getById(userId: string) {
    const builder = this.userRepository.createQueryBuilder('user');

    builder
      .leftJoin('user.role', 'role')
      .leftJoin('role.roleMapPermissions', 'roleMapPermissions')
      .leftJoin('roleMapPermissions.permission', 'permission')
      .leftJoin('user.customer', 'customer');

    builder.select([
      'user.id',
      'user.name',
      'user.surName',
      'user.gender',
      'user.birthday',
      'user.email',
      'user.phone',
      'user.mobile',
      'user.nationalCode',
      'user.roleId',
      'user.isActive',
      'user.avatar',
      'user.virtualWallet',
      'user.realWallet',
      'user.bankName',
      'user.accountNumber',
      'user.cardNumber',
      'user.sheba',
      'user.customerId',
      'user.lastActivityAt',
      'user.createdAt',

      'role.id',
      'role.name',

      'roleMapPermissions.id',

      'permission.id',
      'permission.type',

      'customer.id',
      'customer.name',
    ]);

    builder.where({ id: userId });

    const user = await builder.getOne();
    return {
      ...user,
      roleType: Roles[user.roleId],
      fullName: `${user.name} ${user.surName}`.trim(),
    };
  }

  /**
   * -------------------------------------------------------
   * Add user
   */
  async addUser(dto: CreateUserDto, operatorUserId: string) {
    const newUser = new UserEntity();
    for (const key in dto) {
      newUser[key] = dto[key];
    }
    // hard code
    newUser.createdAt = new Date();
    newUser.updatedAt = new Date();
    newUser.isAdmin = dto.roleId === RoleIds.Admin;
    newUser.isServiceman = dto.roleId === RoleIds.ServiceMan;

    const { identifiers } = await this.userRepository
      .createQueryBuilder()
      .insert()
      .values(newUser)
      .execute();

    const newUserId = identifiers[0].id;
    const newRecord = await this.getById(newUserId);

    // Adding a log
    await this.logService.add({
      type: LogType.user,
      action: LogAction.insert,
      operatorUserId,
      message: `پروفایل ${newUser.name} ${newUser.surName} ایجاد شد`,
      affectedId: newUserId,
      item: newUser,
    });

    return newRecord;
  }

  /**
   * -------------------------------------------------------
   */
  async updateAvatar(
    id: string,
    dto: UpdateAvatarDto,
    file: Express.Multer.File,
  ) {
    if (file) {
      dto.file = `/uploads/avatars/${file.filename}`;
    }
    return await this.userRepository
      .createQueryBuilder()
      .update()
      .set({ avatar: dto.file })
      .where({ id })
      .execute();
  }

  /**
   * -------------------------------------------------------
   */
  async updateUser(id: string, dto: UpdateUserDto, operatorUserId: string) {
    const user = await this.userRepository.findOne(id);
    const oldItem = { ...user };

    // It should not be changed, if the password is empty
    if (!dto.password) {
      delete dto.password;
    }

    Object.keys(dto).forEach((field) => {
      user[field] = dto[field];
    });
    user.updatedAt = new Date();

    await user.save();

    // Adding a log
    await this.logService.add({
      type: LogType.user,
      action: LogAction.update,
      operatorUserId,
      message: `اطلاعات پروفایل ${user.name} ${user.surName} ویرایش شد`,
      affectedId: user.id,
      oldItem,
      item: dto,
    });
  }

  /**
   * -------------------------------------------------------
   */
  async updateWallet(userId, amount) {
    const { realWallet } = await this.userRepository.findOne(userId);
    await this.userRepository
      .createQueryBuilder()
      .update()
      .set({ realWallet: realWallet + amount })
      .where({ id: userId })
      .execute();
  }

  /**
   * -------------------------------------------------------
   */
  async updateWalletUser(id: string, dto: UpdateWalletUserDto) {
    const user = await this.userRepository.findOne({
      where: { id },
      select: ['id', 'realWallet', 'virtualWallet'],
    });
    console.log(user);

    const newRealWallet = user.realWallet + dto.changeRealWallet;
    const newVirtualWallet = user.virtualWallet + dto.changeVirtualWallet;

    if (newRealWallet < 0) {
      this.error.unprocessableEntity([
        'اعتبار نهایی کیف پول حقیقی نمی تواند کمتر از صفر باشد',
      ]);
    }
    if (newVirtualWallet < 0) {
      this.error.unprocessableEntity([
        'اعتبار نهایی کیف پول مجازی نمی تواند کمتر از صفر باشد',
      ]);
    }

    user.realWallet = newRealWallet;
    user.virtualWallet = newVirtualWallet;
    user.updatedAt = new Date();

    await user.save();
    return true;
  }
  /**
   * -------------------------------------------------------
   */
  private _findUserPanelAccessBuilder() {
    return this.userRepository
      .createQueryBuilder('user')
      .andWhere({ isActive: true, deleted: false })
      .innerJoinAndSelect('user.role', 'role')
      .innerJoinAndSelect('role.roleMapPermissions', 'roleMapPermissions')
      .innerJoinAndSelect('roleMapPermissions.permission', 'permission')
      .andWhere({ roleId: Not(RoleIds.User) });
  }

  /**
   * -------------------------------------------------------
   */
  private _findUserActiveBuilder() {
    return this.userRepository
      .createQueryBuilder('user')
      .andWhere({ isActive: true, deleted: false })
      .innerJoinAndSelect('user.role', 'role')
      .leftJoinAndSelect('role.roleMapPermissions', 'roleMapPermissions')
      .leftJoinAndSelect('roleMapPermissions.permission', 'permission');
  }

  /**
   * -------------------------------------------------------
   */
  async getFull(where = {}) {
    return await this.userRepository.find({
      select: ['id', 'name', 'surName', 'gender', 'mobile'],
      where: { isActive: true, deleted: false, ...where },
    });
  }

  /**
   * -------------------------------------------------------
   * show chart of users
   * GET /users/statistic/chart/admin
   */

  async statisticChart(from, to, type = 'daily') {
    const list = await this.userRepository
      .createQueryBuilder()
      .groupBy('DAY(createdAt), MONTH(createdAt)')
      .select([
        'COUNT(id) AS cnt',
        'DAY(createdAt) AS day',
        'MONTH(createdAt) AS month',
        'DATE(createdAt) AS date',
      ])
      .andWhere('deleted = 0 AND isActive = 1')
      .andWhere({ roleId: RoleIds.User })
      .andWhere('DATE(createdAt) BETWEEN :from AND :to', {
        from,
        to,
      })
      .orderBy('createdAt', 'ASC')
      .getRawMany();

    const mapping = list.map((item) => ({
      count: +item.cnt,
      label: moment(item.date).locale('fa').format('D (ddd)'),
      date: item.date,
    }));

    if (type === 'daily') {
      return mapping;
    }

    const obj = {};
    mapping.forEach((item) => {
      const m = moment(item.date).format('jM');

      obj[+m] = {
        label: moment(item.date).locale('fa').format('MMMM'),
        month: +m,
        count: (obj[+m]?.count || 0) + item.count,
      };
    });

    return Object.values(obj).sort((a: any, b: any) => a.month - b.month);
  }

  /**
   * -------------------------------------------------------
   * Show count of users
   * GET /users/statistic/admin
   */
  async statistic() {
    const serviceMans = await this.userRepository.count({
      roleId: RoleIds.ServiceMan,
      isActive: true,
      deleted: false,
    });

    const activeCustomer = await this.userRepository.count({
      roleId: RoleIds.User,
      isActive: true,
      deleted: false,
    });

    const inactiveCutomer = await this.userRepository.count({
      roleId: RoleIds.User,
      isActive: false,
      deleted: false,
    });

    const startOfCurrentMonth = moment().startOf('month').toDate();
    const startOfLastMonth = moment()
      .startOf('month')
      .subtract(1, 'month')
      .toDate();

    const monthCustomer = await this.userRepository.count({
      roleId: RoleIds.User,
      isActive: true,
      deleted: false,
      createdAt: MoreThan(startOfCurrentMonth), // now - 1 month age
    });
    const lastMonthCustomer = await this.userRepository.count({
      roleId: RoleIds.User,
      isActive: true,
      deleted: false,
      createdAt: Between(startOfLastMonth, startOfCurrentMonth), // 1 month ago - 2 month ago
    });

    return {
      serviceMans,
      activeCustomer,
      inactiveCutomer,
      totalCustomer: activeCustomer + inactiveCutomer,
      monthCustomer,
      lastMonthCustomer,
    };
  }

  /**
   * -------------------------------------------------------
   * soft delete
   */
  async deleteUser(id: string, operatorUserId: string) {
    const user = await this.userRepository.findOne(id);

    user.deleted = true;
    user.updatedAt = new Date();
    await user.save();

    // Adding a log
    await this.logService.add({
      type: LogType.user,
      action: LogAction.delete,
      operatorUserId,
      message: `پروفایل ${user.name} ${user.surName} حذف شد`,
      affectedId: user.id,
      item: user,
    });

    return true;
  }

  /**
   * --------------------------------------------------------
   */
  async reduceBothWallets(userId: string, amount: number) {
    if (!amount) return;

    const user = await this.userRepository.findOne(userId);

    let newVirtualWallet = user.virtualWallet;
    let newRealWallet = user.realWallet;
    let usedWalletAmount = amount;

    if (usedWalletAmount <= newVirtualWallet) {
      newVirtualWallet -= usedWalletAmount;
      usedWalletAmount = 0;
    } else {
      usedWalletAmount -= newVirtualWallet;
      newVirtualWallet = 0;
    }

    // Reduce the remaining amount (if any) from realWallet
    newRealWallet -= usedWalletAmount;

    user.virtualWallet = newVirtualWallet;
    user.realWallet = newRealWallet;
    await user.save();
  }
}
