package com.openhis.web.inhospitalnursestation.appservice.impl;

import com.core.common.core.domain.R;
import com.core.common.core.domain.model.LoginUser;
import com.core.common.exception.ServiceException;
import com.core.common.utils.AssignSeqUtil;
import com.core.common.utils.SecurityUtils;
import com.openhis.administration.domain.ChargeItem;
import com.openhis.administration.service.IChargeItemService;
import com.openhis.common.constant.CommonConstants;
import com.openhis.common.enums.*;
import com.openhis.web.doctorstation.dto.AdviceSaveDto;
import com.openhis.web.doctorstation.utils.AdviceUtils;
import com.openhis.web.inhospitalnursestation.appservice.INurseBillingAppService;
import com.openhis.web.regdoctorstation.dto.RegAdviceSaveDto;
import com.openhis.web.regdoctorstation.dto.RegAdviceSaveParam;
import com.openhis.workflow.domain.DeviceRequest;
import com.openhis.workflow.service.IDeviceDispenseService;
import com.openhis.workflow.service.IDeviceRequestService;
import com.openhis.workflow.service.IServiceRequestService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 住院护士站划价服务实现类
 * 核心功能：临时耗材/诊疗活动划价签发、关联费用项管理、库存校验、医嘱删除（已收费校验）等
 */
@Service
public class NurseBillingAppService implements INurseBillingAppService {

    // 依赖注入：保留@Resource注解，按原有方式注入依赖
    @Resource
    private AdviceUtils adviceUtils;
    @Resource
    private AssignSeqUtil assignSeqUtil;
    @Resource
    private IDeviceRequestService iDeviceRequestService;
    @Resource
    private IServiceRequestService iServiceRequestService;
    @Resource
    private IChargeItemService iChargeItemService;
    @Resource
    private IDeviceDispenseService iDeviceDispenseService;

    // 常量提取：避免魔法值，提高代码可维护性和可读性
    /** 耗材请求服务表名（关联费用项使用） */
    private static final String SERVICE_TABLE_DEVICE_REQUEST = CommonConstants.TableName.WOR_DEVICE_REQUEST;
    private static final String SERVICE_TABLE_SERVICE_REQUEST = CommonConstants.TableName.WOR_SERVICE_REQUEST;
    /** 费用项业务编号前缀 */
    private static final String CHARGE_ITEM_BUS_NO_PREFIX = AssignSeqEnum.CHARGE_ITEM_NO.getPrefix();
    /** 耗材申请单号序列号长度（按日生成） */
    private static final int DEVICE_RES_NO_SEQ_LENGTH = 4;
    /** 医嘱签发编码序列号长度 */
    private static final int ADVICE_SIGN_SEQ_LENGTH = 10;

    /**
     * 新增住院护士站划价
     * 完整流程：参数初始化 → 入参校验 → 医嘱分类（耗材/诊疗活动）→ 生成签发编码 → 处理耗材划价 → 处理诊疗活动划价
     *
     * @param regAdviceSaveParam 划价请求参数（含患者信息、医嘱列表、科室ID等核心数据）
     * @return R<?> 划价结果响应（成功/失败信息、状态码）
     */
    @Override
    @Transactional(rollbackFor = Exception.class) // 事务管理：划价相关操作原子性，任一环节失败整体回滚
    public R<?> addInNurseBilling(RegAdviceSaveParam regAdviceSaveParam) {
        // 1. 时间参数初始化：默认使用当前时间，支持入参指定
        Date curDate = new Date();
        Date startTime = regAdviceSaveParam.getStartTime() == null ? curDate : regAdviceSaveParam.getStartTime();
        Date authoredTime = regAdviceSaveParam.getAuthoredTime() == null ? curDate : regAdviceSaveParam.getAuthoredTime();

        // 2. 入参校验：非空校验 + 库存校验，校验失败直接返回错误响应
        R<?> checkResult = checkNurseBillingParam(regAdviceSaveParam);
        if (checkResult.getCode() != R.SUCCESS) {
            return checkResult;
        }

        // 3. 提取核心业务参数
        Long organizationId = regAdviceSaveParam.getOrganizationId(); // 患者住院科室ID
        List<RegAdviceSaveDto> allAdviceList = regAdviceSaveParam.getRegAdviceSaveList(); // 所有待处理医嘱列表

        // 4. 按医嘱类型分类：拆分耗材类和诊疗活动类（分别处理）
        List<RegAdviceSaveDto> deviceAdviceList = filterDeviceAdvice(allAdviceList); // 耗材类医嘱
        List<RegAdviceSaveDto> activityAdviceList = filterActivityAdvice(allAdviceList); // 诊疗活动类医嘱

        // 5. 生成全局唯一的医嘱签发编码
        String signCode = assignSeqUtil.getSeq(AssignSeqEnum.ADVICE_SIGN.getPrefix(), ADVICE_SIGN_SEQ_LENGTH);

        // 6. 处理耗材类医嘱划价（新增/修改/删除逻辑）
        handleDeviceBilling(deviceAdviceList, null, signCode, organizationId, curDate);

        // 7. 处理诊疗活动类医嘱划价（原代码未实现，保留扩展入口）
        handleActivityBilling(activityAdviceList, signCode, organizationId, curDate, startTime, authoredTime);

        // 8. 划价成功返回响应
        return R.ok("住院划价签发成功");
    }

    /**
     * 筛选耗材类医嘱
     * 按医嘱类型枚举（ItemType.DEVICE）过滤，仅保留耗材相关医嘱
     *
     * @param allAdviceList 所有待处理医嘱列表
     * @return List<RegAdviceSaveDto> 耗材类医嘱列表
     */
    private List<RegAdviceSaveDto> filterDeviceAdvice(List<RegAdviceSaveDto> allAdviceList) {
        return allAdviceList.stream()
                .filter(advice -> ItemType.DEVICE.getValue().equals(advice.getAdviceType()))
                .collect(Collectors.toList());
    }

    /**
     * 筛选诊疗活动类医嘱
     * 按医嘱类型枚举（ItemType.ACTIVITY）过滤，仅保留诊疗活动相关医嘱
     *
     * @param allAdviceList 所有待处理医嘱列表
     * @return List<RegAdviceSaveDto> 诊疗活动类医嘱列表
     */
    private List<RegAdviceSaveDto> filterActivityAdvice(List<RegAdviceSaveDto> allAdviceList) {
        return allAdviceList.stream()
                .filter(advice -> ItemType.ACTIVITY.getValue().equals(advice.getAdviceType()))
                .collect(Collectors.toList());
    }

    /**
     * 处理耗材类医嘱划价
     * 核心流程：筛选临时耗材 → 处理删除（已收费校验）→ 新增/修改耗材记录 → 同步发放+费用项
     *
     * @param deviceAdviceList 耗材类医嘱列表
     * @param deleteList       待删除的耗材医嘱列表（可为null，暂无删除场景传null）
     * @param signCode         医嘱签发编码（全局唯一）
     * @param organizationId   患者住院科室ID
     * @param curDate          当前操作时间
     */
    private void handleDeviceBilling(List<RegAdviceSaveDto> deviceAdviceList, List<AdviceSaveDto> deleteList,
                                     String signCode, Long organizationId, Date curDate) {
        // 1. 筛选临时类型耗材：仅处理临时医嘱（TherapyTimeType.TEMPORARY）的新增/修改
        List<AdviceSaveDto> tempDeviceList = deviceAdviceList.stream()
                .filter(advice -> TherapyTimeType.TEMPORARY.getValue().equals(advice.getTherapyEnum()))
                .collect(Collectors.toList());

        // 2. 处理耗材删除：校验已收费状态 + 关联数据删除
        handleDelRequest(deleteList,SERVICE_TABLE_DEVICE_REQUEST);

        // 3. 处理耗材新增/修改：同步保存耗材请求、发放记录、费用项
        handleDeviceInsertOrUpdate(tempDeviceList, organizationId, curDate);
    }

    /**
     * 处理诊疗活动类医嘱划价
     * （扩展入口：原代码未实现，可参考耗材处理逻辑，关联服务请求表、费用项等）
     *
     * @param activityAdviceList 诊疗活动类医嘱列表
     * @param signCode           医嘱签发编码
     * @param organizationId     住院科室ID
     * @param curDate            当前操作时间
     * @param startTime          医嘱开始时间
     * @param authoredTime       医嘱签发时间
     */
    private void handleActivityBilling(List<RegAdviceSaveDto> activityAdviceList, String signCode,
                                       Long organizationId, Date curDate, Date startTime, Date authoredTime) {
        // 注：诊疗活动划价逻辑待实现，建议流程：
        // 1. 筛选临时类型诊疗活动
        // 2. 处理删除（如需）
        // 3. 新增/修改服务请求记录（IServiceRequestService）
        // 4. 同步生成/更新费用项（IChargeItemService）
    }

    /**
     * 处理耗材删除逻辑
     * 关键校验：已收费的耗材项目不允许删除
     * 关联删除：耗材请求表 → 耗材发放记录 → 费用项表（级联删除关联数据）
     *
     * @param deleteList 待删除的耗材医嘱列表（可为null）
     */
    private void handleDelRequest(List<AdviceSaveDto> deleteList,String serviceTable) {
        // 空列表直接返回，避免无效循环和查询
        if (deleteList == null || deleteList.isEmpty()) {
            return;
        }
        // 提取待删除请求ID列表（用于查询费用状态）
        List<Long> delRequestIds = deleteList.stream()
                .map(AdviceSaveDto::getRequestId)
                .collect(Collectors.toList());

        // 校验：待删除项是否已收费，已收费则抛出异常阻止删除
        checkDeletedDeviceChargeStatus(delRequestIds);

        // 执行级联删除：循环处理每个待删除项的关联数据
        for (AdviceSaveDto deleteDto : deleteList) {
            Long requestId = deleteDto.getRequestId();
            if (serviceTable.equals(SERVICE_TABLE_DEVICE_REQUEST)){
                iDeviceRequestService.removeById(requestId); // 删除耗材请求主记录
                iDeviceDispenseService.deleteDeviceDispense(requestId); // 删除关联的耗材发放记录
            }
            // 删除关联的费用项（按服务表+服务ID关联）
            iChargeItemService.deleteByServiceTableAndId(SERVICE_TABLE_DEVICE_REQUEST, requestId);
        }
    }

    /**
     * 校验待删除耗材的费用状态
     * 查询待删除项对应的费用项，若存在已收费（ChargeItemStatus.BILLED）状态则抛出业务异常
     *
     * @param delRequestIds 待删除的耗材请求ID列表
     */
    private void checkDeletedDeviceChargeStatus(List<Long> delRequestIds) {
        if (delRequestIds.isEmpty()) {
            return;
        }

        // 查询待删除耗材对应的费用项列表
        List<ChargeItem> chargeItemList = iChargeItemService.getChargeItemInfoByReqId(delRequestIds);
        if (chargeItemList == null || chargeItemList.isEmpty()) {
            return; // 无关联费用项，直接允许删除
        }

        // 校验是否存在已收费项：使用stream.anyMatch简化循环判断
        boolean hasBilledItem = chargeItemList.stream()
                .anyMatch(ci -> ChargeItemStatus.BILLED.getValue().equals(ci.getStatusEnum()));
        if (hasBilledItem) {
            throw new ServiceException("删除失败：部分项目已完成收费，不支持直接删除，请联系收费人员处理后重试");
        }
    }

    /**
     * 处理耗材新增/修改
     * 流程：构建耗材请求对象 → 保存/更新 → 处理耗材发放 → 构建费用项 → 保存/更新费用项
     *
     * @param tempDeviceList 临时耗材医嘱列表（新增/修改）
     * @param organizationId 住院科室ID
     * @param curDate        当前操作时间
     */
    private void handleDeviceInsertOrUpdate(List<AdviceSaveDto> tempDeviceList, Long organizationId, Date curDate) {
        if (tempDeviceList.isEmpty()) {
            return; // 无临时耗材需处理，直接返回
        }

        // 循环处理每个临时耗材医嘱
        for (AdviceSaveDto adviceDto : tempDeviceList) {
            // 1. 构建耗材请求对象（DeviceRequest）并执行保存/更新
            if (adviceDto.getRequestId() == null) {
                adviceDto.setDbOpType(DbOpType.INSERT.getCode()); // 新增时设置操作类型
            }else {
                adviceDto.setDbOpType(DbOpType.UPDATE.getCode()); // 修改时设置操作类型
            }
            DeviceRequest deviceRequest = buildDeviceRequest(adviceDto, curDate);
            iDeviceRequestService.saveOrUpdate(deviceRequest);

            // 2. 同步处理耗材发放：根据操作类型（新增/修改）执行发放逻辑
            iDeviceDispenseService.handleDeviceDispense(deviceRequest, adviceDto.getDbOpType());

            // 3. 构建费用项对象（ChargeItem）并执行保存/更新
            ChargeItem chargeItem = buildChargeItem(adviceDto, deviceRequest, curDate);
            iChargeItemService.saveOrUpdate(chargeItem);
        }
    }

    /**
     * 构建耗材请求对象（DeviceRequest）
     * 功能：将医嘱DTO（AdviceSaveDto）的属性映射到耗材请求实体，设置默认状态和业务编号
     *
     * @param adviceDto 耗材医嘱DTO（含请求参数）
     * @param curDate   当前操作时间
     * @return DeviceRequest 构建完成的耗材请求实体
     */
    private DeviceRequest buildDeviceRequest(AdviceSaveDto adviceDto, Date curDate) {
        DeviceRequest deviceRequest = new DeviceRequest();

        // 基础配置：主键、状态、业务编号
        deviceRequest.setId(adviceDto.getRequestId()); // 主键ID（新增为null，修改为已有ID）
        deviceRequest.setStatusEnum(RequestStatus.ACTIVE.getValue()); // 划价时直接签发，状态为"激活"
        // 按日生成业务编号：前缀+4位序列号（确保每日唯一）
        deviceRequest.setBusNo(assignSeqUtil.getSeqByDay(AssignSeqEnum.DEVICE_RES_NO.getPrefix(), DEVICE_RES_NO_SEQ_LENGTH));
        deviceRequest.setGenerateSourceEnum(GenerateSource.DOCTOR_PRESCRIPTION.getValue()); // 生成来源：医生处方

        // 业务属性映射：从DTO映射到实体
        deviceRequest.setQuantity(adviceDto.getQuantity());          // 请求数量
        deviceRequest.setUnitCode(adviceDto.getUnitCode());          // 单位编码
        deviceRequest.setLotNumber(adviceDto.getLotNumber());        // 产品批号
        deviceRequest.setCategoryEnum(adviceDto.getCategoryEnum());  // 请求类型（枚举）
        deviceRequest.setDeviceDefId(adviceDto.getAdviceDefinitionId()); // 耗材定义ID
        deviceRequest.setPatientId(adviceDto.getPatientId());        // 患者ID
        deviceRequest.setRequesterId(adviceDto.getPractitionerId()); // 开方医生ID
        deviceRequest.setOrgId(adviceDto.getFounderOrgId());         // 开方人科室ID
        deviceRequest.setReqAuthoredTime(curDate);                   // 请求开始时间
        deviceRequest.setPerformLocation(adviceDto.getLocationId()); // 发放耗材房ID
        deviceRequest.setEncounterId(adviceDto.getEncounterId());    // 就诊ID
        deviceRequest.setPackageId(adviceDto.getPackageId());        // 组套ID
        deviceRequest.setContentJson(adviceDto.getContentJson());    // 请求内容JSON（扩展信息）
        deviceRequest.setYbClassEnum(adviceDto.getYbClassEnum());    // 医保类别编码
        deviceRequest.setConditionId(adviceDto.getConditionId());    // 诊断ID
        deviceRequest.setEncounterDiagnosisId(adviceDto.getEncounterDiagnosisId()); // 就诊诊断ID

        return deviceRequest;
    }

    /**
     * 构建费用项对象（ChargeItem）
     * 功能：关联耗材请求实体和医嘱DTO，映射费用相关属性，设置默认状态和业务编号
     *
     * @param adviceDto        耗材医嘱DTO（含费用参数）
     * @param deviceRequest    已保存的耗材请求实体（关联服务ID）
     * @param curDate          当前操作时间
     * @return ChargeItem 构建完成的费用项实体
     */
    private ChargeItem buildChargeItem(AdviceSaveDto adviceDto, DeviceRequest deviceRequest,Date curDate) {
        ChargeItem chargeItem = new ChargeItem();

        // 基础配置：主键、状态、业务编号
        chargeItem.setId(adviceDto.getChargeItemId()); // 费用项ID（新增为null，修改为已有ID）
        chargeItem.setStatusEnum(ChargeItemStatus.DRAFT.getValue()); // 初始状态：草稿（未收费）
        // 业务编号：费用项前缀+耗材请求编号（确保与耗材请求一一关联）
        chargeItem.setBusNo(CHARGE_ITEM_BUS_NO_PREFIX.concat(deviceRequest.getBusNo()));
        chargeItem.setGenerateSourceEnum(GenerateSource.DOCTOR_PRESCRIPTION.getValue()); // 生成来源：医生处方

        // 业务属性映射：患者、就诊、定价相关信息
        chargeItem.setPatientId(adviceDto.getPatientId());        // 患者ID
        chargeItem.setContextEnum(adviceDto.getAdviceType());     // 费用类型（耗材）
        chargeItem.setEncounterId(adviceDto.getEncounterId());    // 就诊ID
        chargeItem.setDefinitionId(adviceDto.getDefinitionId());  // 费用定价ID
        chargeItem.setDefDetailId(adviceDto.getDefinitionDetailId()); // 定价子表主键（明细定价）
        chargeItem.setEntererId(adviceDto.getPractitionerId());   // 开立人ID（开方医生）
        chargeItem.setRequestingOrgId(SecurityUtils.getLoginUser().getOrgId());            // 开立科室ID
        chargeItem.setEnteredDate(curDate);                       // 开立时间
        chargeItem.setServiceTable(SERVICE_TABLE_DEVICE_REQUEST); // 医疗服务类型（关联耗材请求表）
        chargeItem.setServiceId(deviceRequest.getId());           // 医疗服务ID（关联耗材请求ID）
        chargeItem.setProductTable(SERVICE_TABLE_DEVICE_REQUEST);// 产品所在表（耗材表）
        chargeItem.setProductId(adviceDto.getAdviceDefinitionId());// 产品ID（耗材定义ID）
        chargeItem.setAccountId(adviceDto.getAccountId());        // 关联账户ID（患者账户）
        chargeItem.setConditionId(adviceDto.getConditionId());    // 诊断ID
        chargeItem.setEncounterDiagnosisId(adviceDto.getEncounterDiagnosisId()); // 就诊诊断ID

        // 费用核心属性：数量、单位、单价、总价
        chargeItem.setQuantityValue(adviceDto.getQuantity()); // 数量（与耗材请求一致）
        chargeItem.setQuantityUnit(adviceDto.getUnitCode());   // 单位（与耗材请求一致）
        chargeItem.setUnitPrice(adviceDto.getUnitPrice());     // 单价（从DTO传入，已定价）
        chargeItem.setTotalPrice(adviceDto.getTotalPrice());   // 总价（数量×单价，DTO已计算）

        return chargeItem;
    }

    /**
     * 划价入参校验
     * 校验规则：1. 参数非空 2. 科室ID非空 3. 医嘱列表非空 4. 临时耗材库存校验
     *
     * @param regAdviceSaveParam 划价请求参数
     * @return R<?> 校验结果（成功返回R.ok()，失败返回R.fail()）
     */
    private R<?> checkNurseBillingParam(RegAdviceSaveParam regAdviceSaveParam) {
        // 1. 整体参数非空校验
        if (regAdviceSaveParam == null) {
            return R.fail("划价请求失败：未获取到有效请求数据，请重新提交");
        }

        // 2. 核心字段非空校验：患者住院科室ID
        if (regAdviceSaveParam.getOrganizationId() == null) {
            return R.fail("划价请求失败：患者住院科室信息缺失，请确认患者科室后重试");
        }

        // 3. 医嘱列表非空校验
        List<RegAdviceSaveDto> adviceList = regAdviceSaveParam.getRegAdviceSaveList();
        if (adviceList == null || adviceList.isEmpty()) {
            return R.fail("划价请求失败：未选择任何待划价项目，请添加医嘱后提交");
        }

        // 4. 临时耗材库存校验：仅校验非删除、非诊疗活动的临时医嘱
        List<AdviceSaveDto> needCheckInventoryList = adviceList.stream()
                .filter(advice -> TherapyTimeType.TEMPORARY.getValue().equals(advice.getTherapyEnum())
                        && !DbOpType.DELETE.getCode().equals(advice.getDbOpType())
                        && !ItemType.ACTIVITY.getValue().equals(advice.getAdviceType()))
                .collect(Collectors.toList());

        // 调用工具类校验库存，返回非null则表示库存不足
        String inventoryTip = adviceUtils.checkInventory(new ArrayList<>(needCheckInventoryList));
        if (inventoryTip != null) {
            // 库存提示已包含具体耗材名称和短缺数量，直接拼接操作建议
            return R.fail("划价失败：" + inventoryTip + "，请联系库房确认库存或调整申请数量");
        }

        // 所有校验通过
        return R.ok();
    }

    // ======================== 以下方法原代码未实现，保留接口方法签名 ========================

    @Override
    public R<?> getBillingListByEncounterId() {
        // 功能：根据就诊ID查询划价列表（待实现）
        return null;
    }

    @Override
    public R<?> deleteInNurseBilling() {
        // 功能：删除住院划价记录（待实现）
//        List<AdviceSaveDto> deleteList
        return null;
    }

    @Override
    public R<?> addOrderBilling() {
        // 功能：新增订单划价（待实现）
        return null;
    }

    @Override
    public R<?> deleteOrderBilling() {
        // 功能：删除订单划价（待实现）
        return null;
    }

    @Override
    public R<?> updateOrderBilling() {
        // 功能：修改订单划价（待实现）
        return null;
    }

    @Override
    public R<?> getOrderBillingByRequestId() {
        // 功能：根据请求ID查询订单划价（待实现）
        return null;
    }
}