package com.njcn.rdms.module.system.service.user;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.DigestUtil;
import com.google.common.annotations.VisibleForTesting;
import com.mzt.logapi.context.LogRecordContext;
import com.mzt.logapi.service.impl.DiffParseFunction;
import com.mzt.logapi.starter.annotation.LogRecord;
import com.njcn.rdms.framework.common.enums.CommonStatusEnum;
import com.njcn.rdms.framework.common.enums.UserTypeEnum;
import com.njcn.rdms.framework.common.exception.ServiceException;
import com.njcn.rdms.framework.common.pojo.PageResult;
import com.njcn.rdms.framework.common.util.collection.CollectionUtils;
import com.njcn.rdms.framework.common.util.object.BeanUtils;
import com.njcn.rdms.framework.common.util.validation.ValidationUtils;
import com.njcn.rdms.module.system.controller.admin.auth.vo.AuthRegisterReqVO;
import com.njcn.rdms.module.system.controller.admin.user.vo.profile.UserProfileUpdatePasswordReqVO;
import com.njcn.rdms.module.system.controller.admin.user.vo.profile.UserProfileUpdateReqVO;
import com.njcn.rdms.module.system.controller.admin.user.vo.user.UserImportExcelVO;
import com.njcn.rdms.module.system.controller.admin.user.vo.user.UserImportRespVO;
import com.njcn.rdms.module.system.controller.admin.user.vo.user.UserPageReqVO;
import com.njcn.rdms.module.system.controller.admin.user.vo.user.UserSaveReqVO;
import com.njcn.rdms.module.system.dal.dataobject.config.ConfigDO;
import com.njcn.rdms.module.system.dal.dataobject.dept.DeptDO;
import com.njcn.rdms.module.system.dal.dataobject.file.FileDO;
import com.njcn.rdms.module.system.dal.dataobject.user.AdminUserDO;
import com.njcn.rdms.module.system.dal.mysql.file.FileMapper;
import com.njcn.rdms.module.system.dal.mysql.user.AdminUserMapper;
import com.njcn.rdms.module.system.framework.file.core.client.FileClient;
import com.njcn.rdms.module.system.framework.file.core.utils.FileTypeUtils;
import com.njcn.rdms.module.system.service.config.ConfigService;
import com.njcn.rdms.module.system.service.dept.DeptService;
import com.njcn.rdms.module.system.service.dept.PostService;
import com.njcn.rdms.module.system.service.file.FileConfigService;
import com.njcn.rdms.module.system.service.oauth2.OAuth2TokenService;
import com.njcn.rdms.module.system.service.permission.PermissionService;
import jakarta.annotation.Resource;
import jakarta.validation.ConstraintViolationException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;

import static com.njcn.rdms.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.njcn.rdms.framework.common.util.collection.CollectionUtils.singleton;
import static com.njcn.rdms.module.system.enums.ErrorCodeConstants.*;
import static com.njcn.rdms.module.system.enums.LogRecordConstants.*;

/**
 * 后台用户 Service 实现类
 *
 * @author hongawen
 */
@Service("adminUserService")
@Slf4j
public class AdminUserServiceImpl implements AdminUserService {

    static final String USER_INIT_PASSWORD_KEY = "system.user.init-password";
    static final String USER_REGISTER_ENABLED_KEY = "system.user.register-enabled";
    private static final Integer DEFAULT_REGISTER_USER_SORT = 999;

    @Resource
    private AdminUserMapper userMapper;
    @Resource
    private DeptService deptService;
    @Resource
    private PostService postService;
    @Resource
    private PermissionService permissionService;
    @Resource
    private PasswordEncoder passwordEncoder;
    @Resource
    @Lazy
    private OAuth2TokenService oauth2TokenService;
    @Resource
    private ConfigService configService;
    @Resource
    private UserManagementRelationService userManagementRelationService;
    @Resource
    private FileConfigService fileConfigService;
    @Resource
    private FileMapper fileMapper;

    @Override
    @Transactional(rollbackFor = Exception.class)
    @LogRecord(type = SYSTEM_USER_TYPE, subType = SYSTEM_USER_CREATE_SUB_TYPE, bizNo = "{{#user.id}}",
            success = SYSTEM_USER_CREATE_SUCCESS)
    public Long createUser(UserSaveReqVO createReqVO) {
        // 1. 校验正确性
        validateUserForCreateOrUpdate(null, createReqVO.getUsername(), createReqVO.getMobile(),
                createReqVO.getEmail(), createReqVO.getDeptId(), createReqVO.getPositionId());
        // 2. 插入用户
        AdminUserDO user = BeanUtils.toBean(createReqVO, AdminUserDO.class);
        user.setStatus(CommonStatusEnum.ENABLE.getStatus()); // 默认开启
        user.setPassword(encodePassword(createReqVO.getPassword())); // 加密密码
        userMapper.insert(user);
        // 3. 记录操作日志上下文
        LogRecordContext.putVariable("user", user);
        return user.getId();
    }

    @Override
    public Long registerUser(AuthRegisterReqVO registerReqVO) {
        // 1.1 校验是否开启注册
        if (ObjUtil.notEqual(getConfigValue(USER_REGISTER_ENABLED_KEY), "true")) {
            throw exception(USER_REGISTER_DISABLED);
        }
        // 1.2 校验正确性
        validateUserForCreateOrUpdate(null, registerReqVO.getUsername(), null, null,
                registerReqVO.getDeptId(), registerReqVO.getPositionId());
        // 2. 插入用户
        AdminUserDO user = BeanUtils.toBean(registerReqVO, AdminUserDO.class);
        user.setStatus(CommonStatusEnum.ENABLE.getStatus()); // 默认开启
        user.setSort(DEFAULT_REGISTER_USER_SORT);
        user.setPassword(encodePassword(registerReqVO.getPassword())); // 加密密码
        userMapper.insert(user);
        return user.getId();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @LogRecord(type = SYSTEM_USER_TYPE, subType = SYSTEM_USER_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}",
            success = SYSTEM_USER_UPDATE_SUCCESS)
    public void updateUser(UserSaveReqVO updateReqVO) {
        updateReqVO.setPassword(null); // 特殊：此处不更新密码
        // 1. 校验正确性
        AdminUserDO oldUser = validateUserForCreateOrUpdate(updateReqVO.getId(), updateReqVO.getUsername(),
                updateReqVO.getMobile(), updateReqVO.getEmail(), updateReqVO.getDeptId(), updateReqVO.getPositionId());
        // 2. 更新用户
        AdminUserDO updateObj = BeanUtils.toBean(updateReqVO, AdminUserDO.class);
        userMapper.updateById(updateObj);
        // 3. 记录操作日志上下文
        LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldUser, UserSaveReqVO.class));
        LogRecordContext.putVariable("user", oldUser);
        // 4. 如果更新后用户已不可用，主动移除其 token
        if (!isUserAvailable(buildAvailabilityUser(oldUser, updateReqVO.getResignedAt(), oldUser.getStatus()))) {
            oauth2TokenService.removeAccessToken(oldUser.getId(), UserTypeEnum.ADMIN.getValue());
        }
    }

    @Override
    public void updateUserLogin(Long id, String loginIp) {
        userMapper.updateById(new AdminUserDO().setId(id).setLoginIp(loginIp).setLoginDate(LocalDateTime.now()));
    }

    @Override
    public void updateUserProfile(Long id, UserProfileUpdateReqVO reqVO) {
        // 校验正确性
        validateUserExists(id);
        validateEmailUnique(id, reqVO.getEmail());
        validateMobileUnique(id, reqVO.getMobile());
        // 执行更新
        userMapper.updateById(BeanUtils.toBean(reqVO, AdminUserDO.class).setId(id));
    }

    @Override
    public void updateUserPassword(Long id, UserProfileUpdatePasswordReqVO reqVO) {
        // 校验旧密码
        validateOldPassword(id, reqVO.getOldPassword());
        // 复用已有的密码更新逻辑，保留审计日志
        updateUserPassword(id, reqVO.getNewPassword());
        // 密码变更后强制使原有令牌失效
        oauth2TokenService.removeAccessToken(id, UserTypeEnum.ADMIN.getValue());
    }

    @Override
    @LogRecord(type = SYSTEM_USER_TYPE, subType = SYSTEM_USER_UPDATE_PASSWORD_SUB_TYPE, bizNo = "{{#id}}",
            success = SYSTEM_USER_UPDATE_PASSWORD_SUCCESS)
    public void updateUserPassword(Long id, String password) {
        // 1. 校验用户存在
        AdminUserDO user = validateUserExists(id);
        // 2. 更新密码
        AdminUserDO updateObj = new AdminUserDO();
        updateObj.setId(id);
        updateObj.setPassword(encodePassword(password)); // 加密密码
        userMapper.updateById(updateObj);
        // 3. 记录操作日志上下文
        LogRecordContext.putVariable("user", user);
        LogRecordContext.putVariable("newPassword", updateObj.getPassword());
    }

    @Override
    public void updateUserStatus(Long id, Integer status) {
        // 校验用户存在
        AdminUserDO user = validateUserExists(id);
        // 更新状态
        AdminUserDO updateObj = new AdminUserDO();
        updateObj.setId(id);
        updateObj.setStatus(status);
        userMapper.updateById(updateObj);
        // 如果更新后用户不可用，则删除其 Token 信息
        if (!isUserAvailable(buildAvailabilityUser(user, user.getResignedAt(), status))) {
            oauth2TokenService.removeAccessToken(id, UserTypeEnum.ADMIN.getValue());
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @LogRecord(type = SYSTEM_USER_TYPE, subType = SYSTEM_USER_DELETE_SUB_TYPE, bizNo = "{{#id}}",
            success = SYSTEM_USER_DELETE_SUCCESS)
    public void deleteUser(Long id) {
        // 1. 校验用户存在
        AdminUserDO user = validateUserExists(id);
        // 2. 删除用户及其关联数据
        // 2.1 删除前判断管理链路表是否还在用该用户
        Boolean res = userManagementRelationService.hasRelation(id);
        if (res) {
            throw exception(USER_MANAGEMENT_RELATION_EXISTS);
        }
        // 2.2 确认用户关系表中没使用该用户可继续往下执行
        userMapper.deleteById(id);
        permissionService.processUserDeleted(id);
        oauth2TokenService.removeAccessToken(id, UserTypeEnum.ADMIN.getValue());
        // 3. 记录操作日志上下文
        LogRecordContext.putVariable("user", user);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteUserList(List<Long> ids) {
        //批量删除前查看是否管理链路表还在使用该用户
        for (Long id : ids) {
            Boolean res = userManagementRelationService.hasRelation(id);
            if (res) {
                throw exception(USER_MANAGEMENT_RELATION_EXISTS);
            }
        }
        // 1. 批量删除用户
        userMapper.deleteByIds(ids);
        // 2. 批量删除用户关联数据
        ids.forEach(id -> {
            permissionService.processUserDeleted(id);
            oauth2TokenService.removeAccessToken(id, UserTypeEnum.ADMIN.getValue());
        });
    }

    @Override
    public AdminUserDO getUserByUsername(String username) {
        return userMapper.selectByUsername(username);
    }

    @Override
    public AdminUserDO getUserByMobile(String mobile) {
        return userMapper.selectByMobile(mobile);
    }

    @Override
    public PageResult<AdminUserDO> getUserPage(UserPageReqVO reqVO) {
        // 如果有角色编号，查询角色对应的用户编号
        Set<Long> userIds = null;
        if (reqVO.getRoleId() != null) {
            userIds = permissionService.getUserRoleIdListByRoleId(singleton(reqVO.getRoleId()));
            if (CollUtil.isEmpty(userIds)) {
                return PageResult.empty();
            }
        }
        // 分页查询
        return userMapper.selectPage(reqVO, getDeptCondition(reqVO.getDeptId()), userIds);
    }

    @Override
    public AdminUserDO getUser(Long id) {
        return userMapper.selectById(id);
    }

    @Override
    public List<AdminUserDO> getUserListByDeptIds(Collection<Long> deptIds) {
        if (CollUtil.isEmpty(deptIds)) {
            return Collections.emptyList();
        }
        return userMapper.selectListByDeptIds(deptIds);
    }

    @Override
    public List<AdminUserDO> getUserListByPostIds(Collection<Long> postIds) {
        if (CollUtil.isEmpty(postIds)) {
            return Collections.emptyList();
        }
        return userMapper.selectListByPositionIds(postIds);
    }

    @Override
    public List<AdminUserDO> getUserList(Collection<Long> ids) {
        if (CollUtil.isEmpty(ids)) {
            return Collections.emptyList();
        }
        return userMapper.selectByIds(ids);
    }

    @Override
    public void validateUserList(Collection<Long> ids) {
        if (CollUtil.isEmpty(ids)) {
            return;
        }
        // 获得用户信息
        List<AdminUserDO> users = userMapper.selectByIds(ids);
        Map<Long, AdminUserDO> userMap = CollectionUtils.convertMap(users, AdminUserDO::getId);
        // 校验
        ids.forEach(id -> {
            AdminUserDO user = userMap.get(id);
            if (user == null) {
                throw exception(USER_NOT_EXISTS);
            }
            if (CommonStatusEnum.isDisable(user.getStatus())) {
                throw exception(USER_IS_DISABLE, user.getNickname());
            }
            if (isUserResigned(user)) {
                throw exception(USER_IS_RESIGNED, user.getNickname());
            }
        });
    }

    @Override
    public List<AdminUserDO> getUserListByNickname(String nickname) {
        return userMapper.selectListByNickname(nickname);
    }

    /**
     * 获得部门条件：查询指定部门的子部门编号们，包括自身
     *
     * @param deptId 部门编号
     * @return 部门编号集合
     */
    private Set<Long> getDeptCondition(Long deptId) {
        if (deptId == null) {
            return Collections.emptySet();
        }
        Set<Long> deptIds = CollectionUtils.convertSet(deptService.getChildDeptList(deptId), DeptDO::getId);
        deptIds.add(deptId); // 包括自身
        return deptIds;
    }

    private AdminUserDO validateUserForCreateOrUpdate(Long id, String username, String mobile, String email,
                                                      Long deptId, Long positionId) {
        // 校验用户存在
        AdminUserDO user = validateUserExists(id);
        // 校验用户名唯一
        validateUsernameUnique(id, username);
        // 校验手机号唯一
        validateMobileUnique(id, mobile);
        // 校验邮箱唯一
        validateEmailUnique(id, email);
        // 校验部门处于开启状态
        deptService.validateDeptList(singleton(deptId));
        // 校验主岗位处于开启状态
        postService.validatePostList(singleton(positionId));
        return user;
    }

    @VisibleForTesting
    AdminUserDO validateUserExists(Long id) {
        if (id == null) {
            return null;
        }
        AdminUserDO user = userMapper.selectById(id);
        if (user == null) {
            throw exception(USER_NOT_EXISTS);
        }
        return user;
    }

    @VisibleForTesting
    void validateUsernameUnique(Long id, String username) {
        if (StrUtil.isBlank(username)) {
            return;
        }
        AdminUserDO user = userMapper.selectByUsername(username);
        if (user == null) {
            return;
        }
        // 如果 id 为空，说明不用比较是否为相同 id 的用户
        if (id == null || !user.getId().equals(id)) {
            throw exception(USER_USERNAME_EXISTS);
        }
    }

    @VisibleForTesting
    void validateEmailUnique(Long id, String email) {
        if (StrUtil.isBlank(email)) {
            return;
        }
        AdminUserDO user = userMapper.selectByEmail(email);
        if (user == null) {
            return;
        }
        // 如果 id 为空，说明不用比较是否为相同 id 的用户
        if (id == null || !user.getId().equals(id)) {
            throw exception(USER_EMAIL_EXISTS);
        }
    }

    @VisibleForTesting
    void validateMobileUnique(Long id, String mobile) {
        if (StrUtil.isBlank(mobile)) {
            return;
        }
        AdminUserDO user = userMapper.selectByMobile(mobile);
        if (user == null) {
            return;
        }
        // 如果 id 为空，说明不用比较是否为相同 id 的用户
        if (id == null || !user.getId().equals(id)) {
            throw exception(USER_MOBILE_EXISTS);
        }
    }

    /**
     * 校验旧密码
     *
     * @param id          用户 ID
     * @param oldPassword 旧密码
     */
    @VisibleForTesting
    void validateOldPassword(Long id, String oldPassword) {
        AdminUserDO user = userMapper.selectById(id);
        if (user == null) {
            throw exception(USER_NOT_EXISTS);
        }
        if (!isPasswordMatch(oldPassword, user.getPassword())) {
            throw exception(USER_PASSWORD_FAILED);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public UserImportRespVO importUserList(List<UserImportExcelVO> importUsers, boolean isUpdateSupport) {
        // 1.1 参数校验
        if (CollUtil.isEmpty(importUsers)) {
            throw exception(USER_IMPORT_LIST_IS_EMPTY);
        }
        // 1.2 初始化密码不能为空
        String initPassword = getConfigValue(USER_INIT_PASSWORD_KEY);
        if (StrUtil.isEmpty(initPassword)) {
            throw exception(USER_IMPORT_INIT_PASSWORD);
        }
        // 2. 遍历，逐个创建或更新
        UserImportRespVO respVO = UserImportRespVO.builder().createUsernames(new ArrayList<>())
                .updateUsernames(new ArrayList<>()).failureUsernames(new LinkedHashMap<>()).build();
        AtomicInteger index = new AtomicInteger(1);
        importUsers.forEach(importUser -> {
            int currentIndex = index.getAndIncrement();
            // 2.1.1 校验字段是否符合要求
            try {
                ValidationUtils.validate(BeanUtils.toBean(importUser, UserSaveReqVO.class).setPassword(initPassword));
            } catch (ConstraintViolationException ex) {
                String key = StrUtil.blankToDefault(importUser.getUsername(), "第 " + currentIndex + " 行");
                respVO.getFailureUsernames().put(key, ex.getMessage());
                return;
            }
            // 2.1.2 校验业务约束
            try {
                validateUserForCreateOrUpdate(null, null, importUser.getMobile(), importUser.getEmail(),
                        importUser.getDeptId(), importUser.getPositionId());
            } catch (ServiceException ex) {
                respVO.getFailureUsernames().put(importUser.getUsername(), ex.getMessage());
                return;
            }
            // 2.2.1 判断如果不存在，则插入
            AdminUserDO existUser = userMapper.selectByUsername(importUser.getUsername());
            if (existUser == null) {
                userMapper.insert(BeanUtils.toBean(importUser, AdminUserDO.class)
                        .setPassword(encodePassword(initPassword)));
                respVO.getCreateUsernames().add(importUser.getUsername());
                return;
            }
            // 2.2.2 如果存在，判断是否允许更新
            if (!isUpdateSupport) {
                respVO.getFailureUsernames().put(importUser.getUsername(), USER_USERNAME_EXISTS.getMsg());
                return;
            }
            AdminUserDO updateUser = BeanUtils.toBean(importUser, AdminUserDO.class);
            updateUser.setId(existUser.getId());
            userMapper.updateById(updateUser);
            respVO.getUpdateUsernames().add(importUser.getUsername());
        });
        return respVO;
    }

    @Override
    public List<AdminUserDO> getUserListByStatus(Integer status) {
        return userMapper.selectListByStatus(status);
    }

    @Override
    public boolean isPasswordMatch(String rawPassword, String encodedPassword) {
        return passwordEncoder.matches(rawPassword, encodedPassword);
    }

    @Override
    public boolean isUserResigned(AdminUserDO user) {
        if (user == null || user.getResignedAt() == null) {
            return false;
        }
        return !user.getResignedAt().isAfter(LocalDateTime.now());
    }

    @Override
    public boolean isUserAvailable(AdminUserDO user) {
        return user != null
                && CommonStatusEnum.ENABLE.getStatus().equals(user.getStatus())
                && !isUserResigned(user);
    }

    @Override
    public Set<Long> listEnabledUserIdsByDeptIds(Collection<Long> deptIds) {
        List<AdminUserDO> users = getUserListByDeptIds(deptIds);
        return users.stream()
                .filter(this::isUserAvailable)
                .map(AdminUserDO::getId)
                .collect(java.util.stream.Collectors.toSet());
    }

    @Override
    public List<AdminUserDO> getAllUserByDeptId(Long deptId) {
        Set<Long> deptCondition = getDeptCondition(deptId);
        return getUserListByDeptIds(deptCondition);
    }

    @Override
    public FileDO uploadAvatar(MultipartFile file, AdminUserDO user) {
        String type = file.getContentType();
        String name = file.getOriginalFilename();
        FileDO fileDO = null;
        try {
            byte[] content = IoUtil.readBytes(file.getInputStream());

            // 1.1 处理 type 为空的情况
            if (StrUtil.isEmpty(type)) {
                type = FileTypeUtils.getMineType(content, name);
            }
            // 验证文件类型
            validateImageFile(file);

            // 1.2 处理 name 为空的情况
            if (StrUtil.isEmpty(name)) {
                name = DigestUtil.sha256Hex(content);
            }
            if (StrUtil.isEmpty(FileUtil.extName(name))) {
                // 如果 name 没有后缀 type，则补充后缀
                String extension = FileTypeUtils.getExtension(type);
                if (StrUtil.isNotEmpty(extension)) {
                    name = name + extension;
                }
            }

            // 2.1 生成上传的 path，需要保证唯一
            String path = FileUtil.FILE_SEPARATOR + "rdms\\avatar" + FileUtil.FILE_SEPARATOR + generateUniqueFileName(name);
            // 2.2 上传到文件存储器
            FileClient client = fileConfigService.getMasterFileClient();
            Assert.notNull(client, "客户端(master) 不能为空");
            String url = null;

            url = client.upload(content, path, type);

            //删除旧的
            client.delete(user.getAvatar());

            // 3. 保存到数据库
            fileDO = new FileDO().setConfigId(client.getId())
                    .setName(name).setPath(path).setUrl(url)
                    .setType(type).setSize((long) content.length);
            fileMapper.insert(fileDO);
            user.setAvatar(fileDO.getUrl());
            this.userMapper.updateById(user);
        } catch (Exception e) {
            throw exception(USER_AVATAR_UPLOAD_FAILED);
        }
        return fileDO;
    }

    private void validateImageFile(MultipartFile file) {
        // 检查文件类型
        String contentType = file.getContentType();
        if (contentType == null || !contentType.startsWith("image/")) {
            throw exception(USER_AVATAR_SUFFIX_ERROR);
        }

        // 检查文件大小（例如限制5MB）
        if (file.getSize() > 5 * 1024 * 1024) {
            throw exception(USER_AVATAR_SIZE_ERROR);
        }
    }

    private String generateUniqueFileName(String originalFilename) {
        String extension = "";
        if (originalFilename != null && originalFilename.contains(".")) {
            extension = originalFilename.substring(originalFilename.lastIndexOf("."));
        }
        return UUID.randomUUID().toString() + extension;
    }

    /**
     * 对密码进行加密
     *
     * @param password 密码
     * @return 加密后的密码
     */
    private String encodePassword(String password) {
        return passwordEncoder.encode(password);
    }

    /**
     * 读取 system 模块内部参数配置，避免同服务自调用 Feign。
     *
     * @param key 配置键
     * @return 配置值
     */
    private String getConfigValue(String key) {
        ConfigDO config = configService.getConfigByKey(key);
        return config != null ? config.getValue() : null;
    }

    private AdminUserDO buildAvailabilityUser(AdminUserDO user, LocalDateTime resignedAt, Integer status) {
        return new AdminUserDO()
                .setId(user.getId())
                .setNickname(user.getNickname())
                .setStatus(status)
                .setResignedAt(resignedAt);
    }

}
