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

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.ProductObjectConstants;
import com.njcn.rdms.module.project.controller.admin.product.vo.member.ProductMemberInactiveReqVO;
import com.njcn.rdms.module.project.controller.admin.product.vo.member.ProductMemberRespVO;
import com.njcn.rdms.module.project.controller.admin.product.vo.member.ProductMemberSaveReqVO;
import com.njcn.rdms.module.project.controller.admin.product.vo.member.ProductMemberUpdateReqVO;
import com.njcn.rdms.module.project.framework.security.annotation.CheckObjectPermission;
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.product.ProductDO;
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.product.ProductMapper;
import com.njcn.rdms.module.project.dal.mysql.status.ObjectStatusModelMapper;
import com.njcn.rdms.module.project.enums.ErrorCodeConstants;
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 ProductMemberServiceImpl implements ProductMemberService {

    @Resource
    private ProductMapper productMapper;
    @Resource
    private UserObjectRoleMapper userObjectRoleMapper;
    @Resource
    private ObjectPermissionApi objectPermissionApi;
    @Resource
    private BizAuditLogMapper bizAuditLogMapper;
    @Resource
    private ObjectStatusModelMapper objectStatusModelMapper;
    @Resource
    private AdminUserApi adminUserApi;

    @Override
    public List<ProductMemberRespVO> getProductMemberList(Long productId) {
        ProductDO product = validateProductExists(productId);
        List<UserObjectRoleDO> members = userObjectRoleMapper.selectListByObject(ProductObjectConstants.OBJECT_TYPE, productId);
        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 -> {
            ProductMemberRespVO respVO = new ProductMemberRespVO();
            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(), product.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 = ProductObjectConstants.OBJECT_TYPE, objectId = "#productId",
            permission = ProductObjectConstants.PERMISSION_UPDATE)
    public Long createProductMember(Long productId, ProductMemberSaveReqVO reqVO) {
        ProductDO product = validateProductEditable(productId);
        ObjectRoleRespDTO targetRole = validateProductRole(reqVO.getRoleId());
        UserObjectRoleDO existingMember = userObjectRoleMapper
                .selectByObjectAndUserId(ProductObjectConstants.OBJECT_TYPE, productId, reqVO.getUserId());
        if (existingMember != null && Objects.equals(existingMember.getStatus(), ObjectRoleConstants.MEMBER_STATUS_ACTIVE)) {
            throw exception(ErrorCodeConstants.PRODUCT_MEMBER_ALREADY_EXISTS);
        }

        UserObjectRoleDO member;
        UserObjectRoleDO before = null;
        LocalDateTime now = LocalDateTime.now();
        if (existingMember == null) {
            member = new UserObjectRoleDO();
            member.setUserId(reqVO.getUserId());
            member.setObjectType(ProductObjectConstants.OBJECT_TYPE);
            member.setObjectId(productId);
            member.setRoleId(targetRole.getId());
            member.setStatus(ObjectRoleConstants.MEMBER_STATUS_ACTIVE);
            member.setJoinedTime(now);
            member.setLeftTime(null);
            member.setRemark(normalizeNullableText(reqVO.getRemark()));
            userObjectRoleMapper.insert(member);
        } else {
            before = cloneMember(existingMember);
            member = existingMember;
            member.setRoleId(targetRole.getId());
            member.setStatus(ObjectRoleConstants.MEMBER_STATUS_ACTIVE);
            member.setJoinedTime(now);
            member.setLeftTime(null);
            member.setRemark(normalizeNullableText(reqVO.getRemark()));
            userObjectRoleMapper.updateById(member);
        }

        writeMemberAuditLog(member, ObjectActivityConstants.MEMBER_ACTION_ADD, before, member, null);
        if (isManagerRole(targetRole)) {
            transferManager(product, member, reqVO.getPreviousManagerUserId(), reqVO.getPreviousManagerRoleId(), null);
        }
        return member.getId();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @CheckObjectPermission(objectType = ProductObjectConstants.OBJECT_TYPE, objectId = "#productId",
            permission = ProductObjectConstants.PERMISSION_UPDATE)
    public void updateProductMember(Long productId, Long memberId, ProductMemberUpdateReqVO reqVO) {
        ProductDO product = validateProductEditable(productId);
        UserObjectRoleDO member = validateMemberExists(productId, memberId);
        if (!Objects.equals(member.getStatus(), ObjectRoleConstants.MEMBER_STATUS_ACTIVE)) {
            throw exception(ErrorCodeConstants.PRODUCT_MEMBER_NOT_ACTIVE);
        }

        ObjectRoleRespDTO targetRole = validateProductRole(reqVO.getRoleId());
        UserObjectRoleDO before = cloneMember(member);
        member.setRemark(normalizeNullableText(reqVO.getRemark()));

        if (isManagerRole(targetRole)) {
            member.setRoleId(targetRole.getId());
            userObjectRoleMapper.updateById(member);
            transferManager(product, member, reqVO.getPreviousManagerUserId(),
                    reqVO.getPreviousManagerRoleId(), normalizeNullableText(reqVO.getReason()));
        } else {
            if (Objects.equals(member.getUserId(), product.getManagerUserId())) {
                throw exception(ErrorCodeConstants.PRODUCT_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 = ProductObjectConstants.OBJECT_TYPE, objectId = "#productId",
            permission = ProductObjectConstants.PERMISSION_UPDATE)
    public void inactiveProductMember(Long productId, Long memberId, ProductMemberInactiveReqVO reqVO) {
        ProductDO product = validateProductEditable(productId);
        UserObjectRoleDO member = validateMemberExists(productId, memberId);
        if (!Objects.equals(member.getStatus(), ObjectRoleConstants.MEMBER_STATUS_ACTIVE)) {
            throw exception(ErrorCodeConstants.PRODUCT_MEMBER_NOT_ACTIVE);
        }
        if (Objects.equals(member.getUserId(), product.getManagerUserId())) {
            throw exception(ErrorCodeConstants.PRODUCT_MANAGER_MEMBER_NOT_ALLOW_REMOVE);
        }

        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 ProductDO validateProductExists(Long productId) {
        if (productId == null) {
            throw exception(ErrorCodeConstants.PRODUCT_NOT_EXISTS);
        }
        ProductDO product = productMapper.selectById(productId);
        if (product == null) {
            throw exception(ErrorCodeConstants.PRODUCT_NOT_EXISTS);
        }
        return product;
    }

    private ProductDO validateProductEditable(Long productId) {
        ProductDO product = validateProductExists(productId);
        ObjectStatusModelDO statusModel = objectStatusModelMapper
                .selectByObjectTypeAndStatusCodeEnabled(ProductObjectConstants.OBJECT_TYPE, product.getStatusCode());
        if (statusModel == null) {
            throw exception(ErrorCodeConstants.PRODUCT_STATUS_MODEL_NOT_EXISTS_OR_DISABLED);
        }
        if (!Boolean.TRUE.equals(statusModel.getAllowEdit())) {
            throw exception(ErrorCodeConstants.PRODUCT_STATUS_NOT_ALLOW_EDIT);
        }
        return product;
    }

    private UserObjectRoleDO validateMemberExists(Long productId, Long memberId) {
        UserObjectRoleDO member = userObjectRoleMapper.selectByIdAndObject(memberId, ProductObjectConstants.OBJECT_TYPE, productId);
        if (member == null) {
            throw exception(ErrorCodeConstants.PRODUCT_MEMBER_NOT_EXISTS);
        }
        return member;
    }

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

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

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

        product.setManagerUserId(targetManagerUserId);
        productMapper.updateById(product);
        writeManagerChangeAuditLog(product.getId(), currentManagerUserId, targetManagerUserId, reason);
    }

    private ObjectRoleRespDTO validatePreviousManagerTransfer(Long currentManagerUserId,
                                                              Long previousManagerUserId,
                                                              Long previousManagerRoleId) {
        if (currentManagerUserId == null
                || previousManagerUserId == null
                || previousManagerRoleId == null) {
            throw exception(ErrorCodeConstants.PRODUCT_MANAGER_TRANSFER_INFO_REQUIRED);
        }
        if (!Objects.equals(currentManagerUserId, previousManagerUserId)) {
            throw exception(ErrorCodeConstants.PRODUCT_MANAGER_TRANSFER_SOURCE_INVALID);
        }
        ObjectRoleRespDTO previousManagerRole = validateProductRole(previousManagerRoleId);
        if (isManagerRole(previousManagerRole)) {
            throw exception(ErrorCodeConstants.PRODUCT_MANAGER_TRANSFER_ROLE_INVALID);
        }
        return previousManagerRole;
    }

    private void transferPreviousManager(Long productId, Long previousManagerUserId, Long previousManagerRoleId, String reason) {
        UserObjectRoleDO existingMember = userObjectRoleMapper
                .selectByObjectAndUserId(ProductObjectConstants.OBJECT_TYPE, productId, 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(ProductObjectConstants.OBJECT_TYPE);
            member.setObjectId(productId);
            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(ProductObjectConstants.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, ProductObjectConstants.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 productId, Long beforeManagerUserId, Long afterManagerUserId, String reason) {
        if (Objects.equals(beforeManagerUserId, afterManagerUserId)) {
            return;
        }
        BizAuditLogDO auditLog = new BizAuditLogDO();
        auditLog.setBizType(ProductObjectConstants.OBJECT_TYPE);
        auditLog.setBizId(productId);
        auditLog.setActionType(ObjectActivityConstants.PRODUCT_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 : "";
    }

}
