package pwc.taxtech.atms.service.impl;

import org.apache.commons.lang3.BooleanUtils;
import org.apache.ibatis.session.RowBounds;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import pwc.taxtech.atms.common.*;
import pwc.taxtech.atms.common.message.UserMessage;
import pwc.taxtech.atms.dao.UserHistoricalPasswordMapper;
import pwc.taxtech.atms.dao.UserMapper;
import pwc.taxtech.atms.dto.ForgetPasswordDto;
import pwc.taxtech.atms.dto.LoginOutputDto;
import pwc.taxtech.atms.dto.MailMto;
import pwc.taxtech.atms.dto.OperationResultDto;
import pwc.taxtech.atms.dto.UpdateLogParams;
import pwc.taxtech.atms.dto.user.UserAndUserRoleSaveDto;
import pwc.taxtech.atms.dto.user.UserPasswordDto;
import pwc.taxtech.atms.entity.*;
import pwc.taxtech.atms.entity.UserHistoricalPasswordExample.Criteria;
import pwc.taxtech.atms.security.AtmsPasswordEncoder;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;

@Service
public class UserAccountServiceImpl extends AbstractService {
    // TODO Move it to constants file
    private static final String MAIL_TEMPLATE_PATH = "mailTemplate/";

    private static final Logger logger = LoggerFactory.getLogger(UserAccountServiceImpl.class);

    @Autowired
    private UserMapper userMapper;
    @Autowired
    private StringHelper stringHelper;
    @Autowired
    private UserHistoricalPasswordMapper userHistoricalPasswordMapper;
    @Autowired
    private UserServiceImpl userService;
    @Autowired
    private CommonServiceImpl commonService;

    @Autowired
    private AtmsPasswordEncoder atmsPasswordEncoder;
    @Autowired
    private AuthUserHelper authUserHelper;
    @Autowired
    private AtmsApiSettings atmsApiSettings;

    public OperationResultDto<LoginOutputDto> changeExternalUserPassword(UserPasswordDto userPasswordDto) {
        logger.debug("修改密码 Start");
        final String userName = authUserHelper.getCurrentAuditor().get();

        // find exist user
        User tempUser = userMapper.selectByUserNameIgnoreCase(userName);
        // verify old password

        OperationResultDto<LoginOutputDto> loginResult = userService.activeCheck(tempUser);
        if (loginResult.getResult() != null && loginResult.getResult()) {
            logger.debug("result is true after activeCheck");
            loginResult = userService.externalUserLogin(tempUser, userName, userPasswordDto.getOldPassword());
        } else {
            logger.debug("result is false after activeCheck");
        }

        if (loginResult.getResult() != null && loginResult.getResult()) {
            logger.debug("result is true after externalUserLogin");
            if (tempUser.getAttemptTimes() != null && tempUser.getAttemptTimes() != 0) {
                logger.debug("call method resetAttemptTimes");
                resetAttemptTimes(tempUser);
            }
        } else {
            logger.debug("result is false after externalUserLogin");
            if (loginResult.getData() != null
                    && CheckState.WrongPassword.value().equals(loginResult.getData().getCheckState())) {
                logger.debug("ready to call dealWithWrongPassword");
                dealWithWrongPassword(tempUser);
            }
            if (loginResult.getData() != null && loginResult.getData().getCheckState() != null) {
                String msg = CheckState.int2str(loginResult.getData().getCheckState());
                logger.debug("set setResultMsg to:{}", msg);
                loginResult.setResultMsg(msg);
                return loginResult;
            }
        }
        String newEncodedPassword = atmsPasswordEncoder.encode(userPasswordDto.getNewPassword());

        // check history
        if (checkHistoricalPassword(tempUser.getId(), newEncodedPassword)) {
            logger.debug("历史密码验证失败:密码已被使用");
            loginResult.setResult(false);
            loginResult.setResultMsg(UserMessage.UsedPassword);
        } else {
            logger.debug("历史密码验证通过");
        }
        if (loginResult.getResult() != null && loginResult.getResult()) {
            // set new password
            setNewPassword(tempUser, newEncodedPassword);
            logger.debug("设置新密码成功");
        } else {
            logger.debug("loginResult.result is false");
        }
        return loginResult;
    }

    private void setNewPassword(User tempUser, String newEncodedPassword) {
        // 因为C#的创建UserHistoricalPassword的代码被注释了,所以跳过创建UserHistoricalPassword
        tempUser.setPassword(newEncodedPassword);
        User userTarget = new User();
        userTarget.setPassword(tempUser.getPassword());
        userTarget.setUpdateTime(new Date());
        userTarget.setId(tempUser.getId());
        int resultCount = userMapper.updateByPrimaryKeySelective(userTarget);
        Assert.state(resultCount > 0, "Zero update count");
    }

    private boolean checkHistoricalPassword(String id, String newEncodedPassword) {
        UserHistoricalPasswordExample example = new UserHistoricalPasswordExample();
        example.setOrderByClause("UpdateTime desc");
        Criteria criteria = example.createCriteria();
        // criteria.andPasswordEqualTo(newEncodedPassword);
        criteria.andIdEqualTo(id);
        RowBounds rowBounds = new RowBounds(0, CommonConstants.MaxSamePasswordNumber);
        List<UserHistoricalPassword> list = userHistoricalPasswordMapper.selectByExampleWithRowbounds(example,
                rowBounds);
        boolean containsOldPassword = false;
        for (UserHistoricalPassword item : list) {
            if (item.getPassword() != null && item.getPassword().equals(newEncodedPassword)) {
                containsOldPassword = true;
                break;
            }
        }
        return containsOldPassword;
    }

    public void resetAttemptTimes(User tempUser) {
        User userTarget = new User();
        userTarget.setAttemptTimes(0);
        userTarget.setId(tempUser.getId());
        userMapper.updateByPrimaryKeySelective(userTarget);
    }

    public void dealWithWrongPassword(User tempUser) {
        User userTarget = new User();
        userTarget.setId(tempUser.getId());

        tempUser.setAttemptTimes(tempUser.getAttemptTimes() == null ? 0 : tempUser.getAttemptTimes() + 1);
        userTarget.setAttemptTimes(tempUser.getAttemptTimes());

        logger.debug("print attemptimes [{}]", tempUser.getAttemptTimes());

        if (tempUser.getAttemptTimes() >= CommonConstants.MaxAttemptTimes) {
            logger.warn("Lock user [{}] due to attemptimes is [{}]", tempUser.getUserName(),
                    tempUser.getAttemptTimes());

            tempUser.setStatus(UserStatus.Locked.value());
            userTarget.setStatus(tempUser.getStatus());

            tempUser.setLockedTime(new Date());
            userTarget.setLockedTime(tempUser.getLockedTime());
        } else {
            logger.debug("update user [{}] attemptTimes to [{}]", tempUser.getUserName(), tempUser.getAttemptTimes());
        }
        userMapper.updateByPrimaryKeySelective(userTarget);

    }

    public OperationResultDto<Object> forgetPassword(final String mail) {
        OperationResultDto<ForgetPasswordDto> result = forgetPasswordForUser(mail);
        if (result == null || result.getResult() == null || !result.getResult()) {
            logger.info("the result is false by calling forgetPasswordForUser");
            // 如果失败,直接返回,跳过发邮件
            return new OperationResultDto<>(true);
        }
        logger.info("the result is true by calling forgetPasswordForUser");
        ForgetPasswordDto forgetPasswordDto = result.getData();
        Assert.notNull(forgetPasswordDto, "Null data");

        // 发邮件
        String path = MAIL_TEMPLATE_PATH + "WebAdminResetPasswordLetter.html";
        String text = CommonUtils.readClasspathFileToString(path);

        text = text.replaceAll("\\{Password\\}", forgetPasswordDto.getNewPassword());
        text = text.replaceAll("\\{\\[Env\\]link\\}", atmsApiSettings.getWebUrl());
        text = text.replaceAll("\\{UserName\\}", forgetPasswordDto.getUserName());

        String subject = readHtmlTitle(text);

        MailMto mailmto = new MailMto();
        mailmto.setContent(text);
        mailmto.setTo(mail);
        mailmto.setSubject(subject);
        commonService.sendMail(mailmto);

        return new OperationResultDto<>(true);
    }

    public String readHtmlTitle(String text) {
        try {
            String beginTag = "<title>";
            String endTag = "</title>";
            int beginIndex = text.indexOf(beginTag);
            int endIndex = text.indexOf(endTag);
            // String subject = text.substring(beginTag.length() + beginIndex, endIndex -
            // beginIndex - endTag.length());
            String subject = text.substring(beginTag.length() + beginIndex, endIndex);
            subject = subject.trim();
            return subject;
        } catch (Exception e) {
            logger.error("Cannot get subject:" + e, e);
            return "NA";
        }
    }

    private OperationResultDto<ForgetPasswordDto> forgetPasswordForUser(String mail) {
        if (!StringUtils.hasText(mail) || !mail.contains("@")) {
            return new OperationResultDto<>(false);
        }
        User tempUser = userMapper.selectByUserNameIgnoreCase(mail);
        if (tempUser == null) {
            return new OperationResultDto<>(false);
        }
        final String newPassword = stringHelper.generateRandomPassword();
        String encodedPassword = atmsPasswordEncoder.encode(newPassword);
        tempUser.setPasswordUpdateTime(new Date());
        tempUser.setStatus(UserStatus.InActive.value());
        tempUser.setPassword(encodedPassword);
        userMapper.updateByPrimaryKey(tempUser);

        ForgetPasswordDto forgetPasswordDto = new ForgetPasswordDto();
        forgetPasswordDto.setNewPassword(newPassword);
        // 因为C#代码没有设置changeResult, 所以这里不设置changeResult
        forgetPasswordDto.setUserName(tempUser.getUserName());

        return new OperationResultDto<>(true, null, forgetPasswordDto);
    }

    public OperationResultDto<User> addNewUser(UserAndUserRoleSaveDto userAndUserRoleSaveDto) {
        OperationResultDto<User> user = addUser(userAndUserRoleSaveDto);
        //todo send mail
//        if (BooleanUtils.isTrue(user.getResult())) {
//            MailMto mailMto = new MailMto();
//            String path = MAIL_TEMPLATE_PATH + "WebAdminWelcomeLetter.html";
//            String content = CommonUtils.readClasspathFileToString(path);
//            content = content.replaceAll("\\{Password\\}", user.getResultMsg());
//            content = content.replaceAll("\\{\\[Env\\]link\\}", atmsApiSettings.getWebUrl());
//            content = content.replaceAll("\\{UserName\\}", user.getData().getUserName());
//            mailMto.setContent(content);
//            // Get Subject Avoid Multi-language
//            String beginTag = "<title>";
//            int beginIndex = content.indexOf(beginTag);
//            int endIndex = content.indexOf("</title>");
//            String subject = content.substring(beginIndex + beginTag.length(), endIndex);
//            mailMto.setSubject(subject);
//            mailMto.setTo(user.getData().getEmail());
//            commonService.sendMail(mailMto);
//        }
        return user;
    }

    public OperationResultDto<User> addUser(UserAndUserRoleSaveDto userAndUserRoleSaveDto) {
        String userName = authUserHelper.getCurrentAuditor().get();
        OperationResultDto<User> operationResultDto = new OperationResultDto<>();
        OperationResultDto<User> result = userService.checkUserExist(userAndUserRoleSaveDto.getUserName(),
                CommonConstants.EMPTY_UUID);
        if (result != null && BooleanUtils.isFalse(result.getResult())) {
            return result;
        }

        result = userService.checkEmailExist(userAndUserRoleSaveDto.getEmail(), CommonConstants.EMPTY_UUID);
        if (result != null && BooleanUtils.isFalse(result.getResult())) {
            return result;
        }
        if (BooleanUtils.isTrue(userAndUserRoleSaveDto.getIsAdmin())) {
            User operateUser = userMapper.selectByUserNameIgnoreCase(userName);
            // cs代码逻辑微调, 如果正在执行操作的用户不是admin(isAdmin:0)或者没有设置admin权限(isAdmin:null), 则不执行操作
            if (operateUser != null && !BooleanUtils.isTrue(operateUser.getIsAdmin())) {
                operationResultDto.setResult(false);
                operationResultDto.setResultMsg(UserMessage.HasNoPermission);
            }
        }
        User user = CommonUtils.copyProperties(userAndUserRoleSaveDto, new User());
        // 根据cs代码的mapper方法初始化user的isAdmin值
        user.setIsAdmin(BooleanUtils.isTrue(userAndUserRoleSaveDto.getIsAdmin()));
        if(userAndUserRoleSaveDto.getRoleNames().contains("超级管理员")){
            user.setIsSuperAdmin(true);
        }
        user.setId(CommonUtils.getUUID());
        user.setStatus(UserStatus.Active.value());
        // loginType:{1:AD认证,2:用户名密码登录验证,3:外部用户登录,使用Email和密码登录,因为目前没有选择登录类型,所以直接设置为3,全部使用密码验证,以后根据需求变更}
        user.setLoginType(UserLoginType.ExternalUser);
        List<UserRole> userRoleList = new ArrayList<>();
        // new password
//        String newPwd = stringHelper.generateRandomPassword();
        String newPwd = "12345678";//todo 没有发邮件,先默认这个
        user.setPassword(atmsPasswordEncoder.encode(newPwd));
        user.setCreateTime(
                userAndUserRoleSaveDto.getCreateTime() == null ? new Date() : userAndUserRoleSaveDto.getCreateTime());
        user.setUpdateTime(
                userAndUserRoleSaveDto.getUpdateTime() == null ? new Date() : userAndUserRoleSaveDto.getUpdateTime());
        user.setAttemptTimes(
                userAndUserRoleSaveDto.getAttemptTimes() == null ? 0 : userAndUserRoleSaveDto.getAttemptTimes());
        user.setIsSuperAdmin(false);
        logger.debug("Start to insert new user [ {} ]", user.getId());
        userMapper.insert(user);
        List<String> arrRoles = userAndUserRoleSaveDto.getRoleIds();
        List<Role> roleQuery = roleMapper.selectByExample(new RoleExample());
//        List<Role> roleQuery = roleData.selectByServiceTypeId("All");
        if (arrRoles != null && !arrRoles.isEmpty()) {
            for (String role : arrRoles) {
                UserRole userRole = new UserRole();
                userRole.setId(CommonUtils.getUUID());
                userRole.setUserId(user.getId());
                userRole.setRoleId(role);
                userRole.setServiceTypeId(roleQuery.stream().filter(p -> Objects.equals(p.getId(), role)).findFirst()
                        .orElse(new Role()).getServiceTypeId());
                userRoleList.add(userRole);
                logger.debug("Start to insert user role [ {} ]", userRole.getId());
                userRoleMapper.insertSelective(userRole);
            }
        }
        // 默认添加机构可访问权限
        UserOrganization userOrganization = new UserOrganization();
        userOrganization.setUserId(user.getId());
        userOrganization.setHasOriginalRole(CommonConstants.HasOriginalRole);
        userOrganization.setOrganizationId(user.getOrganizationId());
        userOrganization.setId(CommonUtils.getUUID());
        userOrganization.setIsAccessible(CommonConstants.IsAccessible);
        logger.debug("Start to insert user organization [ {} ]", userOrganization.getId());
        userOrganizationMapper.insert(userOrganization);
        operationLogService.addOrDeleteDataAddLog(generateUpdateLogParams(OperationModule.User.value(),
                user.getUserName(), user.getUserName(), OperationAction.AddNewUser.value()));
        List<UpdateLogParams> userRoleLogs = new ArrayList<>();
        for (UserRole userRole : userRoleList) {
            Role role = roleQuery.stream().filter(sa -> Objects.equals(sa.getId(), userRole.getRoleId())).findFirst()
                    .orElse(null);
            userRoleLogs.add(generateUpdateLogParams(OperationModule.UserRole.value(),
                    role == null ? "" : role.getName(), user.getUserName(), OperationAction.AddNewPermission.value()));

        }
        operationLogService.addOrDeleteDataAddLog(userRoleLogs);
        operationResultDto.setResult(true);
        operationResultDto.setData(user);
        operationResultDto.setResultMsg(newPwd);
        return operationResultDto;
    }

    private UpdateLogParams generateUpdateLogParams(Integer module, String operationContent, String operateObject,
                                                    Integer action) {
        UpdateLogParams updateLogParams = new UpdateLogParams();
        updateLogParams.setOperationModule(module);
        updateLogParams.setOperationContent(operationContent);
        updateLogParams.setOperationObject(operateObject);
        updateLogParams.setOperationAction(action);
        updateLogParams.setOperateLogType(OperateLogType.OperationLogUser.value());
        return updateLogParams;
    }

}