package com.ruoyi.asset.service.impl;

import java.util.Date;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
import com.ruoyi.asset.constant.AssetLifecycleBusinessType;
import com.ruoyi.asset.constant.AssetStatus;
import com.ruoyi.asset.domain.AmsAsset;
import com.ruoyi.asset.domain.AmsAssetLifecycleLog;
import com.ruoyi.asset.domain.AmsAssetLocation;
import com.ruoyi.asset.domain.AmsWarehouse;
import com.ruoyi.asset.domain.AssetTransitionContext;
import com.ruoyi.asset.domain.AssetTransitionResult;
import com.ruoyi.asset.mapper.AmsAssetMapper;
import com.ruoyi.asset.service.IAmsAssetLocationService;
import com.ruoyi.asset.service.IAmsWarehouseService;
import com.ruoyi.asset.service.IAssetLifecycleService;
import com.ruoyi.asset.service.IAssetStatusTransitionService;
import com.ruoyi.common.constant.UserConstants;
import com.ruoyi.common.core.domain.entity.SysDept;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.bean.BeanUtils;
import com.ruoyi.system.service.ISysDeptService;
import com.ruoyi.system.service.ISysUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * 资产状态流转公共服务实现
 * <p>
 * 所有业务单据（入库、领用、退库、借用、调拨、维修、报废）在确认环节统一调用本服务
 * 完成资产状态变更和履历记录，保障状态机的一致性。
 * </p>
 * <p>
 * 核心流程：悲观锁定资产 → 校验前置状态 → 执行归属变更 → 更新状态 → 写入履历日志。
 * 整个流转在同一事务中完成，任何环节失败都会回滚。
 * </p>
 *
 * @author Yangk
 */
@Service
@Transactional(rollbackFor = Exception.class)
public class AssetStatusTransitionServiceImpl implements IAssetStatusTransitionService
{
    private static final String ENABLED_YES = "Y";

    private static final String DEL_FLAG_NORMAL = "0";

    @Autowired
    private AmsAssetMapper amsAssetMapper;

    @Autowired
    private IAmsWarehouseService amsWarehouseService;

    @Autowired
    private IAmsAssetLocationService amsAssetLocationService;

    @Autowired
    private ISysDeptService sysDeptService;

    @Autowired
    private ISysUserService sysUserService;

    @Autowired
    private IAssetLifecycleService assetLifecycleService;

    @Override
    public AssetTransitionResult confirmInbound(Long assetId, Long warehouseId, Long locationId,
            AssetTransitionContext context)
    {
        // 首次入库是待入库资产进入仓库的唯一入口，在库资产换仓必须走调拨业务。
        return executeTransition(assetId, Set.of(AssetStatus.PENDING_INBOUND), asset -> AssetStatus.IN_STOCK,
                context, AssetLifecycleBusinessType.INBOUND, "确认资产入库",
                asset -> {
                    fillWarehouseLocation(asset, warehouseId, locationId);
                    clearUseOwnership(asset);
                });
    }

    @Override
    public AssetTransitionResult confirmReceive(Long assetId, Long useDeptId, Long useUserId,
            AssetTransitionContext context)
    {
        return executeTransition(assetId, Set.of(AssetStatus.IN_STOCK), asset -> AssetStatus.IN_USE,
                context, AssetLifecycleBusinessType.RECEIVE, "确认资产领用",
                asset -> {
                    clearWarehouseLocation(asset);
                    fillUseOwnership(asset, useDeptId, useUserId);
                });
    }

    @Override
    public AssetTransitionResult confirmReturn(Long assetId, Long warehouseId, Long locationId,
            AssetTransitionContext context)
    {
        return executeTransition(assetId, Set.of(AssetStatus.IN_USE), asset -> AssetStatus.IN_STOCK,
                context, AssetLifecycleBusinessType.RETURN, "确认资产退库",
                asset -> {
                    fillWarehouseLocation(asset, warehouseId, locationId);
                    clearUseOwnership(asset);
                });
    }

    @Override
    public AssetTransitionResult confirmBorrow(Long assetId, AssetTransitionContext context)
    {
        return executeTransition(assetId, Set.of(AssetStatus.IN_STOCK), asset -> AssetStatus.BORROWED,
                context, AssetLifecycleBusinessType.BORROW, "确认资产借出",
                asset -> {
                    validateInStockOwnership(asset);
                    clearWarehouseLocation(asset);
                    clearUseOwnership(asset);
                });
    }

    @Override
    public AssetTransitionResult confirmBorrowReturn(Long assetId, Long warehouseId, Long locationId,
            AssetTransitionContext context)
    {
        return executeTransition(assetId, Set.of(AssetStatus.BORROWED), asset -> AssetStatus.IN_STOCK,
                context, AssetLifecycleBusinessType.BORROW_RETURN, "确认资产归还",
                asset -> {
                    fillWarehouseLocation(asset, warehouseId, locationId);
                    clearUseOwnership(asset);
                });
    }

    @Override
    public AssetTransitionResult confirmTransfer(Long assetId, Long warehouseId, Long locationId,
            AssetTransitionContext context)
    {
        return executeTransition(assetId, Set.of(AssetStatus.IN_STOCK),
                asset -> AssetStatus.IN_STOCK, context, AssetLifecycleBusinessType.TRANSFER, "确认资产调拨",
                asset -> {
                    fillWarehouseLocation(asset, warehouseId, locationId);
                    clearUseOwnership(asset);
                });
    }

    @Override
    public AssetTransitionResult startRepair(Long assetId, AssetTransitionContext context)
    {
        return executeTransition(assetId, Set.of(AssetStatus.IN_STOCK, AssetStatus.IN_USE),
                asset -> AssetStatus.REPAIRING, context, AssetLifecycleBusinessType.REPAIR, "开始资产维修",
                this::validateOwnershipForRepair);
    }

    @Override
    public AssetTransitionResult finishRepair(Long assetId, String beforeAssetStatus,
            AssetTransitionContext context)
    {
        // 维修完成恢复维修前状态；未传入 beforeAssetStatus 时默认恢复为在库
        String normalizedBeforeStatus = StringUtils.trim(beforeAssetStatus);
        String restoreStatus = StringUtils.isEmpty(normalizedBeforeStatus)
                ? AssetStatus.IN_STOCK : normalizedBeforeStatus;
        if (!StringUtils.equalsAny(restoreStatus, AssetStatus.IN_STOCK, AssetStatus.IN_USE))
        {
            throw new ServiceException("维修完成只能恢复为在库或在用状态");
        }
        return executeTransition(assetId, Set.of(AssetStatus.REPAIRING), asset -> restoreStatus,
                context, AssetLifecycleBusinessType.REPAIR, "完成资产维修",
                asset -> {
                    if (StringUtils.equals(AssetStatus.IN_STOCK, restoreStatus))
                    {
                        validateWarehouseLocation(asset);
                        clearUseOwnership(asset);
                    }
                    else
                    {
                        validateEmptyWarehouseLocation(asset);
                        validateInUseOwnership(asset);
                    }
                });
    }

    @Override
    public AssetTransitionResult confirmDisposal(Long assetId, AssetTransitionContext context)
    {
        return executeTransition(assetId, Set.of(AssetStatus.IN_STOCK, AssetStatus.IN_USE),
                asset -> AssetStatus.DISPOSED, context, AssetLifecycleBusinessType.DISPOSAL, "确认资产报废",
                asset -> { });
    }

    /**
     * 状态流转模板方法：锁定资产 → 校验前置状态 → 执行变更 → 持久化 → 记录履历
     *
     * @param assetId              资产ID
     * @param allowedStatuses      允许执行此流转的前置状态集合
     * @param targetStatusResolver 根据当前资产计算目标状态的函数
     * @param context              流转操作上下文
     * @param businessType         履历业务类型
     * @param defaultSummary       默认变更摘要（context 未提供时使用）
     * @param assetUpdater         在状态变更前对资产执行附加操作（如变更仓位、归属）
     * @return 变更前后的资产快照
     */
    private AssetTransitionResult executeTransition(Long assetId, Set<String> allowedStatuses,
            Function<AmsAsset, String> targetStatusResolver, AssetTransitionContext context,
            String businessType, String defaultSummary, Consumer<AmsAsset> assetUpdater)
    {
        validateContext(assetId, context);
        // 悲观锁定资产行，防止并发流转导致数据竞争
        AmsAsset lockedAsset = amsAssetMapper.selectAmsAssetByAssetIdForUpdate(assetId);
        if (StringUtils.isNull(lockedAsset))
        {
            throw new ServiceException("资产不存在或已删除");
        }
        // 校验当前状态是否在允许的前置状态集合中
        if (!allowedStatuses.contains(lockedAsset.getAssetStatus()))
        {
            throw new ServiceException(StringUtils.format("资产【{}】当前状态【{}】不允许执行{}",
                    lockedAsset.getAssetCode(), lockedAsset.getAssetStatus(), defaultSummary));
        }

        // 分别保存变更前快照和变更后快照，用于履历记录和返回给调用方
        AmsAsset beforeAsset = copyAsset(lockedAsset);
        AmsAsset afterAsset = copyAsset(lockedAsset);
        // 先执行归属字段变更（如仓位、使用部门等），再设置目标状态
        assetUpdater.accept(afterAsset);
        afterAsset.setAssetStatus(targetStatusResolver.apply(lockedAsset));
        afterAsset.setUpdateBy(context.getOperateLoginName());
        Date operateTime = DateUtils.getNowDate();
        afterAsset.setUpdateTime(operateTime);

        // 使用专用的全字段覆盖式 SQL 更新，避免动态 SQL 导致 null 字段跳过
        if (amsAssetMapper.updateAssetForTransition(afterAsset) != 1)
        {
            throw new ServiceException("资产状态流转更新失败");
        }
        // 写入履历日志，与资产更新在同一事务中
        assetLifecycleService.recordLifecycle(beforeAsset, afterAsset,
                buildLifecycleLog(context, businessType, defaultSummary, operateTime));
        return new AssetTransitionResult(beforeAsset, copyAsset(afterAsset));
    }

    /** 校验流转上下文的必填项和字段长度限制，防止脏数据写入履历 */
    private void validateContext(Long assetId, AssetTransitionContext context)
    {
        if (StringUtils.isNull(assetId))
        {
            throw new ServiceException("资产ID不能为空");
        }
        if (StringUtils.isNull(context))
        {
            throw new ServiceException("资产状态流转上下文不能为空");
        }
        normalizeContext(context);
        if (StringUtils.isNull(context.getSourceOrderId())
                || StringUtils.isEmpty(context.getSourceOrderNo()))
        {
            throw new ServiceException("来源单据ID和单据号不能为空");
        }
        if (StringUtils.isNull(context.getOperateUserId())
                || StringUtils.isEmpty(context.getOperateUserName())
                || StringUtils.isEmpty(context.getOperateLoginName()))
        {
            throw new ServiceException("操作人ID、名称和登录账号不能为空");
        }
        // 长度限制与数据库字段定义一致，防止入库时被截断
        validateLength(context.getSourceOrderNo(), 64, "来源单据号");
        validateLength(context.getOperateUserName(), 100, "操作用户名称");
        validateLength(context.getOperateLoginName(), 64, "操作登录账号");
        validateLength(context.getChangeSummary(), 500, "变更摘要");
        validateLength(context.getRemark(), 500, "备注");
    }

    /** 清除上下文字符串字段的首尾空白，统一数据质量 */
    private void normalizeContext(AssetTransitionContext context)
    {
        context.setSourceOrderNo(StringUtils.trim(context.getSourceOrderNo()));
        context.setOperateUserName(StringUtils.trim(context.getOperateUserName()));
        context.setOperateLoginName(StringUtils.trim(context.getOperateLoginName()));
        context.setChangeSummary(StringUtils.trim(context.getChangeSummary()));
        context.setRemark(StringUtils.trim(context.getRemark()));
    }

    private void validateLength(String value, int maxLength, String fieldName)
    {
        if (StringUtils.isNotEmpty(value) && value.length() > maxLength)
        {
            throw new ServiceException(fieldName + "长度不能超过" + maxLength + "个字符");
        }
    }

    /** 校验并回填目标仓库和位置的快照字段，同时验证位置归属关系和启用状态 */
    private void fillWarehouseLocation(AmsAsset asset, Long warehouseId, Long locationId)
    {
        if (StringUtils.isNull(warehouseId) || StringUtils.isNull(locationId))
        {
            throw new ServiceException("目标仓库和存放位置不能为空");
        }
        AmsWarehouse warehouse = amsWarehouseService.selectAmsWarehouseByWarehouseId(warehouseId);
        if (StringUtils.isNull(warehouse) || !StringUtils.equals(ENABLED_YES, warehouse.getEnabled()))
        {
            throw new ServiceException("目标仓库不存在或已停用");
        }
        AmsAssetLocation location = amsAssetLocationService.selectAmsAssetLocationByLocationId(locationId);
        if (StringUtils.isNull(location) || !StringUtils.equals(ENABLED_YES, location.getEnabled()))
        {
            throw new ServiceException("目标存放位置不存在或已停用");
        }
        // 位置必须隶属于所选仓库，防止跨仓库选位
        if (!warehouseId.equals(location.getWarehouseId()))
        {
            throw new ServiceException("目标存放位置不属于所选仓库");
        }
        asset.setWarehouseId(warehouse.getWarehouseId());
        asset.setWarehouseCode(warehouse.getWarehouseCode());
        asset.setWarehouseName(warehouse.getWarehouseName());
        asset.setLocationId(location.getLocationId());
        asset.setLocationCode(location.getLocationCode());
        asset.setLocationName(location.getLocationName());
    }

    /** 校验并回填使用部门和使用人的快照字段，同时验证其在正常启用状态 */
    private void fillUseOwnership(AmsAsset asset, Long useDeptId, Long useUserId)
    {
        if (StringUtils.isNull(useDeptId) || StringUtils.isNull(useUserId))
        {
            throw new ServiceException("使用部门和使用人不能为空");
        }
        SysDept dept = sysDeptService.selectDeptById(useDeptId);
        if (StringUtils.isNull(dept)
                || !StringUtils.equals(UserConstants.DEPT_NORMAL, dept.getStatus())
                || !StringUtils.equals(DEL_FLAG_NORMAL, dept.getDelFlag()))
        {
            throw new ServiceException("使用部门不存在或已停用");
        }
        SysUser user = sysUserService.selectUserById(useUserId);
        if (StringUtils.isNull(user)
                || !StringUtils.equals(UserConstants.NORMAL, user.getStatus())
                || !StringUtils.equals(DEL_FLAG_NORMAL, user.getDelFlag()))
        {
            throw new ServiceException("使用人不存在或已停用");
        }
        asset.setUseDeptId(dept.getDeptId());
        asset.setUseDeptName(dept.getDeptName());
        asset.setUseUserId(user.getUserId());
        asset.setUseUserName(user.getUserName());
    }

    /** 清除使用归属（入库、退库、归还等回到在库状态时调用） */
    private void clearUseOwnership(AmsAsset asset)
    {
        asset.setUseDeptId(null);
        asset.setUseDeptName(null);
        asset.setUseUserId(null);
        asset.setUseUserName(null);
    }

    /** 清空当前仓位；资产领用或借出后，实物已经离开仓库。 */
    private void clearWarehouseLocation(AmsAsset asset)
    {
        asset.setWarehouseId(null);
        asset.setWarehouseCode(null);
        asset.setWarehouseName(null);
        asset.setLocationId(null);
        asset.setLocationCode(null);
        asset.setLocationName(null);
    }

    /** 恢复在库状态时必须保留完整仓位。 */
    private void validateWarehouseLocation(AmsAsset asset)
    {
        if (StringUtils.isNull(asset.getWarehouseId()) || StringUtils.isNull(asset.getLocationId())
                || StringUtils.isEmpty(asset.getWarehouseCode()) || StringUtils.isEmpty(asset.getWarehouseName())
                || StringUtils.isEmpty(asset.getLocationCode()) || StringUtils.isEmpty(asset.getLocationName()))
        {
            throw new ServiceException("维修前为在库状态，但资产缺少完整仓库或位置信息");
        }
    }

    /** 恢复在用状态时仓位必须为空，避免同时存在仓位和使用归属。 */
    private void validateEmptyWarehouseLocation(AmsAsset asset)
    {
        if (StringUtils.isNotNull(asset.getWarehouseId()) || StringUtils.isNotNull(asset.getLocationId())
                || StringUtils.isNotEmpty(asset.getWarehouseCode()) || StringUtils.isNotEmpty(asset.getWarehouseName())
                || StringUtils.isNotEmpty(asset.getLocationCode()) || StringUtils.isNotEmpty(asset.getLocationName()))
        {
            throw new ServiceException("维修前为在用状态，但资产仍存在仓库或位置信息");
        }
    }

    /** 借出前校验：在库资产不应有残留的使用归属数据，否则说明台账数据不一致 */
    private void validateInStockOwnership(AmsAsset asset)
    {
        if (StringUtils.isNotNull(asset.getUseDeptId()) || StringUtils.isNotNull(asset.getUseUserId())
                || StringUtils.isNotEmpty(asset.getUseDeptName()) || StringUtils.isNotEmpty(asset.getUseUserName()))
        {
            throw new ServiceException("在库资产仍存在使用归属，请先修正后再借出");
        }
    }

    /** 维修完成恢复在用时校验：资产必须仍有完整的使用归属（维修期间不允许修改归属） */
    private void validateInUseOwnership(AmsAsset asset)
    {
        if (StringUtils.isNull(asset.getUseDeptId()) || StringUtils.isNull(asset.getUseUserId())
                || StringUtils.isEmpty(asset.getUseDeptName()) || StringUtils.isEmpty(asset.getUseUserName()))
        {
            throw new ServiceException("维修前为在用状态，但资产缺少使用部门或使用人");
        }
    }

    /** 开始维修前校验当前状态对应的归属模式完整。 */
    private void validateOwnershipForRepair(AmsAsset asset)
    {
        if (StringUtils.equals(AssetStatus.IN_STOCK, asset.getAssetStatus()))
        {
            validateWarehouseLocation(asset);
            validateInStockOwnership(asset);
        }
        else
        {
            validateEmptyWarehouseLocation(asset);
            validateInUseOwnership(asset);
        }
    }

    /** 构建履历日志对象，变更摘要未传入时使用默认描述 */
    private AmsAssetLifecycleLog buildLifecycleLog(AssetTransitionContext context, String businessType,
            String defaultSummary, Date operateTime)
    {
        AmsAssetLifecycleLog lifecycleLog = new AmsAssetLifecycleLog();
        lifecycleLog.setBusinessType(businessType);
        lifecycleLog.setSourceOrderId(context.getSourceOrderId());
        lifecycleLog.setSourceOrderNo(context.getSourceOrderNo());
        lifecycleLog.setSourceItemId(context.getSourceItemId());
        lifecycleLog.setOperateUserId(context.getOperateUserId());
        lifecycleLog.setOperateUserName(context.getOperateUserName());
        lifecycleLog.setOperateTime(operateTime);
        lifecycleLog.setChangeSummary(StringUtils.isEmpty(context.getChangeSummary())
                ? defaultSummary : context.getChangeSummary());
        lifecycleLog.setCreateBy(context.getOperateLoginName());
        lifecycleLog.setRemark(context.getRemark());
        return lifecycleLog;
    }

    /** 浅拷贝资产对象，用于保存变更前快照（AmsAsset 字段均为不可变类型或 null，浅拷贝足够） */
    private AmsAsset copyAsset(AmsAsset source)
    {
        AmsAsset target = new AmsAsset();
        BeanUtils.copyProperties(source, target);
        return target;
    }
}
