import { Config, Inject, Provide } from '@midwayjs/core';
import { get, random } from 'lodash';
import { RoleService } from '../service/system/role.service';
import { RedisService } from '@midwayjs/redis';
import * as Crypto from 'crypto-js';
import { Context } from '@midwayjs/koa';
import { ServiceError, ErrorCode } from '../error/service.error';
import { InjectEntityModel } from '@midwayjs/typeorm';
import { Equal, Repository } from 'typeorm';
import { LoginDTO, LoginType, UPwdDTO } from '../interface/login.interface';
import { Admin } from '../entity/system/admin.entity';
import * as bcrypt from 'bcryptjs';
import { User } from '../entity/system/user.entity';
import { AliyunSmsService } from './thirdParty/aliyunSms.service';
import { EmailService } from './thirdParty/email.service';

@Provide()
export class LoginService {
  @Inject()
  roleService: RoleService;
  @Inject()
  ctx: Context;
  @Config('loginSign')
  loginSign;
  @Config('jwt.secret')
  jwtSecret;
  @Config('jwt.expiresIn')
  jwtExpiresIn;
  @Config('requestTimeLimit')
  timeLimit;
  @Config('loginSmsSign')
  loginSmsSign;
  @Config('pwdSmsSign')
  pwdSmsSign;
  @Config('regSmsSign')
  regSmsSign;

  @Inject()
  redisService: RedisService;

  @Inject()
  smsService: AliyunSmsService;
  @Inject()
  emailService: EmailService;

  @InjectEntityModel(Admin)
  adminModel: Repository<Admin>;
  @InjectEntityModel(User)
  userModel: Repository<User>;

  /**
   * 注册验证 验证码
   * @param phone 电话号码
   * @param code 验证码
   * @returns 
   */
  async checkRegCode(phone,code) {
    const redisKey = `${this.regSmsSign}:${phone}`
    const redisCode = await this.redisService.get(redisKey);
    if (!redisCode) throw new ServiceError(ErrorCode.REG_VALICODE_EXPIRES); // 验证码已过期
    if (`${redisCode}` !== `${code}`) throw new ServiceError(ErrorCode.REG_VALICODE_ERROR); // 验证码错误
    await this.redisService.del(redisKey);
    return true;
  }

  /**
   * 发送注册验证码
   * @param phone 电话号码
   */
  async sendRegCode(phone) {
    const redisKey = `${this.regSmsSign}:${phone}`
    const code = random(1000, 999999);
    await this.redisService.set(redisKey, code, 'EX', this.timeLimit);
    await this.smsService.send(phone, code);
  }

  /**
   * 验证:找回密码的验证是否正确
   * @param type 验证方式
   * @param to 手机号/邮箱地址
   * @param code 验证码
   */
  async checkPwdCode(type, to, code) {
    const redisKey = `${this.pwdSmsSign}:${type}:${to}`;
    const redisCode = await this.redisService.get(redisKey);
    if (!redisCode) throw new ServiceError(ErrorCode.PWD_VALICODE_EXPIRES); // 找回密码验证码已过期
    if (`${redisCode}` !== `${code}`) throw new ServiceError(ErrorCode.PWD_VALICODE_ERROR); // 找回密码验证码错误
    await this.redisService.del(redisKey);
    return true;
  }

  /**
   * 发送忘记密码的验证码
   * @param type 验证方式: phone/email
   * @param to 向 手机号/邮箱地址 发送
   */
  async sendPwdCode(type, to) {
    const redisKey = `${this.pwdSmsSign}:${type}:${to}`;
    const code = random(1000, 999999);
    await this.redisService.set(redisKey, code, 'EX', this.timeLimit);
    if (type === 'email') {
      // 发邮箱
      await this.emailService.sendPwdCode(to, code);
    } else if (type === 'phone') {
      // 发短信
      await this.smsService.send(to, code);
    }
  }

  /**
   * 发送登录验证码
   * @param phone 手机号
   * @param code 验证码
   */
  async sendSmsLoginCode(phone) {
    // code
    const code = random(1000, 999999);
    const redisKey = `${this.loginSmsSign}:${phone}`;
    // 如果以验证码超时为下条验证码发送的条件,则放开下2行注释
    // const hasCode = await this.redisService.get(redisKey);
    // if (hasCode) throw new ServiceError(ErrorCode.LOGIN_VALICODE_IS_EXISTS);
    // redis存起来用于验证
    await this.redisService.set(redisKey, code, 'EX', this.timeLimit);
    // sms发送短信
    await this.smsService.send(phone, code);
  }
  /**
   * 验证登录验证码
   * @param phone 手机号
   * @param code 验证码
   * @returns
   */
  async valiSmsCode(body) {
    const code = get(body, 'checkCode');
    const phone = get(body, 'phone');
    if (!code) throw new ServiceError(ErrorCode.PLEASE_INPUT_LOGIN_CODE);
    if (!phone) throw new ServiceError(ErrorCode.PLEASE_INPUT_LOGIN_PHONE);
    const redisKey = `${this.loginSmsSign}:${phone}`;
    const redisCode = await this.redisService.get(redisKey);
    if (!redisCode) throw new ServiceError(ErrorCode.LOGIN_VALICODE_EXPIRES); // 登录验证码已过期
    if (`${redisCode}` !== `${code}`) throw new ServiceError(ErrorCode.LOGIN_VALICODE_ERROR); // 登录验证码输入错误
    await this.redisService.del(redisKey);
    return true;
  }

  async tokenViewGetUserInfo(role: Array<string>, id: number) {
    const hasAdminRole = role.find(f => f === LoginType.Admin);
    let model;
    if (hasAdminRole) model = this.adminModel;
    else model = this.userModel;
    const user = await model.createQueryBuilder().where('id = :id', { id }).getOne();
    return user;
  }

  async onePointLogin(loginVo) {
    const { id, role } = loginVo;
    const rediskey = `${this.loginSign}:${role}:${id}`;
    // 随机字符串 验证用户登录用
    const str = this.randomStr();
    // 拼接成token内的登录code 加密前数据
    const val = `${rediskey}:${str}`;
    // 加密后存入token内
    const code = Crypto.AES.encrypt(val, this.jwtSecret).toString();
    // 设置redis登录记录 将 随机字符串存在redis中. 验证时需要将加密的字符串解密,解密后和redis中的对比验证
    await this.redisService.set(rediskey, str, 'EX', this.jwtExpiresIn);
    return code;
  }

  /**
   * 账密登录
   * @param data 用户名和密码
   * @param type 用户类型
   * @returns 用户信息/空值
   */
  async loginByAccount(data: LoginDTO, type: LoginType) {
    let model;
    if (type === LoginType.Admin) model = this.adminModel;
    else model = this.userModel;
    const user = await model.createQueryBuilder('t').where('t.account = :account', { account: data.account }).addSelect('t.password').getOne();
    if (!user) throw new ServiceError(ErrorCode.USER_NOT_FOUND);
    await this.checkAccountCanLogin(user, type);
    const result = bcrypt.compareSync(data.password, user.password);
    if (!result) throw new ServiceError(ErrorCode.BAD_PASSWORD);
    return user;
  }
  /**
   * 检查用户是否可以登录(从用户本身和角色检查)
   * @param user 用户信息
   * @param type 用户类型
   */
  async checkAccountCanLogin(user, type: LoginType) {
    // 判断是否可以使用:
    // 管理员: 超级管理员无视,直接往下; 普通管理员需要查看is_use是不是'0'
    // 其他用户需要看status是不是'1';
    if (type === 'Admin') {
      if (get(user, 'is_super') === '1') {
        if (get(user, 'is_use') === '1') throw new ServiceError(ErrorCode.USER_IS_DISABLED);
      }
    } else {
      if (get(user, 'status') !== '1') throw new ServiceError(ErrorCode.USER_IS_DISABLED);
      const qb = { code: type, is_use: '0' };
      const role = await this.roleService.fetch(qb);
      if (!role) throw new ServiceError(ErrorCode.ROLE_IS_DISABLED);
    }
  }
  // 修改密码
  async updatePwd(data: UPwdDTO, type: LoginType) {
    let model;
    if (type === LoginType.Admin) model = this.adminModel;
    else model = this.userModel;
    const user = await model.findOne({ where: { id: Equal(data.id) } });
    if (!user) new ServiceError(ErrorCode.USER_NOT_FOUND);
    await model.update({ id: data.id }, { password: data.password });
  }

  // 需要改的时候再用
  async wxAppLogin(openid: string, type: LoginType) {
    let model;
    if (type === LoginType.Admin) model = this.adminModel;
    else model = this.userModel;
    const user = await model.findOne({ where: { openid: Equal(openid) } });
    if (!user) throw new ServiceError(ErrorCode.USER_NOT_FOUND);
    return user;
  }

  randomStr(len = 32) {
    return Math.random().toString(36).slice(-len);
  }
}
