package com.eitc.patient.service.impl;

import com.aliyuncs.exceptions.ClientException;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.eitc.common.core.domain.AjaxResult;
import com.eitc.common.core.domain.entity.SysUser;
import com.eitc.common.core.redis.RedisCache;
import com.eitc.common.utils.JWTUtil;
import com.eitc.common.utils.PatientBaseUtil;
import com.eitc.common.utils.SecurityUtils;
import com.eitc.patient.constant.PatientConstant;
import com.eitc.patient.domain.AppUser;
import com.eitc.patient.domain.AppUserPatients;
import com.eitc.patient.domain.Patient;
import com.eitc.patient.enums.AppUserLoginTypeEnum;
import com.eitc.patient.enums.PatientCreateTypeEnum;
import com.eitc.patient.mapper.AppUserMapper;
import com.eitc.patient.service.IAppUserPatientsService;
import com.eitc.patient.service.IAppUserService;
import com.eitc.patient.service.IPatientService;
import com.eitc.patient.vo.AppUserPatientVO;
import com.eitc.patient.vo.PatientVO;
import com.eitc.system.service.ISysUserService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * AppUserServiceImpl
 *
 * @author mtx
 * @date: 2024/06/11 17:37
 */
@Service
public class AppUserServiceImpl extends ServiceImpl<AppUserMapper, AppUser> implements IAppUserService {

    @Resource
    private RedisCache redisCache;
    @Resource
    private IAppUserService appUserService;
    @Resource
    private IAppUserPatientsService appUserPatientsService;
    @Resource
    private AppUserMapper appUserMapper;
    @Resource
    private IPatientService patientService;
    @Resource
    private ISysUserService sysUserService;
    @Value(value = "${user.password.maxRetryCount}")
    private int maxRetryCount;
    @Value(value = "${user.password.lockTime}")
    private int lockTime;


    /**
     * @param phoneNumber 手机号
     * @return 是否成功
     * @author mtx
     * @date: 2024/6/12 11:20
     */
    @Override
    public AjaxResult sendCaptchaCode(String phoneNumber) throws ClientException {
        if (StringUtils.isEmpty(phoneNumber)) {
            return AjaxResult.error("手机号不能为空");
        }
        // 校验验证码发送是否超过限制
        String key = PatientConstant.ERM_APP_CAPTCHA_CODE + phoneNumber;
        String repeat = key + ":repeat";
        int repeatNum = 1;
        if (redisCache.hasKey(repeat)) {
            repeatNum = redisCache.getCacheObject(repeat);
            int num = maxRetryCount;
            if (repeatNum >= num) {
                return AjaxResult.error("验证码发送次数超过" + num + "次，请稍后再试");
            }
            repeatNum++;
        }
        redisCache.setCacheObject(repeat, repeatNum, 30, TimeUnit.MINUTES);

        // 生成验证码
        int captchaCode = PatientBaseUtil.getCaptchaCode();
        // 发送短信 todo
//        SendSmsUtil.sendSmsToLogin(phoneNumber, String.valueOf(captchaCode));
        // 缓存验证码
        redisCache.setCacheObject(key, captchaCode, 5, TimeUnit.MINUTES);
        return AjaxResult.success(captchaCode);
    }

    /**
     * 登录
     *
     * @param map 参数
     * @return 成功
     */
    @Override
    public AjaxResult login(Map<String, String> map) {
        String type = map.get("type");
        String loginType = map.get("loginType");
        String phoneNumber = map.get("phoneNumber");

        if (StringUtils.isEmpty(phoneNumber)) {
            return AjaxResult.error("手机号不能为空");
        }
        // 校验登录是否超过限制
        String repeat = checkRepeatLoginError(loginType, phoneNumber);

        Map<String, String> payload;
        if ("1".equals(type)) {
            payload = generateTokenToApp(map, loginType);
        } else if ("2".equals(type)) {
            payload = generateTokenToPC(map, loginType);
        } else {
            throw new RuntimeException("登录类型错误");
        }

        redisCache.deleteObject(repeat);
        return AjaxResult.success(payload);
    }

    /**
     * 生成token通过APP
     *
     * @param map       参数
     * @param loginType 类型(1账号登录，2验证码登录)
     * @return 返回结果
     * @author mtx
     * @date: 2024/7/24 9:34
     */
    private Map<String, String> generateTokenToApp(Map<String, String> map, String loginType) {
        String phoneNumber = map.get("phoneNumber");
        AppUser appUser;
        // 登录
        if (String.valueOf(AppUserLoginTypeEnum.ACCOUNT.getCode()).equals(loginType)) {
            // 账号密码登录
            String password = map.get("password");
            if (StringUtils.isBlank(password)) {
                throw new RuntimeException("密码不能为空");
            }
            AppUser user = getAppUserByPhoneNumber(phoneNumber);
            if (Objects.isNull(user)) {
                throw new RuntimeException(phoneNumber + "用户不存在或密码错误");
            }
            appUser = user;
            boolean flag = SecurityUtils.matchesPassword(password, appUser.getPassword());
            if (!flag) {
                throw new RuntimeException("用户不存在或密码错误");
            }
        } else if (String.valueOf(AppUserLoginTypeEnum.CAPTCHA.getCode()).equals(loginType)) {
            AppUser user = getAppUserByPhoneNumber(phoneNumber);
            if (Objects.isNull(user)) {
                throw new RuntimeException(phoneNumber + "用户不存在");
            }
            appUser = user;
        } else {
            throw new RuntimeException("登录方式错误");
        }
        // 生成token
        return getTokenByAppUserInfo(appUser);
    }

    /**
     * 生成token通过PC
     *
     * @param map       参数
     * @param loginType 类型(1账号登录，2验证码登录)
     * @return 返回结果
     * @author mtx
     * @date: 2024/7/24 9:34
     */
    private Map<String, String> generateTokenToPC(Map<String, String> map, String loginType) {
        String phoneNumber = map.get("phoneNumber");
        SysUser user;
        // 登录
        if (String.valueOf(AppUserLoginTypeEnum.ACCOUNT.getCode()).equals(loginType)) {
            // 账号密码登录
            String password = map.get("password");
            if (StringUtils.isBlank(password)) {
                throw new RuntimeException("密码不能为空");
            }
            SysUser sysUser = getPCUserByPhoneNumber(phoneNumber);
            if (Objects.isNull(sysUser)) {
                throw new RuntimeException("用户不存在或密码错误");
            }
            user = sysUser;
            boolean flag = SecurityUtils.matchesPassword(password, sysUser.getPassword());
            if (!flag) {
                throw new RuntimeException(phoneNumber + "用户不存在或密码错误");
            }
        } else if (String.valueOf(AppUserLoginTypeEnum.CAPTCHA.getCode()).equals(loginType)) {
            SysUser sysUser = getPCUserByPhoneNumber(phoneNumber);
            if (Objects.isNull(sysUser)) {
                throw new RuntimeException(phoneNumber + "用户不存在");
            }
            user = sysUser;
        } else {
            throw new RuntimeException("登录方式错误");
        }
        // 生成token
        return getTokenByPCUserInfo(user);
    }

    /**
     * 获取token
     *
     * @param appUser 用户信息
     * @return token
     */
    private Map<String, String> getTokenByAppUserInfo(AppUser appUser) {
        Map<String, String> payload = new HashMap<>();
        payload.put("id", appUser.getId());
        payload.put("phoneNumber", appUser.getPhoneNumber());
        payload.put("identificationCard", appUser.getIdentificationCard());

        String secretKey = PatientBaseUtil.getSecretKey(appUser.getId());
        //生成JWt的令牌
        String token = JWTUtil.getToken(payload, secretKey);
        payload.put("token", token);
        return payload;
    }

    /**
     * 获取token
     *
     * @param user 用户信息
     * @return token
     */
    private Map<String, String> getTokenByPCUserInfo(SysUser user) {
        String userId = user.getUserId().toString();
        Map<String, String> payload = new HashMap<>();
        payload.put("id", userId);
        payload.put("phoneNumber", user.getPhonenumber());
        String secretKey = PatientBaseUtil.getSecretKey(userId);
        //生成JWt的令牌
        String token = JWTUtil.getToken(payload, secretKey);
        payload.put("token", token);
        return payload;
    }

    /**
     * 检查登录是否存在重复错误的情况。
     * 通过登录类型和电话号码作为标识，检查在Redis缓存中该登录组合的错误尝试次数。
     * 如果错误尝试次数超过预设值，则抛出异常，提示用户登录失败次数过多，请稍后尝试。
     * 此方法用于防止恶意登录攻击，增加系统的安全性。
     *
     * @param loginType   登录类型，例如"手机"、"邮箱"等。
     * @param phoneNumber 用于登录的电话号码。
     * @return 组合了登录类型和电话号码的重复标识字符串。
     * @throws RuntimeException 如果登录失败次数超过预设限制，则抛出运行时异常。
     */
    private String checkRepeatLoginError(String loginType, String phoneNumber) {
        // 构建登录重复标识字符串，格式为"ERM_APP_LOGIN_REPEAT+登录类型+电话号码"。
        String repeat = PatientConstant.ERM_APP_LOGIN_REPEAT + loginType + ":" + phoneNumber;
        // 默认重复次数为1，表示本次登录尝试。
        int repeatNum = 1;
        // 检查Redis缓存中是否存在该登录标识，即是否之前有过登录错误。
        if (redisCache.hasKey(repeat)) {
            // 如果存在，则从缓存中获取重复次数。
            repeatNum = redisCache.getCacheObject(repeat);
            // 如果当前重复次数超过最大允许次数，则抛出异常。
            if (repeatNum >= maxRetryCount) {
                throw new RuntimeException("登录失败次数超过" + maxRetryCount + "次，请稍后再试");
            }
            // 本次登录尝试后，累计重复次数加1。
            repeatNum++;
        }
        // 将更新后的重复次数存入Redis缓存，有效期30分钟。
        redisCache.setCacheObject(repeat, repeatNum, lockTime, TimeUnit.MINUTES);
        // 返回构建的登录重复标识字符串。
        return repeat;
    }

    /**
     * 设置密码
     *
     * @param map 参数
     * @return 返回结果
     * @author mtx
     * @date: 2024/6/13 9:26
     */
    @Override
    public AjaxResult setPassword(Map<String, String> map) {
        String phoneNumber = map.get("phoneNumber");
        String captchaCode = map.get("captchaCode");
        String password = map.get("password");
        String confirmPassword = map.get("confirmPassword");
        // 校验参数
        if (StringUtils.isEmpty(phoneNumber)) {
            return AjaxResult.error("手机号不能为空");
        }
        if (StringUtils.isEmpty(captchaCode)) {
            return AjaxResult.error("验证码不能为空");
        }
        if (StringUtils.isEmpty(password)) {
            return AjaxResult.error("密码不能为空");
        }
        // 校验用户
        AppUser appUser = getAppUserByPhoneNumber(phoneNumber);
        if (Objects.isNull(appUser)) {
            return AjaxResult.error("用户不存在");
        }
        if (!password.equals(confirmPassword)) {
            return AjaxResult.error("两次密码不一致");
        }

        // 修改密码
        appUserService.update(Wrappers.<AppUser>lambdaUpdate()
                .eq(AppUser::getId, appUser.getId())
                .set(AppUser::getPassword, SecurityUtils.encryptPassword(password)));

        return AjaxResult.success();
    }

    /**
     * 修改手机号
     *
     * @param map 参数
     * @return 返回结果
     * @author mtx
     * @date: 2024/6/13 9:29
     */
    @Override
    public AjaxResult updatePhoneNumber(Map<String, String> map) {
        String appPhoneNumber = JWTUtil.getAppPhoneNumber();
        String phoneNumber = map.get("phoneNumber");
        String captchaCode = map.get("captchaCode");

        // 校验参数
        if (appPhoneNumber.equals(phoneNumber)) {
            return AjaxResult.error("手机号一样");
        }
        if (StringUtils.isEmpty(phoneNumber)) {
            return AjaxResult.error("手机号不能为空");
        }
        if (StringUtils.isEmpty(captchaCode)) {
            return AjaxResult.error("验证码不能为空");
        }
        // 注册过的手机号不允许绑定
        if (appUserService.count(Wrappers.<AppUser>lambdaQuery().eq(AppUser::getPhoneNumber, phoneNumber)) > 0) {
            return AjaxResult.error("您所设置的手机号已绑定");
        }

        // 修改本人的患者手机号
        List<AppUserPatients> userPatients = appUserPatientsService.list(Wrappers.<AppUserPatients>lambdaQuery()
                .eq(AppUserPatients::getRelationship, "本人")
                .eq(AppUserPatients::getAppUserId, JWTUtil.getAppUserId()));
        if (CollectionUtils.isNotEmpty(userPatients)) {
            patientService.update(Wrappers.<Patient>lambdaUpdate()
                    .set(Patient::getPatientPhone, phoneNumber)
                    .set(Patient::getLinkPhone, phoneNumber)
                    .in(Patient::getId, userPatients.stream().map(AppUserPatients::getPatientId).collect(Collectors.toList())));
        }

        // 修改手机号
        appUserService.update(Wrappers.<AppUser>lambdaUpdate()
                .eq(AppUser::getId, JWTUtil.getAppUserId())
                .set(AppUser::getPhoneNumber, phoneNumber));

        return AjaxResult.success();
    }

    /**
     * 锁定用户&解锁用户
     *
     * @param map id type 类型（1锁定用户，0解锁用户）
     * @return 返回结果
     * @author mtx
     * @date: 2024/6/13 10:27
     */
    @Override
    public AjaxResult locking(Map<String, String> map) {
        String id = map.get("id");
        String type = map.get("type");
        if (StringUtils.isEmpty(id)) {
            return AjaxResult.error("id不能为空");
        }
        if (StringUtils.isEmpty(type)) {
            return AjaxResult.error("type不能为空");
        }
        if ("0".equals(type)) {
            // 解锁
            this.revert(id);
        } else if ("1".equals(type)) {
            // 锁定用户
            this.removeById(id);
        } else {
            return AjaxResult.error("type错误");
        }

        return AjaxResult.success();
    }

    /**
     * 回复删除数据
     *
     * @param id id
     * @author mtx
     * @date: 2024/6/13 10:33
     */
    @Override
    public void revert(String id) {
        appUserMapper.updateRevert(id);
    }

    /**
     * 添加就诊人
     *
     * @param vo 参数
     * @author mtx
     * @date: 2024/6/14 11:23
     */
    @Override
    public void add(AppUserPatientVO vo) {
        String userId = JWTUtil.getAppUserId();

        String self = "本人";
        if (!self.equals(vo.getUserRelationship())) {
            String patientPhone = vo.getPatientPhone();
            long count1 = appUserService.count(Wrappers.<AppUser>lambdaQuery().eq(AppUser::getPhoneNumber, patientPhone));
            if (count1 > 0) {
                throw new RuntimeException("就诊人已注册，请登录就诊人账号修改");
            }
        } else {
            long count = appUserPatientsService.count(Wrappers.<AppUserPatients>lambdaQuery()
                    .eq(AppUserPatients::getRelationship, self)
                    .eq(AppUserPatients::getAppUserId, userId));
            if (count > 0) {
                throw new RuntimeException("您已添加过本人，请勿重复添加");
            }
        }

        PatientVO patientVO = new PatientVO();
        // 添加患者信息
        Patient one = patientService.getOne(Wrappers.<Patient>lambdaQuery().eq(Patient::getIdentificationCard, vo.getIdentificationCard()));
        if (Objects.nonNull(one)) {
            if (!vo.getPatientPhone().equals(one.getPatientPhone())) {
                throw new RuntimeException("您输入的手机号和身份证号与就诊人预留信息不一致");
            }
            patientVO.setId(one.getId());
        }
        Patient patient = savePatient(vo, patientVO);

        // 如果是默认，则将其他的数据改为非默认
        AppUserPatients appUserPatients = new AppUserPatients();
        if (Objects.nonNull(vo.getIsDefault()) && 1 == vo.getIsDefault()) {
            appUserPatientsService.update(Wrappers.<AppUserPatients>lambdaUpdate()
                    .set(AppUserPatients::getIsDefault, 0)
                    .eq(AppUserPatients::getAppUserId, userId));
            appUserPatients.setIsDefault(vo.getIsDefault());
        } else {
            appUserPatients.setIsDefault(0);
        }

        // 关联账号
        appUserPatients.setAppUserId(userId);
        appUserPatients.setPatientId(patient.getId());
        appUserPatients.setRelationship(vo.getUserRelationship());
        appUserPatientsService.remove(Wrappers.<AppUserPatients>lambdaQuery()
                .eq(AppUserPatients::getAppUserId, userId)
                .eq(AppUserPatients::getPatientId, patient.getId()));
        appUserPatientsService.save(appUserPatients);
    }

    @Override
    public void edit(AppUserPatientVO vo) {
        PatientVO patientVO = new PatientVO();
        String userId = JWTUtil.getAppUserId();
        // 添加患者信息
        Patient one = patientService.getById(vo.getPatientId());
        if (Objects.nonNull(one)) {
            patientVO.setId(one.getId());
        } else {
            throw new IllegalArgumentException("患者信息不存在");
        }
        Patient patient = savePatient(vo, patientVO);

        // 如果是默认，则将其他的数据改为非默认
        Integer isDefault = 0;
        if (Objects.nonNull(vo.getIsDefault()) && 1 == vo.getIsDefault()) {
            appUserPatientsService.update(Wrappers.<AppUserPatients>lambdaUpdate()
                    .set(AppUserPatients::getIsDefault, 0)
                    .eq(AppUserPatients::getAppUserId, userId));
            isDefault = vo.getIsDefault();
        }

        // 关联账号
        appUserPatientsService.update(Wrappers.<AppUserPatients>lambdaUpdate()
                .set(AppUserPatients::getIsDefault, isDefault)
                .set(AppUserPatients::getRelationship, vo.getUserRelationship())
                .eq(AppUserPatients::getAppUserId, userId)
                .eq(AppUserPatients::getPatientId, patient.getId()));
    }

    /**
     * 保存患者信息
     *
     * @param vo        参数
     * @param patientVO 患者信息
     * @return 返回结果
     * @author mtx
     * @date: 2024/6/13 10:01
     */
    private Patient savePatient(AppUserPatientVO vo, PatientVO patientVO) {
        patientVO.setFamilyHistory(vo.getFamilyHistory());
        patientVO.setAllergyHistory(vo.getAllergyHistory());
        patientVO.setPastHistory(vo.getPastHistory());
        patientVO.setPatientName(vo.getPatientName());
        patientVO.setDocumentType(vo.getDocumentType());
        patientVO.setIdentificationCard(vo.getIdentificationCard());
        patientVO.setPatientPhone(vo.getPatientPhone());
        patientVO.setLinkPhone(vo.getPatientPhone());
        patientVO.setHomeAddress(vo.getHomeAddress());
        patientVO.setCreateType(PatientCreateTypeEnum.APP.getCode());
        return patientService.addOrEdit(patientVO);
    }

    /**
     * 通过手机号获取app用户信息
     *
     * @param phoneNumber 手机号
     * @return 返回结果
     * @author mtx
     * @date: 2024/6/13 9:59
     */
    public AppUser getAppUserByPhoneNumber(String phoneNumber) {
        return appUserService.getOne(Wrappers.<AppUser>lambdaQuery().eq(AppUser::getPhoneNumber, phoneNumber));
    }

    /**
     * 通过手机号获取PC用户信息
     *
     * @param phoneNumber 手机号
     * @return 返回结果
     * @author mtx
     * @date: 2024/6/13 9:59
     */
    public SysUser getPCUserByPhoneNumber(String phoneNumber) {
        return sysUserService.selectUserToUserNameOrPhoneNumber(phoneNumber);
    }

    /**
     * 注册
     *
     * @param map 参数
     * @return 用户
     */
    @Override
    public boolean register(Map<String, String> map) {
        String phoneNumber = map.get("phoneNumber");
        if (Objects.nonNull(getAppUserByPhoneNumber(phoneNumber))) {
            throw new IllegalArgumentException("手机号已注册");
        }

        // 添加用户
        AppUser appUser = new AppUser();
        appUser.setPhoneNumber(phoneNumber);
        appUser.setIdentificationCard(map.get("identificationCard"));
        String password = map.get("password");
        if (StringUtils.isNotEmpty(password)) {
            appUser.setPassword(SecurityUtils.encryptPassword(password));
        }
        appUser.setCreateUser(phoneNumber);
        appUser.setUpdateUser(phoneNumber);
        return appUserService.save(appUser);
    }

    /**
     * 当应用程序切换诊所时，同步用户数据。
     * 此方法确保用户在切换诊所后仍然能够访问其个人数据，而这些数据可能会因诊所的不同而有所不同。
     * 它通过验证用户的电话号码和身份证号码来确保数据的准确性，并更新或保存用户的信息以供将来访问。
     *
     * @param beforeAppUser 上一次登录的用户信息，包含电话号码、身份证号码和密码等关键信息。
     * @return 返回一个包含操作结果和新生成的token的AjaxResult对象。
     * 如果电话号码或身份证号码为空，则返回错误信息；否则，返回成功结果和新生成的token。
     * @author mtx
     * @date 2024/6/20 8:53
     */
    @Override
    @Transactional(rollbackFor = {Exception.class})
    public AjaxResult synchronizeData(AppUser beforeAppUser) {
        // 验证电话号码是否为空
        String phoneNumber = beforeAppUser.getPhoneNumber();
        if (StringUtils.isEmpty(phoneNumber)) {
            return AjaxResult.error("手机号不能为空");
        }

        // 验证身份证号码是否为空
        String identificationCard = beforeAppUser.getIdentificationCard();
        if (StringUtils.isEmpty(identificationCard)) {
            return AjaxResult.error("身份证号不能为空");
        }

        // 准备要保存或更新的用户信息
        String password = beforeAppUser.getPassword();
        AppUser saveAppUser = new AppUser();

        // 检查是否已存在相同的电话号码的用户，如果存在，则更新用户信息；否则，创建新用户
        AppUser appUser = getAppUserByPhoneNumber(phoneNumber);
        if (Objects.nonNull(appUser)) {
            saveAppUser.setId(appUser.getId());
            saveAppUser.setPhoneNumber(appUser.getPhoneNumber());
        } else {
            saveAppUser.setPhoneNumber(phoneNumber);
        }

        saveAppUser.setIdentificationCard(identificationCard);
        saveAppUser.setPassword(password);
        this.saveOrUpdate(saveAppUser);

        // 生成新的token，以便用户在切换诊所后能够安全地访问其个人数据
        Map<String, String> payload = getTokenByAppUserInfo(saveAppUser);

        // 退出当前会话，确保之前的token失效
        logout();

        return AjaxResult.success(payload);
    }

    @Override
    public void logout() {
        try {
            String userId = JWTUtil.getAppUserId();
            String key = PatientConstant.ERM_APP_LOGIN_SECRET_KEY + userId;
            redisCache.deleteObject(key);
        } catch (Exception ignored) {
        }
    }

    /**
     * 验证短信验证码时候正确
     *
     * @param phoneNumber 手机号
     * @param captchaCode 录入的验证码
     */
    @Override
    public void checkCaptchaCode(String phoneNumber, String captchaCode) {
        if (StringUtils.isEmpty(phoneNumber)) {
            throw new IllegalArgumentException("手机号不能为空");
        }
        if (StringUtils.isEmpty(captchaCode)) {
            throw new IllegalArgumentException("验证码不能为空");
        }

        String key = PatientConstant.ERM_APP_CAPTCHA_CODE + phoneNumber;
        if (redisCache.hasKey(key)) {
            int code = redisCache.getCacheObject(key);
            boolean b = String.valueOf(code).equals(captchaCode);
            if (b) {
                // 删除验证码
                delCaptchaCode(phoneNumber);
                return;
            }
        }
        throw new IllegalArgumentException("验证码错误");
    }

    /**
     * 删除验证码
     *
     * @param phoneNumber 手机号
     */
    public void delCaptchaCode(String phoneNumber) {
        String key = PatientConstant.ERM_APP_CAPTCHA_CODE + phoneNumber;
        redisCache.deleteObject(key);
    }
}
