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

import com.njcn.rdms.framework.common.pojo.CommonResult;
import com.njcn.rdms.framework.common.pojo.PageResult;
import com.njcn.rdms.framework.common.util.object.BeanUtils;
import com.njcn.rdms.framework.security.core.util.SecurityFrameworkUtils;
import com.njcn.rdms.module.project.constant.ObjectActivityConstants;
import com.njcn.rdms.module.project.constant.ProjectExecutionConstants;
import com.njcn.rdms.module.project.constant.ProjectObjectConstants;
import com.njcn.rdms.module.project.constant.ProjectTaskConstants;
import com.njcn.rdms.module.project.controller.admin.project.task.vo.assignee.TaskAssigneeInactiveReqVO;
import com.njcn.rdms.module.project.controller.admin.project.task.vo.assignee.TaskAssigneeLogPageReqVO;
import com.njcn.rdms.module.project.controller.admin.project.task.vo.assignee.TaskAssigneeLogRespVO;
import com.njcn.rdms.module.project.controller.admin.project.task.vo.assignee.TaskAssigneeRespVO;
import com.njcn.rdms.module.project.controller.admin.project.task.vo.assignee.TaskAssigneeSaveReqVO;
import com.njcn.rdms.module.project.dal.dataobject.project.ProjectDO;
import com.njcn.rdms.module.project.dal.dataobject.project.execution.ProjectExecutionDO;
import com.njcn.rdms.module.project.dal.dataobject.project.task.ProjectTaskDO;
import com.njcn.rdms.module.project.dal.dataobject.project.task.TaskAssigneeDO;
import com.njcn.rdms.module.project.dal.dataobject.project.task.TaskAssigneeLogDO;
import com.njcn.rdms.module.project.dal.dataobject.status.ObjectStatusModelDO;
import com.njcn.rdms.module.project.dal.mysql.project.ProjectMapper;
import com.njcn.rdms.module.project.dal.mysql.project.execution.ExecutionAssigneeMapper;
import com.njcn.rdms.module.project.dal.mysql.project.execution.ProjectExecutionMapper;
import com.njcn.rdms.module.project.dal.mysql.project.task.ProjectTaskMapper;
import com.njcn.rdms.module.project.dal.mysql.project.task.TaskAssigneeLogMapper;
import com.njcn.rdms.module.project.dal.mysql.project.task.TaskAssigneeMapper;
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.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.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

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

@Service
public class TaskAssigneeServiceImpl implements TaskAssigneeService {

    @Resource
    private ProjectMapper projectMapper;
    @Resource
    private ProjectExecutionMapper projectExecutionMapper;
    @Resource
    private ProjectTaskMapper projectTaskMapper;
    @Resource
    private ExecutionAssigneeMapper executionAssigneeMapper;
    @Resource
    private TaskAssigneeMapper taskAssigneeMapper;
    @Resource
    private TaskAssigneeLogMapper taskAssigneeLogMapper;
    @Resource
    private ObjectStatusModelMapper objectStatusModelMapper;
    @Resource
    private AdminUserApi adminUserApi;

    @Override
    @CheckObjectPermission(objectType = ProjectObjectConstants.OBJECT_TYPE, objectId = "#projectId", accessible = true)
    public List<TaskAssigneeRespVO> getAssigneeList(Long projectId, Long executionId, Long taskId) {
        validateExecutionAndTaskExists(projectId, executionId, taskId);
        List<TaskAssigneeDO> activeList = taskAssigneeMapper.selectActiveListByTaskId(taskId);
        if (activeList.isEmpty()) {
            return Collections.emptyList();
        }
        Map<Long, String> nicknameMap = loadUserNicknameMap(activeList.stream()
                .map(TaskAssigneeDO::getUserId).collect(Collectors.toCollection(LinkedHashSet::new)));
        return activeList.stream().map(active -> {
            TaskAssigneeRespVO respVO = BeanUtils.toBean(active, TaskAssigneeRespVO.class);
            respVO.setUserNickname(nicknameMap.get(active.getUserId()));
            return respVO;
        }).collect(Collectors.toList());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @CheckObjectPermission(objectType = ProjectObjectConstants.OBJECT_TYPE, objectId = "#projectId",
            permission = ProjectTaskConstants.PERMISSION_ASSIGNEE)
    public Long createAssignee(Long projectId, Long executionId, Long taskId, TaskAssigneeSaveReqVO reqVO) {
        ProjectTaskDO task = validateEditableContext(projectId, executionId, taskId);
        Long userId = reqVO.getUserId();
        validateAssigneeCandidate(executionId, taskId, task.getOwnerId(), userId);
        return doInsertAssignee(taskId, userId);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @CheckObjectPermission(objectType = ProjectObjectConstants.OBJECT_TYPE, objectId = "#projectId",
            permission = ProjectTaskConstants.PERMISSION_ASSIGNEE)
    public void inactiveAssignee(Long projectId, Long executionId, Long taskId, Long assigneeId,
                                 TaskAssigneeInactiveReqVO reqVO) {
        validateEditableContext(projectId, executionId, taskId);
        TaskAssigneeDO assignee = taskAssigneeMapper.selectByIdAndTaskId(assigneeId, taskId);
        if (assignee == null) {
            throw exception(ErrorCodeConstants.PROJECT_TASK_ASSIGNEE_NOT_EXISTS);
        }
        if (assignee.getRemovedAt() != null) {
            throw exception(ErrorCodeConstants.PROJECT_TASK_ASSIGNEE_NOT_ACTIVE);
        }
        String reason = normalizeNullableText(reqVO.getReason());
        if (!StringUtils.hasText(reason)) {
            throw exception(ErrorCodeConstants.PROJECT_TASK_ASSIGNEE_REASON_REQUIRED);
        }
        LocalDateTime now = LocalDateTime.now();
        assignee.setRemovedAt(now);
        taskAssigneeMapper.updateById(assignee);
        writeAssigneeLog(assignee.getTaskId(), assignee.getUserId(),
                ObjectActivityConstants.TASK_ASSIGNEE_ACTION_INACTIVE, reason, now);
    }

    @Override
    @CheckObjectPermission(objectType = ProjectObjectConstants.OBJECT_TYPE, objectId = "#projectId", accessible = true)
    public PageResult<TaskAssigneeLogRespVO> getAssigneeLogPage(Long projectId, Long executionId, Long taskId,
                                                                 TaskAssigneeLogPageReqVO reqVO) {
        validateExecutionAndTaskExists(projectId, executionId, taskId);
        PageResult<TaskAssigneeLogDO> page = taskAssigneeLogMapper.selectPageByTaskId(taskId, reqVO);
        return BeanUtils.toBean(page, TaskAssigneeLogRespVO.class);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void initializeAssignees(Long taskId, Long ownerId, Long executionId, List<Long> assigneeUserIds) {
        if (assigneeUserIds == null || assigneeUserIds.isEmpty()) {
            return;
        }
        // 保留首次出现顺序的去重
        Set<Long> uniqueUserIds = new LinkedHashSet<>(assigneeUserIds);
        for (Long userId : uniqueUserIds) {
            validateAssigneeCandidate(executionId, taskId, ownerId, userId);
            doInsertAssignee(taskId, userId);
        }
    }

    @Override
    public Map<Long, List<TaskAssigneeDO>> loadActiveAssigneesGroupedByTaskId(Collection<Long> taskIds) {
        if (taskIds == null || taskIds.isEmpty()) {
            return Collections.emptyMap();
        }
        List<TaskAssigneeDO> all = taskAssigneeMapper.selectActiveListByTaskIds(taskIds);
        return all.stream().collect(Collectors.groupingBy(TaskAssigneeDO::getTaskId));
    }

    // -------------------- 内部辅助 --------------------

    /**
     * 校验项目、执行、任务都允许编辑，并返回任务实体。
     * 复用 ProjectTaskServiceImpl 的同口径校验，避免跨 Service 互相依赖。
     */
    private ProjectTaskDO validateEditableContext(Long projectId, Long executionId, Long taskId) {
        ProjectDO project = validateProjectExists(projectId);
        validateAllowEdit(ProjectObjectConstants.OBJECT_TYPE, project.getStatusCode());
        ProjectExecutionDO execution = validateExecutionExists(projectId, executionId);
        validateAllowEdit(ProjectExecutionConstants.OBJECT_TYPE, execution.getStatusCode());
        ProjectTaskDO task = validateTaskExists(projectId, executionId, taskId);
        validateAllowEdit(ProjectTaskConstants.OBJECT_TYPE, task.getStatusCode());
        return task;
    }

    private ProjectTaskDO validateExecutionAndTaskExists(Long projectId, Long executionId, Long taskId) {
        validateExecutionExists(projectId, executionId);
        return validateTaskExists(projectId, executionId, taskId);
    }

    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 ProjectExecutionDO validateExecutionExists(Long projectId, Long executionId) {
        if (executionId == null) {
            throw exception(ErrorCodeConstants.PROJECT_EXECUTION_NOT_EXISTS);
        }
        ProjectExecutionDO execution = projectExecutionMapper.selectByProjectIdAndId(projectId, executionId);
        if (execution == null) {
            throw exception(ErrorCodeConstants.PROJECT_EXECUTION_NOT_EXISTS);
        }
        return execution;
    }

    private ProjectTaskDO validateTaskExists(Long projectId, Long executionId, Long taskId) {
        if (taskId == null) {
            throw exception(ErrorCodeConstants.PROJECT_TASK_NOT_EXISTS);
        }
        ProjectTaskDO task = projectTaskMapper.selectByProjectIdAndExecutionIdAndId(projectId, executionId, taskId);
        if (task == null) {
            throw exception(ErrorCodeConstants.PROJECT_TASK_NOT_EXISTS);
        }
        return task;
    }

    private void validateAllowEdit(String objectType, String statusCode) {
        ObjectStatusModelDO status = objectStatusModelMapper
                .selectByObjectTypeAndStatusCodeEnabled(objectType, statusCode);
        if (status == null || !Boolean.TRUE.equals(status.getAllowEdit())) {
            throw exception(ErrorCodeConstants.PROJECT_TASK_NOT_ALLOW_EDIT);
        }
    }

    /**
     * 校验单个候选协办人：与 owner 不能重复、必须是当前有效执行成员、不能已有活跃段。
     */
    private void validateAssigneeCandidate(Long executionId, Long taskId, Long ownerId, Long userId) {
        if (userId == null) {
            throw exception(ErrorCodeConstants.PROJECT_TASK_ASSIGNEE_INVALID_MEMBER);
        }
        if (Objects.equals(userId, ownerId)) {
            throw exception(ErrorCodeConstants.PROJECT_TASK_ASSIGNEE_OWNER_CONFLICT);
        }
        if (executionAssigneeMapper.selectActiveByExecutionIdAndUserId(executionId, userId) == null) {
            throw exception(ErrorCodeConstants.PROJECT_TASK_ASSIGNEE_INVALID_MEMBER);
        }
        if (taskAssigneeMapper.selectActiveByTaskIdAndUserId(taskId, userId) != null) {
            throw exception(ErrorCodeConstants.PROJECT_TASK_ASSIGNEE_ALREADY_ACTIVE);
        }
    }

    /**
     * 执行 INSERT 活跃 + INSERT 日志（join），返回新建协办关系编号。
     */
    private Long doInsertAssignee(Long taskId, Long userId) {
        LocalDateTime now = LocalDateTime.now();
        TaskAssigneeDO assignee = new TaskAssigneeDO();
        assignee.setTaskId(taskId);
        assignee.setUserId(userId);
        assignee.setJoinedAt(now);
        assignee.setRemovedAt(null);
        taskAssigneeMapper.insert(assignee);
        writeAssigneeLog(taskId, userId, ObjectActivityConstants.TASK_ASSIGNEE_ACTION_JOIN, null, now);
        return assignee.getId();
    }

    private void writeAssigneeLog(Long taskId, Long userId, String actionType, String reason, LocalDateTime when) {
        TaskAssigneeLogDO log = new TaskAssigneeLogDO();
        log.setTaskId(taskId);
        log.setUserId(userId);
        log.setUserNicknameSnapshot(loadUserNickname(userId));
        log.setActionType(actionType);
        log.setReason(reason);
        log.setOperatorUserId(SecurityFrameworkUtils.getLoginUserId());
        log.setOperatorNicknameSnapshot(SecurityFrameworkUtils.getLoginUserNickname());
        log.setActionTime(when);
        taskAssigneeLogMapper.insert(log);
    }

    private String loadUserNickname(Long userId) {
        if (userId == null) {
            return null;
        }
        CommonResult<AdminUserRespDTO> result = adminUserApi.getUser(userId);
        AdminUserRespDTO user = result == null ? null : result.getCheckedData();
        return user == null ? null : user.getNickname();
    }

    private Map<Long, String> loadUserNicknameMap(Collection<Long> userIds) {
        if (userIds == null || userIds.isEmpty()) {
            return Collections.emptyMap();
        }
        Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds);
        if (userMap == null || userMap.isEmpty()) {
            return Collections.emptyMap();
        }
        Map<Long, String> nicknameMap = new HashMap<>(userMap.size());
        userMap.forEach((id, user) -> nicknameMap.put(id, user == null ? null : user.getNickname()));
        return nicknameMap;
    }

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