import { Injectable } from '@nestjs/common';
import { EntityManager, Repository } from 'typeorm';
import { UserEntity } from '../entities/user.entity';
import { ChangeUserProfileDTO } from './dto/user-role-dto';
import { UserDTO } from '../common/dto/user-dto';
import { AppTokenEntity } from '../entities/app-token.entity';
import { InjectRepository } from '@nestjs/typeorm';
import { ProjectAccess, checkProjectAccess } from '../utils/project-access';
import { ApiErrorCodes } from '../common/error/api-error-codes';
import { ApiError } from '../common/error/api-error';
import { v4 as uuidv4 } from 'uuid';
import { NotificationReadResultDTO } from '../notifier/dto/notification-dto';
import { NotifierService } from '../notifier/notifier.service';
import { SendNotificationDTO } from './dto/notification-dto';
import validator from 'validator';
import { ConfigService } from '@nestjs/config';
// eslint-disable-next-line @typescript-eslint/no-var-requires
const crypto = require('crypto');

type UserData = {
  name: string;
  email: string;
  id: number;
};

@Injectable()
export class UserService {
  constructor(
    @InjectRepository(AppTokenEntity)
    private readonly appTokenRepo: Repository<AppTokenEntity>,
    private readonly notifierService: NotifierService,
    private readonly configService: ConfigService,
  ) {}

  async getUser(
    tx: EntityManager,
    userId: number,
  ): Promise<UserEntity | undefined> {
    const user_repo = tx.getRepository(UserEntity);
    return await user_repo.findOne({
      id: userId,
    });
  }

  async changeUserProfile(
    tx: EntityManager,
    userId: number,
    params: ChangeUserProfileDTO,
  ): Promise<UserEntity | undefined> {
    const user_repo = tx.getRepository(UserEntity);
    await user_repo.update(
      {
        id: userId,
      },
      params,
    );
    return this.getUser(tx, userId);
  }

  async createUserIfNotExists(
    tx: EntityManager,
    user: UserData,
  ): Promise<UserEntity> {
    const user_repo = tx.getRepository(UserEntity);
    let user_entity = await user_repo.findOne({
      id: user.id,
    });
    if (!user_entity) {
      user_entity = {
        id: user.id,
        name: user.name,
        email: user.email,
      };
      await user_repo.insert(user_entity);
    }
    return user_entity;
  }

  async setApiKey(
    user: UserDTO,
    has_api_key: boolean,
    project: ProjectAccess,
  ): Promise<string | null> {
    if (!project.projectLicense.features.apiAccess) {
      throw new ApiError(
        "User hasn't PRO license",
        ApiErrorCodes.ACCESS_DENIED,
      );
    }
    const new_app_token_row = new AppTokenEntity();
    new_app_token_row.userId = user.id;
    let new_api_key: string | null = null;
    if (has_api_key) {
      const temp_bf = await this._getRandomBytes(60);
      new_api_key = temp_bf.toString('base64').replace(/[\/+=]/g, 'x');
      new_app_token_row.id = uuidv4();
      new_app_token_row.token = new_api_key;
      new_app_token_row.projectId = project.projectId;
      await this.appTokenRepo.upsert(
        [new_app_token_row],
        ['userId', 'projectId'],
      );
      return new_api_key;
    } else {
      await this.appTokenRepo.delete({
        userId: user.id,
        projectId: project.projectId,
      });
    }
    return null;
  }

  async getApiKey(
    user: UserDTO,
    project: ProjectAccess,
  ): Promise<string | null> {
    if (!project.projectLicense.features.apiAccess) {
      throw new ApiError(
        "User hasn't PRO license",
        ApiErrorCodes.ACCESS_DENIED,
      );
    }
    let api_key: string | null = null;
    const res = await this.appTokenRepo.findOne({
      userId: user.id,
      projectId: project.projectId,
    });
    if (res) {
      api_key = res.token;
    }
    return api_key;
  }

  private async _getRandomBytes(count: number): Promise<any> {
    return new Promise((res, rej) => {
      crypto.randomBytes(count, (err: any, buf: any) => {
        if (err) rej(err);
        else res(buf);
      });
    });
  }

  public async sendNotification(
    project_access: ProjectAccess,
    message: SendNotificationDTO,
  ) {
    if (!project_access.userRole) {
      throw new ApiError('Access denied', ApiErrorCodes.ACCESS_DENIED);
    }
    if (!project_access.userRole.isAdmin) {
      throw new ApiError('Access denied', ApiErrorCodes.ACCESS_DENIED);
    }

    const toUser = await this.getUser(
      this.appTokenRepo.manager,
      message.toUserId,
    );
    if (!toUser) {
      throw new ApiError('User not found', ApiErrorCodes.ENTITY_NOT_FOUND);
    }

    const toUserProjectAccess = await checkProjectAccess(
      this.appTokenRepo.manager.connection,
      toUser.id,
      project_access.projectId,
    );
    if (!toUserProjectAccess.userRole) {
      throw new ApiError('User not found', ApiErrorCodes.ENTITY_NOT_FOUND);
    }

    try {
      await this.notifierService.sendEmail(toUser.email, {
        await: true,
        subject: 'Уведомление',
        immediately: true,
        text: message.text
          .split('\n')
          .map((t) => validator.escape(t))
          .join('<br>'),
      });
    } catch (err) {
      console.error(err);
    }
    const has_tg_bot_url = this.configService.get('notification.tg_bot_url');
    if (has_tg_bot_url) {
      try {
        await this.notifierService.sendNotificationToTelegramm(
          toUser.id,
          message.text,
        );
      } catch (err) {
        console.error(err);
      }
    }

    try {
      await this.notifierService.sendNotificationsToClient(
        toUser.id,
        project_access.projectId,
        message.text,
        [],
      );
    } catch (err) {
      console.error(err);
    }
    return {};
  }
}
