package tech.easyflow.skill.publish;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.stereotype.Component;
import com.mybatisflex.core.query.QueryWrapper;
import tech.easyflow.ai.enums.PublishStatus;
import tech.easyflow.ai.publish.AbstractAiResourceLifecycleHandler;
import tech.easyflow.approval.entity.ApprovalInstance;
import tech.easyflow.approval.entity.vo.ApprovalSubmitRequest;
import tech.easyflow.approval.enums.ApprovalActionType;
import tech.easyflow.approval.enums.ApprovalInstanceStatus;
import tech.easyflow.approval.enums.ApprovalResourceType;
import tech.easyflow.approval.service.ApprovalInstanceService;
import tech.easyflow.common.web.exceptions.BusinessException;
import tech.easyflow.common.entity.LoginAccount;
import tech.easyflow.common.satoken.util.SaTokenUtil;
import tech.easyflow.skill.entity.Skill;
import tech.easyflow.skill.mapper.SkillMapper;
import tech.easyflow.skill.service.SkillService;
import tech.easyflow.system.enums.CategoryResourceType;
import tech.easyflow.system.enums.ResourceAction;
import tech.easyflow.system.service.ResourceAccessService;

import java.math.BigInteger;
import java.util.Date;
import java.util.Map;

/**
 * Skill 审批资源处理器。
 */
@Component
public class SkillApprovalSubjectHandler extends AbstractAiResourceLifecycleHandler<Skill> {

    private final SkillService skillService;
    private final SkillMapper skillMapper;
    private final ResourceAccessService resourceAccessService;
    private final ApprovalInstanceService approvalInstanceService;

    /**
     * 创建 Skill 审批资源处理器。
     *
     * @param approvalInstanceService 审批实例服务
     * @param objectMapper JSON 映射器
     * @param skillService Skill 服务
     * @param skillMapper Skill Mapper
     * @param resourceAccessService 资源访问服务
     */
    public SkillApprovalSubjectHandler(ApprovalInstanceService approvalInstanceService,
                                       ObjectMapper objectMapper,
                                       SkillService skillService,
                                       SkillMapper skillMapper,
                                       ResourceAccessService resourceAccessService) {
        super(approvalInstanceService, objectMapper);
        this.skillService = skillService;
        this.skillMapper = skillMapper;
        this.resourceAccessService = resourceAccessService;
        this.approvalInstanceService = approvalInstanceService;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String resourceType() {
        return ApprovalResourceType.SKILL.getCode();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void assertPublishedAccess(Object identifier, String denyMessage) {
        Skill skill = findCurrentTenantSkill(new BigInteger(String.valueOf(identifier)), false);
        if (skill == null || !PublishStatus.from(skill.getPublishStatus()).isExternallyVisible()
                || skill.getPublishedSnapshotJson() == null || skill.getPublishedSnapshotJson().isEmpty()) {
            throw new BusinessException(denyMessage);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void retainSubmittedSnapshot(String actionType, Map<String, Object> resourceSnapshot) {
        if (ApprovalActionType.PUBLISH.getCode().equals(actionType)) {
            skillService.retainSnapshotContents(resourceSnapshot);
        }
    }

    @Override
    protected Skill requireResource(BigInteger resourceId) {
        // 生命周期提交与审批决策均在事务中执行，行锁串行化同一 Skill 的状态迁移。
        Skill skill = findCurrentTenantSkill(resourceId, true);
        if (skill == null) {
            throw new BusinessException(404, 404, "Skill 不存在");
        }
        return skill;
    }

    @Override
    protected void assertManagePermission(Skill resource) {
        resourceAccessService.assertAccess(CategoryResourceType.SKILL, resource, ResourceAction.MANAGE, "无权限管理该 Skill");
    }

    @Override
    protected BigInteger getCategoryId(Skill resource) {
        return resource.getCategoryId();
    }

    @Override
    protected BigInteger getDeptId(Skill resource) {
        return resource.getDeptId();
    }

    @Override
    protected String getTitle(Skill resource) {
        return resource.getDisplayName() == null || resource.getDisplayName().isBlank()
                ? resource.getName()
                : resource.getDisplayName();
    }

    @Override
    protected PublishStatus getCurrentStatus(Skill resource) {
        return PublishStatus.from(resource.getPublishStatus());
    }

    @Override
    protected Map<String, Object> getPublishedSnapshot(Skill resource) {
        Map<String, Object> content = resource.getPublishedSnapshotJson();
        if (content == null || content.isEmpty()) {
            return content;
        }
        Map<String, Object> toolSnapshot = resource.getPublishedToolBindingsJson();
        if (toolSnapshot == null || toolSnapshot.isEmpty()) {
            return content;
        }
        Map<String, Object> combined = new java.util.LinkedHashMap<>(content);
        Object contentHash = combined.remove("snapshotHash");
        combined.put("contentSnapshotHash", contentHash);
        combined.put("platformToolBindings", toolSnapshot);
        combined.put("toolBindingsHash", toolSnapshot.get("snapshotHash"));
        combined.put("snapshotHash", resource.getSnapshotHash());
        return combined;
    }

    @Override
    protected Map<String, Object> buildResourceSnapshot(Skill resource) {
        return skillService.buildApprovalSnapshot(resource);
    }

    /**
     * 删除审批只记录最小治理信息，避免把指令和资源内容写入审批快照。
     *
     * @param resource Skill
     * @return 删除审批治理快照
     */
    @Override
    protected Map<String, Object> buildDeleteResourceSnapshot(Skill resource) {
        return skillService.buildGovernanceSnapshot(resource);
    }

    /**
     * 优先使用稳定快照 hash 判断内容是否变化，兼容旧快照中的时间字段。
     *
     * @param currentSnapshot 当前草稿快照
     * @param publishedSnapshot 已发布快照
     * @return 内容一致时为 true
     */
    @Override
    protected boolean isSameSnapshot(Map<String, Object> currentSnapshot, Map<String, Object> publishedSnapshot) {
        Object currentHash = currentSnapshot == null ? null : currentSnapshot.get("snapshotHash");
        Object publishedHash = publishedSnapshot == null ? null : publishedSnapshot.get("snapshotHash");
        if (currentHash != null && publishedHash != null) {
            return currentHash.equals(publishedHash);
        }
        return super.isSameSnapshot(currentSnapshot, publishedSnapshot);
    }

    @Override
    protected void persistResourceState(BigInteger resourceId, PublishStatus publishStatus, BigInteger currentApprovalInstanceId) {
        Skill existing = requireResource(resourceId);
        if (skillMapper.updateApprovalState(resourceId, existing.getTenantId(), publishStatus.getCode(),
                currentApprovalInstanceId) != 1) {
            throw new BusinessException(500, 500, "更新 Skill 审批状态失败，请稍后重试");
        }
    }

    @Override
    protected void publishResource(BigInteger resourceId, Map<String, Object> resourceSnapshot, BigInteger operatorId) {
        Skill existing = requireResource(resourceId);
        Map<String, Object> contentSnapshot = skillService.extractContentSnapshot(resourceSnapshot);
        Map<String, Object> toolSnapshot = skillService.extractToolBindingsSnapshot(resourceSnapshot);
        if (skillMapper.publish(resourceId, existing.getTenantId(), contentSnapshot, toolSnapshot,
                new Date(), operatorId,
                stringValue(resourceSnapshot.get("snapshotHash"))) != 1) {
            throw new BusinessException(500, 500, "发布 Skill 失败，请稍后重试");
        }
        skillService.releaseSnapshotContents(existing.getPublishedSnapshotJson());
    }

    /**
     * 按审批实例和快照哈希执行 Skill 生命周期回调，拒绝旧申请覆盖新状态。
     *
     * @param actionType 动作类型
     * @param resourceId Skill ID
     * @param resourceSnapshot 审批冻结快照
     * @param operatorId 操作人 ID
     * @param approvalInstanceId 审批实例 ID
     */
    @Override
    public void applyApprovedAction(String actionType,
                                    BigInteger resourceId,
                                    Map<String, Object> resourceSnapshot,
                                    BigInteger operatorId,
                                    BigInteger approvalInstanceId) {
        ApprovalActionType action = ApprovalActionType.from(actionType);
        requireMatchingInstance(approvalInstanceId, resourceId, action, ApprovalInstanceStatus.APPROVED);
        Skill existing = findCurrentTenantSkill(resourceId, true);
        if (existing == null) {
            if (action == ApprovalActionType.DELETE) {
                if (approvalInstanceService.isLatestResourceInstance(
                        approvalInstanceId, resourceType(), resourceId)) {
                    return;
                }
                throw new BusinessException(409, 4092, "审批申请已过期，不能删除当前 Skill");
            }
            throw new BusinessException(404, 404, "Skill 不存在");
        }
        if (!approvalInstanceId.equals(existing.getCurrentApprovalInstanceId())) {
            throw new BusinessException(409, 4092, "审批申请已过期，不能更新当前 Skill");
        }
        if (isAppliedTerminal(existing, action, resourceSnapshot)) {
            return;
        }
        if (action == ApprovalActionType.PUBLISH) {
            skillService.assertSnapshotHash(resourceSnapshot);
            Map<String, Object> contentSnapshot = skillService.extractContentSnapshot(resourceSnapshot);
            Map<String, Object> toolSnapshot = skillService.extractToolBindingsSnapshot(resourceSnapshot);
            if (skillMapper.publishApproved(resourceId, existing.getTenantId(), approvalInstanceId,
                    contentSnapshot, toolSnapshot, new Date(), operatorId,
                    stringValue(resourceSnapshot.get("snapshotHash"))) != 1) {
                throw new BusinessException(409, 4092, "Skill 发布状态已变化，请刷新后重试");
            }
            skillService.releaseSnapshotContents(existing.getPublishedSnapshotJson());
            return;
        }
        if (action == ApprovalActionType.OFFLINE) {
            skillService.assertNoActiveReferences(resourceId);
            if (skillMapper.markOfflineApproved(resourceId, existing.getTenantId(), approvalInstanceId) != 1) {
                throw new BusinessException(409, 4092, "Skill 下线状态已变化，请刷新后重试");
            }
            return;
        }
        removeResource(resourceId);
    }

    @Override
    protected void markResourceOffline(BigInteger resourceId) {
        skillService.assertNoActiveReferences(resourceId);
        Skill existing = requireResource(resourceId);
        if (skillMapper.markOffline(resourceId, existing.getTenantId()) != 1) {
            throw new BusinessException(500, 500, "下线 Skill 失败，请稍后重试");
        }
    }

    @Override
    protected void removeResource(BigInteger resourceId) {
        skillService.removeLifecycleAggregate(resourceId);
    }

    @Override
    protected String resourceLabel() {
        return "Skill";
    }

    /**
     * 审批驳回或撤回时释放发布候选快照持有的二进制内容。
     *
     * @param resourceId Skill ID
     * @param previousStatus 审批前发布状态
     */
    @Override
    public void restoreState(BigInteger resourceId, PublishStatus previousStatus) {
        Skill skill = requireResource(resourceId);
        BigInteger instanceId = skill.getCurrentApprovalInstanceId();
        if (instanceId != null) {
            ApprovalInstance instance = approvalInstanceService.getById(instanceId);
            if (instance == null) {
                throw new BusinessException(500, 500, "Skill 审批状态异常，无法安全恢复内容引用");
            }
            if (ApprovalActionType.PUBLISH.getCode().equals(instance.getActionType())) {
                skillService.releaseSnapshotContents(readResourceSnapshot(instance.getSnapshotJson()));
            }
        }
        super.restoreState(resourceId, previousStatus);
    }

    /**
     * 仅允许当前审批实例恢复状态并释放其候选快照引用。
     *
     * @param resourceId Skill ID
     * @param previousStatus 提交前状态
     * @param approvalInstanceId 审批实例 ID
     */
    @Override
    public void restoreState(BigInteger resourceId,
                             PublishStatus previousStatus,
                             BigInteger approvalInstanceId) {
        ApprovalInstance instance = requireRestorableInstance(approvalInstanceId, resourceId);
        Skill skill = requireResource(resourceId);
        if (!approvalInstanceId.equals(skill.getCurrentApprovalInstanceId())) {
            if (skill.getCurrentApprovalInstanceId() == null
                    && PublishStatus.from(skill.getPublishStatus()) == previousStatus
                    && approvalInstanceService.isLatestResourceInstance(
                    approvalInstanceId, resourceType(), resourceId)) {
                return;
            }
            throw new BusinessException(409, 4092, "审批申请已过期，不能恢复当前 Skill");
        }
        if (ApprovalActionType.PUBLISH.getCode().equals(instance.getActionType())) {
            skillService.releaseSnapshotContents(readResourceSnapshot(instance.getSnapshotJson()));
        }
        if (skillMapper.restoreApprovalState(resourceId, skill.getTenantId(), approvalInstanceId,
                previousStatus.getCode()) != 1) {
            throw new BusinessException(409, 4092, "Skill 审批状态已变化，请刷新后重试");
        }
    }

    private String stringValue(Object value) {
        return value == null ? null : String.valueOf(value);
    }

    /**
     * 判断同一审批实例的终态动作是否已经应用。
     *
     * @param skill 当前 Skill
     * @param action 审批动作
     * @param snapshot 审批冻结快照
     * @return 已应用时返回 {@code true}
     */
    private boolean isAppliedTerminal(Skill skill,
                                      ApprovalActionType action,
                                      Map<String, Object> snapshot) {
        PublishStatus status = PublishStatus.from(skill.getPublishStatus());
        if (action == ApprovalActionType.PUBLISH) {
            return status == PublishStatus.PUBLISHED
                    && java.util.Objects.equals(skill.getSnapshotHash(), snapshot.get("snapshotHash"));
        }
        return action == ApprovalActionType.OFFLINE && status == PublishStatus.OFFLINE;
    }

    /**
     * 校验审批实例与当前回调的资源、动作和终态完全匹配。
     *
     * @param instanceId 审批实例 ID
     * @param resourceId Skill ID
     * @param action 动作类型
     * @param expectedStatus 期望实例终态
     * @return 已校验的审批实例
     */
    private ApprovalInstance requireMatchingInstance(BigInteger instanceId,
                                                      BigInteger resourceId,
                                                      ApprovalActionType action,
                                                      ApprovalInstanceStatus expectedStatus) {
        ApprovalInstance instance = approvalInstanceService.getById(instanceId);
        if (instance == null
                || !resourceType().equals(instance.getResourceType())
                || !resourceId.equals(instance.getResourceId())
                || !action.getCode().equals(instance.getActionType())
                || expectedStatus != ApprovalInstanceStatus.from(instance.getStatus())) {
            throw new BusinessException(409, 4092, "审批回调与 Skill 申请不匹配");
        }
        return instance;
    }

    /**
     * 校验驳回或撤回实例可用于恢复当前 Skill。
     *
     * @param instanceId 审批实例 ID
     * @param resourceId Skill ID
     * @return 已校验的审批实例
     */
    private ApprovalInstance requireRestorableInstance(BigInteger instanceId, BigInteger resourceId) {
        ApprovalInstance instance = approvalInstanceService.getById(instanceId);
        if (instance == null
                || !resourceType().equals(instance.getResourceType())
                || !resourceId.equals(instance.getResourceId())) {
            throw new BusinessException(409, 4092, "审批回调与 Skill 申请不匹配");
        }
        ApprovalInstanceStatus status = ApprovalInstanceStatus.from(instance.getStatus());
        if (status != ApprovalInstanceStatus.REJECTED && status != ApprovalInstanceStatus.REVOKED) {
            throw new BusinessException(409, 4092, "审批实例尚未进入可恢复终态");
        }
        return instance;
    }

    private Skill findCurrentTenantSkill(BigInteger id, boolean forUpdate) {
        LoginAccount account = SaTokenUtil.getLoginAccount();
        if (account == null || account.getId() == null || account.getTenantId() == null) {
            throw new BusinessException(401, 401, "未登录或登录态无效");
        }
        QueryWrapper query = QueryWrapper.create()
                .eq(Skill::getId, id)
                .eq(Skill::getTenantId, account.getTenantId());
        if (forUpdate) {
            query.forUpdate();
        }
        return skillMapper.selectOneByQuery(query);
    }
}
