package com.njcn.rdms.module.project.service.project;

import com.njcn.rdms.framework.common.util.json.JsonUtils;
import com.njcn.rdms.framework.security.core.util.SecurityFrameworkUtils;
import com.njcn.rdms.module.project.constant.ObjectActivityConstants;
import com.njcn.rdms.module.project.constant.ObjectRoleConstants;
import com.njcn.rdms.module.project.constant.ProjectExecutionConstants;
import com.njcn.rdms.module.project.constant.ProjectObjectConstants;
import com.njcn.rdms.module.project.controller.admin.project.vo.member.ProjectMemberInactiveReqVO;
import com.njcn.rdms.module.project.controller.admin.project.vo.member.ProjectMemberRespVO;
import com.njcn.rdms.module.project.controller.admin.project.vo.member.ProjectMemberSaveReqVO;
import com.njcn.rdms.module.project.controller.admin.project.vo.member.ProjectMemberUpdateReqVO;
import com.njcn.rdms.module.project.dal.dataobject.audit.BizAuditLogDO;
import com.njcn.rdms.module.project.dal.dataobject.member.UserObjectRoleDO;
import com.njcn.rdms.module.project.dal.dataobject.project.ProjectDO;
import com.njcn.rdms.module.project.dal.dataobject.status.ObjectStatusModelDO;
import com.njcn.rdms.module.project.dal.mysql.audit.BizAuditLogMapper;
import com.njcn.rdms.module.project.dal.mysql.member.UserObjectRoleMapper;
import com.njcn.rdms.module.project.dal.mysql.project.ProjectMapper;
import com.njcn.rdms.module.project.dal.mysql.project.execution.ProjectExecutionMapper;
import com.njcn.rdms.module.project.dal.mysql.status.ObjectStatusModelMapper;
import com.njcn.rdms.module.project.enums.ErrorCodeConstants;
import com.njcn.rdms.module.project.framework.security.annotation.CheckObjectPermission;
import com.njcn.rdms.module.system.api.permission.ObjectPermissionApi;
import com.njcn.rdms.module.system.api.permission.dto.ObjectRoleRespDTO;
import com.njcn.rdms.module.system.api.user.AdminUserApi;
import com.njcn.rdms.module.system.api.user.dto.AdminUserRespDTO;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.time.LocalDateTime;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

import static com.njcn.rdms.framework.common.exception.util.ServiceExceptionUtil.exception;

/**
 * 项目成员 Service 实现类
 */
@Service
public class ProjectMemberServiceImpl implements ProjectMemberService {

    @Resource
    private ProjectMapper projectMapper;
    @Resource
    private UserObjectRoleMapper userObjectRoleMapper;
    @Resource
    private ObjectPermissionApi objectPermissionApi;
    @Resource
    private BizAuditLogMapper bizAuditLogMapper;
    @Resource
    private ObjectStatusModelMapper objectStatusModelMapper;
    @Resource
    private AdminUserApi adminUserApi;
    @Resource
    private ProjectExecutionMapper projectExecutionMapper;

    @Override
    public List<ProjectMemberRespVO> getProjectMemberList(Long projectId) {
        ProjectDO project = validateProjectExists(projectId);
        List<UserObjectRoleDO> members = userObjectRoleMapper.selectListByObject(ProjectObjectConstants.OBJECT_TYPE, projectId);
        Map<Long, ObjectRoleRespDTO> roleMap = getRoleMap(members.stream().map(UserObjectRoleDO::getRoleId).collect(Collectors.toSet()));
        Map<Long, AdminUserRespDTO> userMap = getUserMap(members.stream().map(UserObjectRoleDO::getUserId).collect(Collectors.toSet()));
        return members.stream().map(member -> {
            ProjectMemberRespVO respVO = new ProjectMemberRespVO();
            respVO.setId(member.getId());
            respVO.setUserId(member.getUserId());
            AdminUserRespDTO user = userMap.get(member.getUserId());
            respVO.setUserNickname(user == null ? null : user.getNickname());
            respVO.setRoleId(member.getRoleId());
            ObjectRoleRespDTO role = roleMap.get(member.getRoleId());
            respVO.setRoleName(role == null ? null : role.getName());
            respVO.setRoleCode(role == null ? null : role.getCode());
            respVO.setManagerFlag(Objects.equals(member.getUserId(), project.getManagerUserId())
                    && Objects.equals(member.getStatus(), ObjectRoleConstants.MEMBER_STATUS_ACTIVE));
            respVO.setStatus(member.getStatus());
            respVO.setJoinedTime(member.getJoinedTime());
            respVO.setLeftTime(member.getLeftTime());
            respVO.setRemark(member.getRemark());
            return respVO;
        }).collect(Collectors.toList());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @CheckObjectPermission(objectType = ProjectObjectConstants.OBJECT_TYPE, objectId = "#projectId",
            permission = ProjectObjectConstants.PERMISSION_MEMBER)
    public Long createProjectMember(Long projectId, ProjectMemberSaveReqVO reqVO) {
        ProjectDO project = validateProjectEditable(projectId);
        validateMemberUser(reqVO.getUserId());
        ObjectRoleRespDTO targetRole = validateProjectRole(reqVO.getRoleId());
        UserObjectRoleDO existingMember = userObjectRoleMapper
                .selectByObjectAndUserId(ProjectObjectConstants.OBJECT_TYPE, projectId, reqVO.getUserId());
        if (existingMember != null && Objects.equals(existingMember.getStatus(), ObjectRoleConstants.MEMBER_STATUS_ACTIVE)) {
            throw exception(ErrorCodeConstants.PROJECT_MEMBER_ALREADY_EXISTS);
        }

        UserObjectRoleDO before = existingMember == null ? null : cloneMember(existingMember);
        UserObjectRoleDO member = existingMember == null ? new UserObjectRoleDO() : existingMember;
        member.setUserId(reqVO.getUserId());
        member.setObjectType(ProjectObjectConstants.OBJECT_TYPE);
        member.setObjectId(projectId);
        member.setRoleId(targetRole.getId());
        member.setStatus(ObjectRoleConstants.MEMBER_STATUS_ACTIVE);
        member.setJoinedTime(LocalDateTime.now());
        member.setLeftTime(null);
        member.setRemark(normalizeNullableText(reqVO.getRemark()));
        if (existingMember == null) {
            userObjectRoleMapper.insert(member);
        } else {
            userObjectRoleMapper.updateById(member);
        }
        writeMemberAuditLog(member, ObjectActivityConstants.MEMBER_ACTION_ADD, before, member, null);

        if (isManagerRole(targetRole)) {
            transferManager(project, member, reqVO.getPreviousManagerUserId(), reqVO.getPreviousManagerRoleId(), null);
        }
        return member.getId();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @CheckObjectPermission(objectType = ProjectObjectConstants.OBJECT_TYPE, objectId = "#projectId",
            permission = ProjectObjectConstants.PERMISSION_MEMBER)
    public void updateProjectMember(Long projectId, Long memberId, ProjectMemberUpdateReqVO reqVO) {
        ProjectDO project = validateProjectEditable(projectId);
        UserObjectRoleDO member = validateMemberExists(projectId, memberId);
        if (!Objects.equals(member.getStatus(), ObjectRoleConstants.MEMBER_STATUS_ACTIVE)) {
            throw exception(ErrorCodeConstants.PROJECT_MEMBER_NOT_ACTIVE);
        }
        ObjectRoleRespDTO targetRole = validateProjectRole(reqVO.getRoleId());
        UserObjectRoleDO before = cloneMember(member);
        member.setRemark(normalizeNullableText(reqVO.getRemark()));

        if (isManagerRole(targetRole)) {
            // 项目经理交接只切换负责人并调整原经理角色，不再把原经理自动移出项目团队。
            member.setRoleId(targetRole.getId());
            userObjectRoleMapper.updateById(member);
            transferManager(project, member, reqVO.getPreviousManagerUserId(),
                    reqVO.getPreviousManagerRoleId(), normalizeNullableText(reqVO.getReason()));
        } else {
            if (Objects.equals(member.getUserId(), project.getManagerUserId())) {
                throw exception(ErrorCodeConstants.PROJECT_MANAGER_MEMBER_NOT_ALLOW_DOWNGRADE);
            }
            member.setRoleId(targetRole.getId());
            userObjectRoleMapper.updateById(member);
        }

        writeMemberAuditLog(member, ObjectActivityConstants.MEMBER_ACTION_UPDATE,
                before, member, normalizeNullableText(reqVO.getReason()));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @CheckObjectPermission(objectType = ProjectObjectConstants.OBJECT_TYPE, objectId = "#projectId",
            permission = ProjectObjectConstants.PERMISSION_MEMBER)
    public void inactiveProjectMember(Long projectId, Long memberId, ProjectMemberInactiveReqVO reqVO) {
        ProjectDO project = validateProjectEditable(projectId);
        UserObjectRoleDO member = validateMemberExists(projectId, memberId);
        if (!Objects.equals(member.getStatus(), ObjectRoleConstants.MEMBER_STATUS_ACTIVE)) {
            throw exception(ErrorCodeConstants.PROJECT_MEMBER_NOT_ACTIVE);
        }
        if (Objects.equals(member.getUserId(), project.getManagerUserId())) {
            throw exception(ErrorCodeConstants.PROJECT_MANAGER_MEMBER_NOT_ALLOW_REMOVE);
        }
        validateNoOpenOwnedExecutions(projectId, member.getUserId());

        UserObjectRoleDO before = cloneMember(member);
        member.setStatus(ObjectRoleConstants.MEMBER_STATUS_INACTIVE);
        member.setLeftTime(LocalDateTime.now());
        userObjectRoleMapper.updateById(member);
        writeMemberAuditLog(member, ObjectActivityConstants.MEMBER_ACTION_REMOVE, before, member,
                normalizeNullableText(reqVO.getReason()));
    }

    private ProjectDO validateProjectExists(Long projectId) {
        if (projectId == null) {
            throw exception(ErrorCodeConstants.PROJECT_NOT_EXISTS);
        }
        ProjectDO project = projectMapper.selectById(projectId);
        if (project == null) {
            throw exception(ErrorCodeConstants.PROJECT_NOT_EXISTS);
        }
        return project;
    }

    private ProjectDO validateProjectEditable(Long projectId) {
        ProjectDO project = validateProjectExists(projectId);
        // 项目成员维护当前完全跟随状态模型 allow_edit，不再按具体状态编码写死可编辑范围。
        ObjectStatusModelDO statusModel = objectStatusModelMapper
                .selectByObjectTypeAndStatusCodeEnabled(ProjectObjectConstants.OBJECT_TYPE, project.getStatusCode());
        if (statusModel == null) {
            throw exception(ErrorCodeConstants.PROJECT_STATUS_MODEL_NOT_EXISTS_OR_DISABLED);
        }
        if (!Boolean.TRUE.equals(statusModel.getAllowEdit())) {
            throw exception(ErrorCodeConstants.PROJECT_STATUS_NOT_ALLOW_EDIT);
        }
        return project;
    }

    private void validateMemberUser(Long userId) {
        try {
            Boolean valid = adminUserApi.validateUserList(List.of(userId)).getCheckedData();
            if (Boolean.TRUE.equals(valid)) {
                return;
            }
        } catch (RuntimeException ex) {
            throw exception(ErrorCodeConstants.PROJECT_MEMBER_USER_INVALID);
        }
        throw exception(ErrorCodeConstants.PROJECT_MEMBER_USER_INVALID);
    }

    private UserObjectRoleDO validateMemberExists(Long projectId, Long memberId) {
        UserObjectRoleDO member = userObjectRoleMapper.selectByIdAndObject(memberId, ProjectObjectConstants.OBJECT_TYPE, projectId);
        if (member == null) {
            throw exception(ErrorCodeConstants.PROJECT_MEMBER_NOT_EXISTS);
        }
        return member;
    }

    private void validateNoOpenOwnedExecutions(Long projectId, Long userId) {
        Integer count = projectExecutionMapper.countNonTerminalByProjectIdAndOwnerId(projectId, userId,
                getExecutionTerminalStatusCodes());
        if (count != null && count > 0) {
            throw exception(ErrorCodeConstants.PROJECT_EXECUTION_OWNER_HANDOFF_REQUIRED);
        }
    }

    private List<String> getExecutionTerminalStatusCodes() {
        List<ObjectStatusModelDO> statusModels = objectStatusModelMapper
                .selectListByObjectType(ProjectExecutionConstants.OBJECT_TYPE);
        if (statusModels == null || statusModels.isEmpty()) {
            throw exception(ErrorCodeConstants.PROJECT_EXECUTION_STATUS_MODEL_NOT_EXISTS_OR_DISABLED);
        }
        List<String> terminalStatusCodes = statusModels.stream()
                .filter(statusModel -> Boolean.TRUE.equals(statusModel.getTerminalFlag()))
                .map(ObjectStatusModelDO::getStatusCode)
                .filter(StringUtils::hasText)
                .toList();
        if (terminalStatusCodes.isEmpty()) {
            throw exception(ErrorCodeConstants.PROJECT_EXECUTION_STATUS_MODEL_NOT_EXISTS_OR_DISABLED);
        }
        return terminalStatusCodes;
    }

    private ObjectRoleRespDTO validateProjectRole(Long roleId) {
        ObjectRoleRespDTO role = objectPermissionApi
                .getObjectRoleById(roleId, ObjectRoleConstants.ROLE_SCOPE_OBJECT, ProjectObjectConstants.OBJECT_TYPE)
                .getCheckedData();
        if (role == null) {
            throw exception(ErrorCodeConstants.PROJECT_MEMBER_ROLE_INVALID);
        }
        return role;
    }

    private void transferManager(ProjectDO project,
                                 UserObjectRoleDO targetManagerMember,
                                 Long previousManagerUserId,
                                 Long previousManagerRoleId,
                                 String reason) {
        Long currentManagerUserId = project.getManagerUserId();
        Long targetManagerUserId = targetManagerMember.getUserId();
        if (Objects.equals(currentManagerUserId, targetManagerUserId)) {
            project.setManagerUserId(targetManagerUserId);
            projectMapper.updateById(project);
            return;
        }

        ObjectRoleRespDTO previousManagerRole = validatePreviousManagerTransfer(currentManagerUserId,
                previousManagerUserId, previousManagerRoleId);
        transferPreviousManager(project.getId(), previousManagerUserId, previousManagerRole.getId(), reason);

        project.setManagerUserId(targetManagerUserId);
        projectMapper.updateById(project);
        writeManagerChangeAuditLog(project.getId(), currentManagerUserId, targetManagerUserId, reason);
    }

    private ObjectRoleRespDTO validatePreviousManagerTransfer(Long currentManagerUserId,
                                                              Long previousManagerUserId,
                                                              Long previousManagerRoleId) {
        if (currentManagerUserId == null
                || previousManagerUserId == null
                || previousManagerRoleId == null) {
            throw exception(ErrorCodeConstants.PROJECT_MANAGER_TRANSFER_INFO_REQUIRED);
        }
        if (!Objects.equals(currentManagerUserId, previousManagerUserId)) {
            throw exception(ErrorCodeConstants.PROJECT_MANAGER_TRANSFER_SOURCE_INVALID);
        }
        ObjectRoleRespDTO previousManagerRole = validateProjectRole(previousManagerRoleId);
        if (isManagerRole(previousManagerRole)) {
            throw exception(ErrorCodeConstants.PROJECT_MANAGER_TRANSFER_ROLE_INVALID);
        }
        return previousManagerRole;
    }

    private void transferPreviousManager(Long projectId, Long previousManagerUserId, Long previousManagerRoleId, String reason) {
        UserObjectRoleDO existingMember = userObjectRoleMapper
                .selectByObjectAndUserId(ProjectObjectConstants.OBJECT_TYPE, projectId, previousManagerUserId);
        UserObjectRoleDO before = existingMember == null ? null : cloneMember(existingMember);
        LocalDateTime now = LocalDateTime.now();
        UserObjectRoleDO member;
        String actionType;
        if (existingMember == null) {
            member = new UserObjectRoleDO();
            member.setUserId(previousManagerUserId);
            member.setObjectType(ProjectObjectConstants.OBJECT_TYPE);
            member.setObjectId(projectId);
            member.setRoleId(previousManagerRoleId);
            member.setStatus(ObjectRoleConstants.MEMBER_STATUS_ACTIVE);
            member.setJoinedTime(now);
            member.setLeftTime(null);
            member.setRemark(null);
            userObjectRoleMapper.insert(member);
            actionType = ObjectActivityConstants.MEMBER_ACTION_ADD;
        } else {
            member = existingMember;
            member.setRoleId(previousManagerRoleId);
            member.setStatus(ObjectRoleConstants.MEMBER_STATUS_ACTIVE);
            member.setLeftTime(null);
            if (!Objects.equals(before.getStatus(), ObjectRoleConstants.MEMBER_STATUS_ACTIVE)) {
                member.setJoinedTime(now);
                actionType = ObjectActivityConstants.MEMBER_ACTION_ADD;
            } else {
                actionType = ObjectActivityConstants.MEMBER_ACTION_UPDATE;
            }
            userObjectRoleMapper.updateById(member);
        }
        writeMemberAuditLog(member, actionType, before, member, reason);
    }

    private boolean isManagerRole(ObjectRoleRespDTO role) {
        return Objects.equals(ProjectObjectConstants.MANAGER_ROLE_CODE, role.getCode());
    }

    private Map<Long, ObjectRoleRespDTO> getRoleMap(Set<Long> roleIds) {
        if (roleIds.isEmpty()) {
            return Collections.emptyMap();
        }
        List<ObjectRoleRespDTO> roles = objectPermissionApi
                .getObjectRoleList(roleIds, ObjectRoleConstants.ROLE_SCOPE_OBJECT, ProjectObjectConstants.OBJECT_TYPE)
                .getCheckedData();
        if (roles == null || roles.isEmpty()) {
            return Collections.emptyMap();
        }
        return roles.stream().collect(Collectors.toMap(ObjectRoleRespDTO::getId, Function.identity()));
    }

    private Map<Long, AdminUserRespDTO> getUserMap(Set<Long> userIds) {
        if (userIds.isEmpty()) {
            return Collections.emptyMap();
        }
        return adminUserApi.getUserMap(userIds);
    }

    private void writeMemberAuditLog(UserObjectRoleDO member,
                                     String actionType,
                                     UserObjectRoleDO before,
                                     UserObjectRoleDO after,
                                     String reason) {
        BizAuditLogDO auditLog = new BizAuditLogDO();
        auditLog.setBizType(ObjectActivityConstants.MEMBER_BIZ_TYPE);
        auditLog.setBizId(member.getId());
        auditLog.setActionType(actionType);
        auditLog.setFieldChanges(buildMemberFieldChanges(before, after));
        auditLog.setReason(reason);
        auditLog.setOperatorUserId(SecurityFrameworkUtils.getLoginUserId());
        auditLog.setOperatorName(defaultText(SecurityFrameworkUtils.getLoginUserNickname()));
        bizAuditLogMapper.insert(auditLog);
    }

    private void writeManagerChangeAuditLog(Long projectId, Long beforeManagerUserId, Long afterManagerUserId, String reason) {
        if (Objects.equals(beforeManagerUserId, afterManagerUserId)) {
            return;
        }
        BizAuditLogDO auditLog = new BizAuditLogDO();
        auditLog.setBizType(ProjectObjectConstants.OBJECT_TYPE);
        auditLog.setBizId(projectId);
        auditLog.setActionType(ObjectActivityConstants.PROJECT_ACTION_CHANGE_MANAGER);
        auditLog.setFieldChanges(buildManagerFieldChanges(beforeManagerUserId, afterManagerUserId));
        auditLog.setReason(reason);
        auditLog.setOperatorUserId(SecurityFrameworkUtils.getLoginUserId());
        auditLog.setOperatorName(defaultText(SecurityFrameworkUtils.getLoginUserNickname()));
        bizAuditLogMapper.insert(auditLog);
    }

    private String buildMemberFieldChanges(UserObjectRoleDO before, UserObjectRoleDO after) {
        Map<String, Object> fieldChanges = new LinkedHashMap<>();
        appendFieldChange(fieldChanges, "userId", valueOf(before, UserObjectRoleDO::getUserId),
                valueOf(after, UserObjectRoleDO::getUserId));
        appendFieldChange(fieldChanges, "roleId", valueOf(before, UserObjectRoleDO::getRoleId),
                valueOf(after, UserObjectRoleDO::getRoleId));
        appendFieldChange(fieldChanges, "status", valueOf(before, UserObjectRoleDO::getStatus),
                valueOf(after, UserObjectRoleDO::getStatus));
        appendFieldChange(fieldChanges, "joinedTime", valueOf(before, UserObjectRoleDO::getJoinedTime),
                valueOf(after, UserObjectRoleDO::getJoinedTime));
        appendFieldChange(fieldChanges, "leftTime", valueOf(before, UserObjectRoleDO::getLeftTime),
                valueOf(after, UserObjectRoleDO::getLeftTime));
        appendFieldChange(fieldChanges, "remark", valueOf(before, UserObjectRoleDO::getRemark),
                valueOf(after, UserObjectRoleDO::getRemark));
        return fieldChanges.isEmpty() ? null : JsonUtils.toJsonString(fieldChanges);
    }

    private String buildManagerFieldChanges(Long beforeManagerUserId, Long afterManagerUserId) {
        Map<String, Object> fieldChanges = new LinkedHashMap<>();
        appendFieldChange(fieldChanges, "managerUserId", beforeManagerUserId, afterManagerUserId);
        return JsonUtils.toJsonString(fieldChanges);
    }

    private UserObjectRoleDO cloneMember(UserObjectRoleDO source) {
        UserObjectRoleDO clone = new UserObjectRoleDO();
        clone.setId(source.getId());
        clone.setUserId(source.getUserId());
        clone.setObjectType(source.getObjectType());
        clone.setObjectId(source.getObjectId());
        clone.setRoleId(source.getRoleId());
        clone.setStatus(source.getStatus());
        clone.setJoinedTime(source.getJoinedTime());
        clone.setLeftTime(source.getLeftTime());
        clone.setRemark(source.getRemark());
        return clone;
    }

    private <T> T valueOf(UserObjectRoleDO member, Function<UserObjectRoleDO, T> getter) {
        return member == null ? null : getter.apply(member);
    }

    private void appendFieldChange(Map<String, Object> fieldChanges, String fieldName, Object before, Object after) {
        if (Objects.equals(before, after)) {
            return;
        }
        Map<String, Object> value = new LinkedHashMap<>();
        value.put("before", before);
        value.put("after", after);
        fieldChanges.put(fieldName, value);
    }

    private String normalizeNullableText(String value) {
        if (!StringUtils.hasText(value)) {
            return null;
        }
        return value.trim();
    }

    private String defaultText(String value) {
        return StringUtils.hasText(value) ? value : "";
    }

}
