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

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.njcn.rdms.framework.common.pojo.PageResult;
import com.njcn.rdms.framework.common.util.json.JsonUtils;
import com.njcn.rdms.framework.common.util.object.PageUtils;
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.activity.ProductActivityPageReqVO;
import com.njcn.rdms.module.project.controller.admin.product.vo.activity.ProductActivityRespVO;
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.ProductStatusLogDO;
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.ProductStatusLogMapper;
import com.njcn.rdms.module.system.api.permission.ObjectPermissionApi;
import com.njcn.rdms.module.system.api.permission.dto.ObjectRoleRespDTO;
import jakarta.annotation.Resource;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
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;

@Service
public class ProductActivityQueryService {

    private static final String MEMBER_BIZ_TYPE = ObjectActivityConstants.MEMBER_BIZ_TYPE;
    private static final String ACTIVITY_ROLE_NAME_CACHE = "project_activity_role_name#10m";

    @Resource
    private ProductStatusLogMapper productStatusLogMapper;
    @Resource
    private BizAuditLogMapper bizAuditLogMapper;
    @Resource
    private UserObjectRoleMapper userObjectRoleMapper;
    @Resource
    private CacheManager cacheManager;
    @Resource
    private ObjectPermissionApi objectPermissionApi;

    public PageResult<ProductActivityRespVO> getProductActivities(Long productId, ProductActivityPageReqVO reqVO) {
        List<ActivityItem> items = new ArrayList<>();
        if (includeType(reqVO.getActivityType(), ObjectActivityConstants.ACTIVITY_TYPE_STATUS)) {
            productStatusLogMapper.selectListByProductId(productId, reqVO.getActionType(), reqVO.getOperateTime())
                    .forEach(log -> items.add(new ActivityItem(log.getId(), log.getCreateTime(), toStatusActivity(log))));
        }
        if (includeType(reqVO.getActivityType(), ObjectActivityConstants.ACTIVITY_TYPE_PRODUCT)) {
            bizAuditLogMapper.selectListByBiz(ProductObjectConstants.OBJECT_TYPE, productId,
                            reqVO.getActionType(), reqVO.getOperateTime())
                    .forEach(log -> items.add(new ActivityItem(log.getId(), log.getCreateTime(), toProductActivity(log))));
        }
        if (includeType(reqVO.getActivityType(), ObjectActivityConstants.ACTIVITY_TYPE_MEMBER)) {
            appendMemberActivities(productId, reqVO, items);
        }

        items.sort(Comparator.comparing(ActivityItem::operateTime, Comparator.nullsLast(LocalDateTime::compareTo))
                .thenComparing(ActivityItem::sourceId, Comparator.nullsLast(Long::compareTo))
                .reversed());

        List<ProductActivityRespVO> activities = items.stream()
                .map(ActivityItem::respVO)
                .toList();
        PageResult<ProductActivityRespVO> pageResult = buildPageResult(activities, reqVO);
        fillMemberRoleNames(pageResult.getList());
        return pageResult;
    }

    private void appendMemberActivities(Long productId, ProductActivityPageReqVO reqVO, List<ActivityItem> items) {
        List<BizAuditLogDO> memberLogs = bizAuditLogMapper
                .selectListByBizType(MEMBER_BIZ_TYPE, reqVO.getActionType(), reqVO.getOperateTime());
        if (memberLogs.isEmpty()) {
            return;
        }

        List<Long> memberIds = memberLogs.stream()
                .map(BizAuditLogDO::getBizId)
                .filter(Objects::nonNull)
                .distinct()
                .toList();
        if (memberIds.isEmpty()) {
            return;
        }

        Map<Long, UserObjectRoleDO> memberMap = userObjectRoleMapper
                .selectListByIdsAndObject(memberIds, ProductObjectConstants.OBJECT_TYPE, productId)
                .stream()
                .collect(Collectors.toMap(UserObjectRoleDO::getId, Function.identity()));
        memberLogs.stream()
                .filter(log -> memberMap.containsKey(log.getBizId()))
                .forEach(log -> items.add(new ActivityItem(log.getId(), log.getCreateTime(), toMemberActivity(log))));
    }

    private PageResult<ProductActivityRespVO> buildPageResult(List<ProductActivityRespVO> activities,
                                                              ProductActivityPageReqVO reqVO) {
        if (activities.isEmpty()) {
            return PageResult.empty();
        }
        int start = PageUtils.getStart(reqVO);
        if (start >= activities.size()) {
            return PageResult.empty((long) activities.size());
        }
        int end = Math.min(start + reqVO.getPageSize(), activities.size());
        return new PageResult<>(activities.subList(start, end), (long) activities.size());
    }

    private void fillMemberRoleNames(List<ProductActivityRespVO> activities) {
        if (activities == null || activities.isEmpty()) {
            return;
        }
        Set<Long> roleIds = new LinkedHashSet<>();
        for (ProductActivityRespVO activity : activities) {
            if (!Objects.equals(activity.getType(), ObjectActivityConstants.ACTIVITY_TYPE_MEMBER)) {
                continue;
            }
            Long beforeRoleId = getFieldChangeLong(activity.getDetails(), "roleId", "before");
            Long afterRoleId = getFieldChangeLong(activity.getDetails(), "roleId", "after");
            if (beforeRoleId != null) {
                roleIds.add(beforeRoleId);
            }
            if (afterRoleId != null) {
                roleIds.add(afterRoleId);
            }
        }
        if (roleIds.isEmpty()) {
            return;
        }
        Map<Long, String> roleNameMap = loadRoleNameMap(roleIds);
        for (ProductActivityRespVO activity : activities) {
            if (!Objects.equals(activity.getType(), ObjectActivityConstants.ACTIVITY_TYPE_MEMBER)) {
                continue;
            }
            activity.setDetails(appendRoleNames(activity.getDetails(), roleNameMap));
        }
    }

    private Map<Long, String> loadRoleNameMap(Set<Long> roleIds) {
        Map<Long, String> roleNameMap = new LinkedHashMap<>();
        if (roleIds == null || roleIds.isEmpty()) {
            return roleNameMap;
        }
        Cache cache = cacheManager == null ? null : cacheManager.getCache(ACTIVITY_ROLE_NAME_CACHE);
        Set<Long> missIds = new LinkedHashSet<>();
        for (Long roleId : roleIds) {
            String roleName = cache == null ? null : cache.get(roleId, String.class);
            if (roleName != null) {
                roleNameMap.put(roleId, roleName);
            } else {
                missIds.add(roleId);
            }
        }
        if (missIds.isEmpty()) {
            return roleNameMap;
        }
        Map<Long, ObjectRoleRespDTO> roleMap = objectPermissionApi == null ? Map.of()
                : objectPermissionApi.getObjectRoleMap(missIds, ObjectRoleConstants.ROLE_SCOPE_OBJECT,
                        ProductObjectConstants.OBJECT_TYPE);
        if (roleMap == null || roleMap.isEmpty()) {
            return roleNameMap;
        }
        for (Long roleId : missIds) {
            ObjectRoleRespDTO role = roleMap.get(roleId);
            if (role == null || !StringUtils.hasText(role.getName())) {
                continue;
            }
            roleNameMap.put(roleId, role.getName());
            if (cache != null) {
                cache.put(roleId, role.getName());
            }
        }
        return roleNameMap;
    }

    private Long getFieldChangeLong(String fieldChanges, String fieldName, String valueField) {
        JsonNode valueNode = getFieldChangeNode(fieldChanges, fieldName, valueField);
        if (valueNode == null || valueNode.isNull()) {
            return null;
        }
        if (valueNode.isNumber()) {
            return valueNode.longValue();
        }
        if (valueNode.isTextual() && StringUtils.hasText(valueNode.textValue())) {
            return Long.valueOf(valueNode.textValue().trim());
        }
        return null;
    }

    private JsonNode getFieldChangeNode(String fieldChanges, String fieldName, String valueField) {
        if (!StringUtils.hasText(fieldChanges) || !JsonUtils.isJsonObject(fieldChanges)) {
            return null;
        }
        JsonNode fieldNode = JsonUtils.parseTree(fieldChanges).path(fieldName);
        if (fieldNode.isMissingNode()) {
            return null;
        }
        JsonNode valueNode = fieldNode.path(valueField);
        return valueNode.isMissingNode() ? null : valueNode;
    }

    private String appendRoleNames(String details, Map<Long, String> roleNameMap) {
        if (!StringUtils.hasText(details) || !JsonUtils.isJsonObject(details)) {
            return details;
        }
        Long beforeRoleId = getFieldChangeLong(details, "roleId", "before");
        Long afterRoleId = getFieldChangeLong(details, "roleId", "after");
        if (beforeRoleId == null && afterRoleId == null) {
            return details;
        }
        JsonNode detailsNode = JsonUtils.parseTree(details);
        if (!(detailsNode instanceof ObjectNode)) {
            return details;
        }
        ObjectNode objectNode = ((ObjectNode) detailsNode).deepCopy();
        ObjectNode roleNameNode = objectNode.putObject("roleName");
        appendRoleName(roleNameNode, "before", beforeRoleId, roleNameMap);
        appendRoleName(roleNameNode, "after", afterRoleId, roleNameMap);
        return JsonUtils.toJsonString(objectNode);
    }

    private void appendRoleName(ObjectNode roleNameNode, String fieldName, Long roleId, Map<Long, String> roleNameMap) {
        if (roleId == null) {
            roleNameNode.putNull(fieldName);
            return;
        }
        String roleName = roleNameMap.get(roleId);
        if (StringUtils.hasText(roleName)) {
            roleNameNode.put(fieldName, roleName);
            return;
        }
        roleNameNode.putNull(fieldName);
    }

    private boolean includeType(String actual, String expected) {
        return !StringUtils.hasText(actual) || Objects.equals(actual.trim(), expected);
    }

    private ProductActivityRespVO toStatusActivity(ProductStatusLogDO log) {
        ProductActivityRespVO respVO = new ProductActivityRespVO();
        respVO.setType(ObjectActivityConstants.ACTIVITY_TYPE_STATUS);
        respVO.setActionType(log.getActionType());
        respVO.setActionName(ObjectActivityConstants.resolveActionName(log.getActionType()));
        respVO.setFromStatus(log.getFromStatus());
        respVO.setToStatus(log.getToStatus());
        respVO.setReason(log.getReason());
        respVO.setOperatorUserId(log.getOperatorUserId());
        respVO.setOperatorName(log.getOperatorName());
        respVO.setOperateTime(log.getCreateTime());
        respVO.setSummary(buildSummary(log.getOperatorName(), respVO.getActionName(), log.getReason()));
        respVO.setDetails(JsonUtils.toJsonString(buildStatusDetails(log)));
        return respVO;
    }

    private Map<String, Object> buildStatusDetails(ProductStatusLogDO log) {
        Map<String, Object> details = new LinkedHashMap<>();
        details.put("productCodeSnapshot", log.getProductCodeSnapshot());
        details.put("productNameSnapshot", log.getProductNameSnapshot());
        return details;
    }

    private ProductActivityRespVO toProductActivity(BizAuditLogDO log) {
        ProductActivityRespVO respVO = new ProductActivityRespVO();
        respVO.setType(ObjectActivityConstants.ACTIVITY_TYPE_PRODUCT);
        respVO.setActionType(log.getActionType());
        respVO.setActionName(ObjectActivityConstants.resolveActionName(log.getActionType()));
        respVO.setFromStatus(log.getFromStatus());
        respVO.setToStatus(log.getToStatus());
        respVO.setReason(log.getReason());
        respVO.setOperatorUserId(log.getOperatorUserId());
        respVO.setOperatorName(log.getOperatorName());
        respVO.setOperateTime(log.getCreateTime());
        respVO.setSummary(buildSummary(log.getOperatorName(), respVO.getActionName(), log.getReason()));
        respVO.setDetails(log.getFieldChanges());
        return respVO;
    }

    private ProductActivityRespVO toMemberActivity(BizAuditLogDO log) {
        ProductActivityRespVO respVO = toProductActivity(log);
        respVO.setType(ObjectActivityConstants.ACTIVITY_TYPE_MEMBER);
        return respVO;
    }

    private String buildSummary(String operatorName, String actionName, String reason) {
        String actualOperatorName = StringUtils.hasText(operatorName) ? operatorName : "系统";
        if (StringUtils.hasText(reason)) {
            return String.format("%s执行了【%s】：%s", actualOperatorName, actionName, reason);
        }
        return String.format("%s执行了【%s】", actualOperatorName, actionName);
    }

    private record ActivityItem(Long sourceId, LocalDateTime operateTime, ProductActivityRespVO respVO) {
    }

}
