package com.red.circle.other.app.inner.service.user.user.impl;

import com.red.circle.auth.inner.endpoint.AuthClient;
import com.red.circle.common.business.dto.cmd.AppExtCommand;
import com.red.circle.common.business.enums.AccountStatusEnum;
import com.red.circle.common.business.enums.AccountStatusStrEnum;
import com.red.circle.external.inner.endpoint.message.ImAccountClient;
import com.red.circle.external.inner.endpoint.message.NewsletterClient;
import com.red.circle.external.inner.model.cmd.message.NewsletterResultCmd;
import com.red.circle.external.inner.model.enums.message.NewsletterEvent;
import com.red.circle.framework.core.asserts.ResponseAssert;
import com.red.circle.framework.core.dto.CommonCommand;
import com.red.circle.framework.core.response.CommonErrorCode;
import com.red.circle.framework.dto.IResponseErrorCode;
import com.red.circle.framework.dto.PageResult;
import com.red.circle.framework.mybatis.constant.PageConstant;
import com.red.circle.framework.web.props.EnvProperties;
import com.red.circle.mq.rocket.business.producer.UserMqMessageService;
import com.red.circle.other.app.inner.convertor.user.AuthInnerConvertor;
import com.red.circle.other.app.inner.convertor.user.UserAccountInnerConvertor;
import com.red.circle.other.app.inner.convertor.user.UserProfileInnerConvertor;
import com.red.circle.other.app.inner.service.user.user.UserAccountClientService;
import com.red.circle.other.domain.gateway.user.UserProfileGateway;
import com.red.circle.other.domain.model.user.UserProfile;
import com.red.circle.other.infra.common.user.UserAccountCommon;
import com.red.circle.other.infra.database.cache.service.user.UserAccountAuthCacheService;
import com.red.circle.other.infra.database.mongo.service.user.status.OnlineUserService;
import com.red.circle.other.infra.database.rds.entity.user.user.AccountAuth;
import com.red.circle.other.infra.database.rds.entity.user.user.ApprovalUserAccountStatusLog;
import com.red.circle.other.infra.database.rds.entity.user.user.AuthType;
import com.red.circle.other.infra.database.rds.entity.user.user.BaseInfo;
import com.red.circle.other.infra.database.rds.entity.user.user.LogoutRecord;
import com.red.circle.other.infra.database.rds.service.user.device.ArchiveDeviceService;
import com.red.circle.other.infra.database.rds.service.user.device.LatestMobileDeviceService;
import com.red.circle.other.infra.database.rds.service.user.user.AccountAuthService;
import com.red.circle.other.infra.database.rds.service.user.user.ApprovalUserAccountStatusLogService;
import com.red.circle.other.infra.database.rds.service.user.user.AuthTypeService;
import com.red.circle.other.infra.database.rds.service.user.user.BaseInfoService;
import com.red.circle.other.infra.database.rds.service.user.user.LogoutRecordService;
import com.red.circle.other.infra.database.rds.service.user.user.MobileAuthService;
import com.red.circle.other.inner.asserts.user.UserErrorCode;
import com.red.circle.other.inner.enums.user.AuthTypeEnum;
import com.red.circle.other.inner.model.cmd.user.UserChannelCredentialCmd;
import com.red.circle.other.inner.model.cmd.user.account.AccountLoginCmd;
import com.red.circle.other.inner.model.cmd.user.account.CreateAccountCmd;
import com.red.circle.other.inner.model.cmd.user.account.MobileCredentialCmd;
import com.red.circle.other.inner.model.cmd.user.account.PunishmentAccountCmd;
import com.red.circle.other.inner.model.cmd.user.account.UnblockAccountCmd;
import com.red.circle.other.inner.model.dto.user.UserAuthTypeDTO;
import com.red.circle.other.inner.model.dto.user.account.MobileAuthDTO;
import com.red.circle.other.inner.model.dto.user.account.UserAccountDTO;
import com.red.circle.other.inner.model.dto.user.account.UserMobileUpdateCmd;
import com.red.circle.tool.core.date.TimestampUtils;
import com.red.circle.tool.core.func.VoidConsumer;
import com.red.circle.tool.core.sequence.IdWorkerUtils;
import com.red.circle.tool.core.text.StringUtils;
import com.red.circle.other.inner.model.cmd.user.UserAccountStatusLogQryCmd;
import com.red.circle.other.inner.model.dto.user.ApprovalUserAccountStatusLogDTO;

import java.util.List;
import java.util.Objects;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

/**
 * 用户账号服务 实现.
 *
 * @author pengliang on 2023/5/22
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class UserAccountClientServiceImpl implements UserAccountClientService {

  private final AuthClient authClient;
  private final EnvProperties envProperties;
  private final BaseInfoService baseInfoService;
  private final AuthTypeService authTypeService;
  private final ImAccountClient imAccountClient;
  private final NewsletterClient newsletterClient;
  private final UserAccountCommon userAccountCommon;
  private final OnlineUserService onlineUserService;
  private final MobileAuthService mobileAuthService;
  private final AuthInnerConvertor authInnerConvertor;
  private final AccountAuthService accountAuthService;
  private final UserProfileGateway userProfileGateway;
  private final LogoutRecordService logoutRecordService;
  private final UserMqMessageService userMqMessageService;
  private final ArchiveDeviceService archiveDeviceService;
  private final UserAccountInnerConvertor userAccountInnerConvertor;
  private final UserProfileInnerConvertor userProfileInnerConvertor;
  private final LatestMobileDeviceService latestMobileDeviceService;
  private final UserAccountAuthCacheService accountAuthCacheService;
  private final UserAccountAuthCacheService userAccountAuthCacheService;
  private final ApprovalUserAccountStatusLogService approvalUserAccountStatusLogService;

  @Override
  public UserAccountDTO create(CreateAccountCmd cmd) {
    return userAccountCommon.create(cmd);
  }

  @Override
  public void logout(CommonCommand cmd) {
    BaseInfo baseInfo = baseInfoService.getById(cmd.requiredReqUserId());
    // 1011 未找到相关信息
    ResponseAssert.notNull(UserErrorCode.USER_INFO_NOT_FOUND, baseInfo);

    userProfileGateway.removeCacheAll(baseInfo.getId());
    if (envProperties.isProd()) {
      authTypeService.deleteAuth(cmd.requiredReqUserId());
      mobileAuthService.deleteLogicByUserId(cmd.requiredReqUserId());
    } else {
      authTypeService.update()
          .set(AuthType::getOpenId, IdWorkerUtils.getIdStr())
          .set(AuthType::getDel, Boolean.TRUE)
          .eq(AuthType::getUserId, cmd.requiredReqUserId())
          .last(PageConstant.LIMIT_ONE).execute();
    }
    logoutRecordService.save(new LogoutRecord()
        .setIp(cmd.getReqIp())
        .setUserId(baseInfo.getId())
        .setPlatform(Objects.toString(cmd.getReqClient()))
    );
    baseInfoService.deleteUser(baseInfo.getId());
  }

  @Override
  public void signOut(CommonCommand cmd) {
    onlineUserService.removeByUserId(cmd.requiredReqUserId());
    imAccountClient.kick(cmd.requiredReqUserId());
  }

  @Override
  public UserAccountDTO mobileCredential(MobileCredentialCmd cmd) {
    Long userId = mobileAuthService
        .checkPasswordReturnUserId(cmd.getPhonePrefix(), cmd.getPhoneNumber(),
            cmd.getPwd());
    // 4004 账号或密码错误
    ResponseAssert.notNull(UserErrorCode.ACCOUNT_OR_PASSWORD_NOT_MATCH, userId);
    cmd.setReqUserId(userId);

    UserProfile userProfile = userProfileGateway.getByUserId(userId);
    ResponseAssert.notNull(CommonErrorCode.DATA_ERROR, userProfile);
    ResponseAssert.isTrue(UserErrorCode.ACCOUNT_OR_PASSWORD_NOT_MATCH,
        Objects.equals(cmd.requireReqSysOrigin(), userProfile.getOriginSys()));

    return createUserAccountDTO(userId,
        cmd.getReqImei(),
        () -> sendLoginMessage(AuthTypeEnum.MOBILE, cmd));
  }

  @Override
  public UserAccountDTO channelCredential(UserChannelCredentialCmd cmd) {
    AuthType authType = authTypeService.getAuthInfo(cmd.openId());
    // 用户未注册
    ResponseAssert.notNull(UserErrorCode.USER_NOT_REGISTERED, authType);
    cmd.setReqUserId(authType.getUserId());

    return createUserAccountDTO(
        authType.getUserId(),
        cmd.getReqImei(),
        () -> sendLoginMessage(cmd.getAuthType(), cmd));
  }

  @Override
  public void unblock(UnblockAccountCmd cmd) {
    userAccountCommon.unblock(cmd);
  }

  @Override
  public void punishment(PunishmentAccountCmd cmd) {
    userAccountCommon.punishmentPro(cmd);
  }

  @Override
  public UserAuthTypeDTO getByUserIdOne(Long userId) {
    return authInnerConvertor.toUserAuthTypeDTO(authTypeService.getByUserIdOne(userId));
  }

  @Override
  public UserAccountDTO accountLogin(AccountLoginCmd cmd) {
    Long userId = getAccountLoginUserId(cmd);
    cmd.setReqUserId(userId);
    return createUserAccountDTO(userId,
        cmd.getReqImei(),
        () -> sendLoginMessage(AuthTypeEnum.ACCOUNT, cmd));
  }

  @Override
  public MobileAuthDTO getMobileAuthByUserId(Long userId) {
    return authInnerConvertor.toMobileAuthDTO(mobileAuthService.getByUserId(userId));
  }


  @Override
  public void editUserMobileAuth(UserMobileUpdateCmd param) {

    UserProfile userProfile = userProfileGateway.getByUserId(param.getUserId());

    if (Objects.isNull(userProfile)) {
      return;
    }

    // 验证绑定手机号码
    AuthType userAuthType = authTypeService.getAuthInfo(
        AuthTypeEnum.MOBILE.name(),
        getOpenId(param));

    // 没有被绑定
    if (Objects.isNull(userAuthType)) {
      mobileAuthService.addOrUpdateUserMobileAuth(param);
      authTypeService
          .saveUserAuthType(param.getUserId(), AuthTypeEnum.MOBILE.name(), getOpenId(param));
      return;
    }

    // 被自己绑定
    if (Objects.equals(userAuthType.getUserId(), param.getUserId())) {
      ResponseAssert.isTrue(UserErrorCode.MOBILE_BIND_ERROR,
          mobileAuthService.updateUserMobileAuthPassword(param));
      return;
    }

    // 被使用
    ResponseAssert.failure(UserErrorCode.UNABLE_BIND_ERROR);
  }

  @Override
  public List<UserAuthTypeDTO> listByUserId(Long userId) {
    return authInnerConvertor.toListUserAuthTypeDTO(authTypeService.listByUserId(userId));
  }


  @Override
  public void deleteUserMobileAuth(Long userId) {
    authTypeService.deleteLogicAuthType(userId, AuthTypeEnum.MOBILE.name());
    mobileAuthService.deleteByUserId(userId);
  }


  @Override
  public void deleteAccountAuthByUserId(Long userId) {
    accountAuthCacheService.deleteByUserId(userId);
  }

  @Override
  public void deleteUserAccountLogin(Long userId) {
    authTypeService.deleteAuthType(userId, AuthTypeEnum.ACCOUNT.name());
    accountAuthService.deleteByUserId(userId);
    // 删除密码之后 重置用户账号密码登录信息
    accountAuthCacheService.deleteByUserId(userId);
  }


  @Override
  public boolean updateAccountFreeze(Long userId, Integer days) {
    return baseInfoService.updateAccountFreeze(userId, days);
  }

  @Override
  public boolean updateAccountStatus(Long userId, AccountStatusEnum accountStatus) {
    return baseInfoService.updateAccountStatus(userId, accountStatus);
  }

  @Override
  public boolean updateAccountStatusDays(Long userId, AccountStatusEnum accountStatus,  Integer days) {
    return baseInfoService.updateAccountStatusDays(userId, accountStatus, days);
  }

  @Override
  public PageResult<ApprovalUserAccountStatusLogDTO> pgeApprovalUserAccountStatusLog(
      UserAccountStatusLogQryCmd cmd) {
    PageResult<ApprovalUserAccountStatusLog> pageResult = approvalUserAccountStatusLogService.pgeApprovalUserAccountStatusLog(
        cmd);
    PageResult<ApprovalUserAccountStatusLogDTO> pageResult1 = pageResult.convert(userAccountInnerConvertor::toApprovalUserAccountStatusLogDTO);
    pageResult1.getRecords().forEach(item -> {
      item.setStatusName(AccountStatusStrEnum.getDescValue(item.getStatus()));
    });
    return pageResult1;
  }

  @Override
  public List<ApprovalUserAccountStatusLogDTO> lastApprovalUserAccountStatusLog(Long beApprovalUser,
      Integer size) {
    List<ApprovalUserAccountStatusLog> list = approvalUserAccountStatusLogService.lastApprovalUserAccountStatusLog(
        beApprovalUser, size);
    return userAccountInnerConvertor.toListApprovalUserAccountStatusLogDTO(list);
  }


  public String getOpenId(UserMobileUpdateCmd param) {
    return param.getPhonePrefix() + "," + param.getPhoneNumber();
  }

  private Long getAccountLoginUserId(AccountLoginCmd cmd) {
    // 2042 设备不可用
    ResponseAssert.isFalse(CommonErrorCode.DEVICE_UNAVAILABLE,
        archiveDeviceService.existsDevice(cmd.getReqImei(), cmd.requireReqSysOrigin()));

    UserProfile userProfile = userProfileGateway.getByAccount(cmd.requireReqSysOrigin(),
        cmd.getAccount());

    // 4060 账号错误
    ResponseAssert.notNull(UserErrorCode.ACCOUNT_NOT_MATCH, userProfile);

    // 4060 账号错误
    ResponseAssert.isFalse(UserErrorCode.ACCOUNT_NOT_MATCH, !cmd.requireReqSysOrigin().equals(userProfile.getOriginSys()));
    // 查询密码输入错误次数
    Integer times = userAccountAuthCacheService.getAccountAuthTimes(
        userProfile.getId());

    if (times >= 3) {
      // 4059密码3次输入错误
      ResponseAssert.isFalse(UserErrorCode.PASSWORD_NOT_MATCH_THREE, Boolean.TRUE);
    }

    AccountAuth accountAuth = accountAuthService.getByPassword(cmd.getPwd(), userProfile.getId());

    if (Objects.isNull(accountAuth)) {
      // 密码输错次数
      userAccountAuthCacheService.recordAccountAuth(userProfile.getId());

      Integer count = Optional.ofNullable(userAccountAuthCacheService.getAccountAuthTimes(
          userProfile.getId())).orElse(0);

      Boolean firstLogin = latestMobileDeviceService.isFirstLogin(userProfile.getId(),
          cmd.getReqImei());

      // 新设备只允许输入一次错误密码
      if (Boolean.TRUE.equals(firstLogin) && count >= 1) {
        // 4059密码3次输入错误
        ResponseAssert.isFalse(UserErrorCode.PASSWORD_NOT_MATCH_THREE, Boolean.TRUE);
      }

      // 4057还有2次机会
      ResponseAssert.isFalse(UserErrorCode.PASSWORD_NOT_MATCH_ONE, Objects.equals(count, 1));

      // 4058还有1次机会
      ResponseAssert.isFalse(UserErrorCode.PASSWORD_NOT_MATCH_TWO, Objects.equals(count, 2));

      // 4059密码3次输入错误
      ResponseAssert.isFalse(UserErrorCode.PASSWORD_NOT_MATCH_THREE, count >= 3);

      return userProfile.getId();
    }
    return accountAuth.getUserId();
  }

  private void clearUserSource(Long userId, AccountStatusEnum accountStatus) {
    authClient.removeToken(userId);
    userProfileGateway.removeCacheAll(userId);
    imAccountClient.kick(userId);
    sendNotice(userId, accountStatus);
  }

  private void sendNotice(Long userId, AccountStatusEnum accountStatus) {
    if (Objects.equals(accountStatus, AccountStatusEnum.FREEZE)) {
      newsletterClient.send(new NewsletterResultCmd()
          .setUserIds(List.of(userId))
          .setEvent(NewsletterEvent.ACCOUNT_FREEZE)
          .setData(TimestampUtils.nowPlusDays(3))
      );
    }

    if (Objects.equals(accountStatus, AccountStatusEnum.ARCHIVE)) {
      newsletterClient.send(new NewsletterResultCmd()
          .setUserIds(List.of(userId))
          .setEvent(NewsletterEvent.ACCOUNT_ARCHIVE)
      );
    }

    if (Objects.equals(accountStatus, AccountStatusEnum.ARCHIVE_DEVICE)) {
      newsletterClient.send(new NewsletterResultCmd()
          .setUserIds(List.of(userId))
          .setEvent(NewsletterEvent.ARCHIVE_DEVICE)
      );
    }
  }


  public UserAccountDTO createUserAccountDTO(Long userId, String imei, VoidConsumer voidConsumer) {
    UserProfile userProfile = userProfileGateway.getByUserId(userId);
    ResponseAssert.notNull(CommonErrorCode.DATA_ERROR, userProfile);
    List<String> imeiList = List.of("7DD73EF2FC33128DEB6D68ED1E931379C2C613B5","2674AA85CF8EF827660DDABBFC231D615AD6E529","FE94F2B2CEEDEEADA31C790991B05CC06238EB68");

    // 注册黑名单
    if (Objects.equals(userProfile.getDel(), Boolean.TRUE)) {
      ResponseAssert.failure(UserErrorCode.REGISTER_BLACKLIST);
    }

    // 检查账号是否被封禁
    ResponseAssert.isFalse(UserErrorCode.ACCOUNT_ARCHIVE,
        getTipsUserAccount(UserErrorCode.ACCOUNT_ARCHIVE, userProfile),
        userProfile.checkAccountArchive());

    // 检查账号是否被冻结
    ResponseAssert.isFalse(UserErrorCode.ACCOUNT_FREEZE,
        getTipsUserAccount(UserErrorCode.ACCOUNT_FREEZE, userProfile),
        userProfile.checkAccountFreeze());

    // 验证设备是否被封禁
    ResponseAssert.isTrue(CommonErrorCode.DEVICE_UNAVAILABLE,
        "Device required.",
        StringUtils.isNotBlank(imei));

    //运营临时封禁设备
    ResponseAssert.isTrue(CommonErrorCode.DEVICE_UNAVAILABLE,
            "Device required.",
            !imeiList.contains(imei));

    ResponseAssert.isFalse(CommonErrorCode.DEVICE_UNAVAILABLE,
        getTipsUserAccount(CommonErrorCode.DEVICE_UNAVAILABLE, userProfile),
        archiveDeviceService.existsDevice(imei, userProfile.getOriginSys()));

    voidConsumer.accept();

    return new UserAccountDTO()
        .setUserProfile(userProfileInnerConvertor.toUserProfileDTO(userProfile))
        .setUserSig(requestUserSign(userProfile.getId()));
  }

  private String getTipsUserAccount(IResponseErrorCode errorCode, UserProfile userProfile) {
    return errorCode.getMessage() + "[ ID:" + userProfile.getAccount() + "]";
  }

  private void sendLoginMessage(AuthTypeEnum authTypeEnum, AppExtCommand cmd) {
    userMqMessageService.loginLogger(Objects.toString(cmd.requiredReqUserId()),
        userProfileInnerConvertor.toLoginLoggerEventLogin(authTypeEnum, cmd));
  }

  private String requestUserSign(Long userId) {
    return ResponseAssert.requiredSuccess(imAccountClient.createUserSig(userId));
  }
}
