package com.njcn.gather.icd.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.njcn.common.pojo.enums.common.DataStateEnum;
import com.njcn.common.pojo.exception.BusinessException;
import com.njcn.common.pojo.constant.PatternRegex;
import com.njcn.gather.icd.mapper.PqIcdPathMapper;
import com.njcn.gather.icd.pojo.enums.IcdResponseEnum;
import com.njcn.gather.icd.pojo.param.PqIcdPathParam;
import com.njcn.gather.icd.pojo.po.PqIcdPath;
import com.njcn.gather.icd.pojo.vo.PqIcdExportJsonVO;
import com.njcn.gather.pojo.constant.DetectionValidMessage;
import com.njcn.gather.icd.service.IPqIcdPathService;
import com.njcn.gather.icd.service.support.IcdArchiveBuilder;
import com.njcn.gather.icd.service.support.IcdPayloadAssembler;
import com.njcn.web.factory.PageFactory;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.Base64;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * @author caozehui
 * @date 2025-02-10
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class PqIcdPathServiceImpl extends ServiceImpl<PqIcdPathMapper, PqIcdPath> implements IPqIcdPathService {
    // 手动录入标准ICD
    private static final int TYPE_STANDARD = 1;
    // 手动录入非标准ICD
    private static final int TYPE_NON_STANDARD = 2;
    // 上游录入的标准ICD
    private static final int TYPE_UPSTREAM_STANDARD = 3;

    // 上游录入的非标准ICD
    private static final int TYPE_UPSTREAM_NON_STANDARD = 4;
    private static final int RESULT_NO = 0;
    private static final int RESULT_YES = 1;

    @Value("${icd.txt-dir}")
    private String icdTxtDir;

    private final IcdPayloadAssembler icdPayloadAssembler;
    private final IcdArchiveBuilder icdArchiveBuilder;

    @Override
    public List<PqIcdPath> listAll() {
        return this.lambdaQuery()
                .select(PqIcdPath::getId, PqIcdPath::getName, PqIcdPath::getType, PqIcdPath::getReferenceIcdId)
                .eq(PqIcdPath::getState, DataStateEnum.ENABLE.getCode())
                .orderByDesc(PqIcdPath::getCreateTime)
                .list();
    }

    @Override
    public List<PqIcdPath> listStandardIcd() {
        return this.baseMapper.selectStandardList();
    }

    @Override
    public Page<PqIcdPath> listIcd(PqIcdPathParam.QueryParam param) {
        Page<PqIcdPath> page = new Page<>(PageFactory.getPageNum(param), PageFactory.getPageSize(param));
        page.setRecords(this.baseMapper.selectPageList(page, StrUtil.trim(param.getName())));
        return page;
    }

    @Override
    @Transactional
    public boolean addIcd(PqIcdPathParam.CreateParam param) {
        validateBusinessFields(param, null);
        PqIcdPath pqIcdPath = icdPayloadAssembler.fromCreate(param);
        validateReferenceIcd(pqIcdPath.getType(), pqIcdPath.getReferenceIcdId(), null);
        boolean saved = this.save(pqIcdPath);
        if (saved) {
            writeIcdTextFile(resolveIcdTxtDir(), pqIcdPath.getName(), pqIcdPath.getJsonStr());
        }
        return saved;
    }

    @Override
    @Transactional
    public boolean addUpstreamIcd(List<PqIcdPathParam.ExternalCreateParam> param) {
        if (CollUtil.isEmpty(param)) {
            return true;
        }
        validateExternalItems(param);
        Set<String> requestStandardIds = collectRequestStandardIds(param);
        Path directory = resolveIcdTxtDir();
        for (PqIcdPathParam.ExternalCreateParam item : param) {
            validateBusinessFields(item, null);
            PqIcdPath current = this.baseMapper.selectById(item.getId());
            if (current != null && !ObjectUtil.equals(current.getState(), DataStateEnum.ENABLE.getCode())) {
                throw new BusinessException(IcdResponseEnum.EXTERNAL_ICD_DELETED_NOT_ALLOW_OVERWRITE);
            }
            PqIcdPath pqIcdPath = icdPayloadAssembler.fromExternalCreate(item, item.getType(), item.getAngle(), item.getUsePhaseIndex());
            String currentId = current == null ? null : current.getId();
            String oldName = current == null ? null : current.getName();
            validateExternalReferenceIcd(pqIcdPath.getType(), pqIcdPath.getReferenceIcdId(), currentId, requestStandardIds);
            boolean saved = current == null ? this.save(pqIcdPath) : this.updateById(pqIcdPath);
            if (!saved) {
                throw new BusinessException(IcdResponseEnum.EXTERNAL_ICD_IMPORT_FAILED);
            }
            if (current == null) {
                writeIcdTextFile(directory, pqIcdPath.getName(), pqIcdPath.getJsonStr());
            } else {
                syncUpdatedIcdTextFile(directory, oldName, pqIcdPath.getName(), pqIcdPath.getJsonStr());
            }
        }
        return true;
    }

    @Override
    @Transactional
    public boolean updateIcd(PqIcdPathParam.UpdateParam param) {
        PqIcdPath current = getActiveEntity(param.getId());
        String oldName = current.getName();
        validateBusinessFields(param, current.getId());
        PqIcdPath pqIcdPath = icdPayloadAssembler.merge(current, param);
        validateReferenceIcd(pqIcdPath.getType(), pqIcdPath.getReferenceIcdId(), current.getId());
        boolean updated = this.updateById(pqIcdPath);
        if (updated) {
            syncUpdatedIcdTextFile(resolveIcdTxtDir(), oldName, pqIcdPath.getName(), pqIcdPath.getJsonStr());
        }
        return updated;
    }

    @Override
    @Transactional
    public boolean deleteIcd(List<String> ids) {
        if (CollUtil.isEmpty(ids)) {
            return true;
        }
        List<PqIcdPath> pqIcdPaths = this.baseMapper.selectByIdsWithoutBlob(ids);
        List<String> standardIds = pqIcdPaths.stream()
                .filter(pqIcdPath -> ObjectUtil.equals(pqIcdPath.getType(), TYPE_STANDARD))
                .map(PqIcdPath::getId)
                .collect(Collectors.toList());
        if (!standardIds.isEmpty()) {
            Long count = this.baseMapper.countActiveReferencesToStandards(standardIds);
            if (ObjectUtil.isNotNull(count) && count > 0) {
                throw new BusinessException(IcdResponseEnum.STANDARD_ICD_IN_USE);
            }
        }

        boolean deleted = this.lambdaUpdate().in(PqIcdPath::getId, ids).set(PqIcdPath::getState, DataStateEnum.DELETED.getCode()).update();
        if (deleted) {
            Path directory = resolveIcdTxtDir();
            for (PqIcdPath pqIcdPath : pqIcdPaths) {
                deleteIcdTextFile(directory, pqIcdPath.getName());
            }
        }
        return deleted;
    }

    @Override
    public PqIcdPath getIcdById(String id) {
        PqIcdPath detail = this.baseMapper.selectDetailById(id);
        if (ObjectUtil.isNull(detail)) {
            throw new BusinessException(IcdResponseEnum.ICD_NOT_FOUND);
        }
        return detail;
    }

    @Override
    public byte[] exportIcd(String id) {
        PqIcdPath detail = this.baseMapper.selectExportById(id);
        if (ObjectUtil.isNull(detail)) {
            throw new BusinessException(IcdResponseEnum.ICD_NOT_FOUND);
        }
        return icdArchiveBuilder.build(detail);
    }

    @Override
    public byte[] exportIcdJson(List<String> ids) {
        if (CollUtil.isEmpty(ids)) {
            throw new BusinessException(IcdResponseEnum.ICD_NOT_FOUND);
        }
        List<PqIcdExportJsonVO> details = this.baseMapper.selectExportJsonByIds(ids);
        if (CollUtil.isEmpty(details)) {
            throw new BusinessException(IcdResponseEnum.ICD_NOT_FOUND);
        }
        for (PqIcdExportJsonVO detail : details) {
            detail.setIcd(encodeIcdFile(detail.getRawIcd()));
            normalizeExportDetail(detail);
        }
        return JSON.toJSONString(details).getBytes(StandardCharsets.UTF_8);
    }

    @Override
    public PqIcdPath getIcdByDevType(String devTypeId) {
        return this.baseMapper.selectIcdByDevType(devTypeId);
    }

    private void validateBusinessFields(PqIcdPathParam param, String currentId) {
        if (ObjectUtil.isNull(param.getResult())) {
            throw new BusinessException(IcdResponseEnum.RESULT_NOT_NULL);
        }
        if (ObjectUtil.equals(param.getResult(), RESULT_NO) && StrUtil.isBlank(param.getMsg())) {
            throw new BusinessException(IcdResponseEnum.MSG_NOT_BLANK);
        }
        if (ObjectUtil.isNull(param.getType())) {
            throw new BusinessException(IcdResponseEnum.TYPE_NOT_NULL);
        }
        if (!ObjectUtil.equals(param.getType(), TYPE_STANDARD)
                && !ObjectUtil.equals(param.getType(), TYPE_NON_STANDARD)
                && !ObjectUtil.equals(param.getType(), TYPE_UPSTREAM_STANDARD)
                && !ObjectUtil.equals(param.getType(), TYPE_UPSTREAM_NON_STANDARD)) {
            throw new BusinessException(IcdResponseEnum.TYPE_VALUE_ERROR);
        }
        if (requiresReferenceIcd(param.getType()) && StrUtil.isBlank(param.getReferenceIcdId())) {
            throw new BusinessException(IcdResponseEnum.REFERENCE_ICD_NOT_BLANK);
        }
        if (StrUtil.isNotBlank(currentId) && StrUtil.equals(currentId, StrUtil.trim(param.getReferenceIcdId()))) {
            throw new BusinessException(IcdResponseEnum.REFERENCE_ICD_INVALID);
        }
    }

    private void validateReferenceIcd(Integer type, String referenceIcdId, String currentId) {
        if (!requiresReferenceIcd(type)) {
            return;
        }
        if (StrUtil.isNotBlank(currentId) && StrUtil.equals(currentId, referenceIcdId)) {
            throw new BusinessException(IcdResponseEnum.REFERENCE_ICD_INVALID);
        }
        long count = this.lambdaQuery()
                .eq(PqIcdPath::getId, referenceIcdId)
                .in(PqIcdPath::getType, TYPE_STANDARD, TYPE_UPSTREAM_STANDARD)
                .eq(PqIcdPath::getState, DataStateEnum.ENABLE.getCode())
                .count();
        if (count <= 0L) {
            throw new BusinessException(IcdResponseEnum.REFERENCE_ICD_INVALID);
        }
    }

    private void validateExternalReferenceIcd(Integer type, String referenceIcdId, String currentId, Set<String> requestStandardIds) {
        if (!requiresReferenceIcd(type)) {
            return;
        }
        String trimmedReferenceId = StrUtil.trim(referenceIcdId);
        if (StrUtil.isNotBlank(currentId) && StrUtil.equals(currentId, trimmedReferenceId)) {
            throw new BusinessException(IcdResponseEnum.REFERENCE_ICD_INVALID);
        }
        if (StrUtil.isNotBlank(trimmedReferenceId) && requestStandardIds.contains(trimmedReferenceId)) {
            return;
        }
        validateReferenceIcd(type, trimmedReferenceId, currentId);
    }

    private Set<String> collectRequestStandardIds(List<PqIcdPathParam.ExternalCreateParam> params) {
        Set<String> ids = new HashSet<>();
        for (PqIcdPathParam.ExternalCreateParam param : params) {
            if (isStandardType(param.getType()) && StrUtil.isNotBlank(param.getId())) {
                ids.add(StrUtil.trim(param.getId()));
            }
        }
        return ids;
    }

    private void validateExternalItems(List<PqIcdPathParam.ExternalCreateParam> params) {
        Set<String> ids = new HashSet<>();
        for (PqIcdPathParam.ExternalCreateParam param : params) {
            if (param == null) {
                throw new BusinessException(IcdResponseEnum.EXTERNAL_ICD_ITEM_NOT_NULL);
            }
            validateExternalItem(param);
            String trimmedId = StrUtil.trim(param.getId());
            if (!ids.add(trimmedId)) {
                throw new BusinessException(IcdResponseEnum.EXTERNAL_ICD_ID_REPEAT);
            }
        }
    }

    private void validateExternalItem(PqIcdPathParam.ExternalCreateParam param) {
        if (StrUtil.isBlank(param.getId())) {
            throw new BusinessException(DetectionValidMessage.ID_NOT_BLANK);
        }
        if (!ReUtil.isMatch(PatternRegex.SYSTEM_ID, StrUtil.trim(param.getId()))) {
            throw new BusinessException(DetectionValidMessage.ID_FORMAT_ERROR);
        }
        if (StrUtil.isBlank(param.getName())) {
            throw new BusinessException(DetectionValidMessage.NAME_NOT_BLANK);
        }
        if (!ReUtil.isMatch(PatternRegex.SCRIPT_NAME_REGEX, StrUtil.trim(param.getName()))) {
            throw new BusinessException(DetectionValidMessage.ICD_NAME_FORMAT_ERROR);
        }
        if (StrUtil.isBlank(param.getIcd())) {
            throw new BusinessException(IcdResponseEnum.ICD_FILE_NOT_NULL);
        }
        if (ObjectUtil.isNull(param.getAngle())) {
            throw new BusinessException(IcdResponseEnum.ANGLE_NOT_NULL);
        }
        if (!ObjectUtil.equals(param.getAngle(), 0) && !ObjectUtil.equals(param.getAngle(), 1)) {
            throw new BusinessException(IcdResponseEnum.ANGLE_VALUE_ERROR);
        }
        if (ObjectUtil.isNull(param.getUsePhaseIndex())) {
            throw new BusinessException(IcdResponseEnum.USE_PHASE_INDEX_NOT_NULL);
        }
        if (!ObjectUtil.equals(param.getUsePhaseIndex(), 0) && !ObjectUtil.equals(param.getUsePhaseIndex(), 1)) {
            throw new BusinessException(IcdResponseEnum.USE_PHASE_INDEX_VALUE_ERROR);
        }
    }

    private boolean requiresReferenceIcd(Integer type) {
        return ObjectUtil.equals(type, TYPE_NON_STANDARD) || ObjectUtil.equals(type, TYPE_UPSTREAM_NON_STANDARD);
    }

    private boolean isStandardType(Integer type) {
        return ObjectUtil.equals(type, TYPE_STANDARD) || ObjectUtil.equals(type, TYPE_UPSTREAM_STANDARD);
    }

    private PqIcdPath getActiveEntity(String id) {
        PqIcdPath current = this.lambdaQuery()
                .eq(PqIcdPath::getId, id)
                .eq(PqIcdPath::getState, DataStateEnum.ENABLE.getCode())
                .one();
        if (ObjectUtil.isNull(current)) {
            throw new BusinessException(IcdResponseEnum.ICD_NOT_FOUND);
        }
        return current;
    }

    Path resolveIcdTxtDir() {
        if (StrUtil.isBlank(icdTxtDir)) {
            throw new BusinessException(IcdResponseEnum.ICD_TXT_SYNC_FAILED);
        }
        return Paths.get(icdTxtDir.trim());
    }

    void writeIcdTextFile(Path directory, String name, String jsonStr) {
        try {
            Files.createDirectories(directory);
            Files.write(resolveTxtFile(directory, name), StrUtil.nullToDefault(jsonStr, "").getBytes(StandardCharsets.UTF_8),
                    StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE);
        } catch (IOException ex) {
            log.error("sync icd txt file failed, name={}", name, ex);
            throw new BusinessException(IcdResponseEnum.ICD_TXT_SYNC_FAILED);
        }
    }

    void syncUpdatedIcdTextFile(Path directory, String oldName, String newName, String jsonStr) {
        if (StrUtil.isNotBlank(oldName) && !StrUtil.equals(oldName, newName)) {
            deleteIcdTextFile(directory, oldName);
        }
        writeIcdTextFile(directory, newName, jsonStr);
    }

    void deleteIcdTextFile(Path directory, String name) {
        try {
            Files.deleteIfExists(resolveTxtFile(directory, name));
        } catch (IOException ex) {
            log.error("delete icd txt file failed, name={}", name, ex);
            throw new BusinessException(IcdResponseEnum.ICD_TXT_SYNC_FAILED);
        }
    }

    private Path resolveTxtFile(Path directory, String name) {
        return directory.resolve(name + ".txt");
    }

    private String encodeIcdFile(byte[] rawValue) {
        if (rawValue == null || rawValue.length == 0) {
            return null;
        }
        return Base64.getEncoder().encodeToString(rawValue);
    }

    private void normalizeExportDetail(PqIcdExportJsonVO detail) {
        detail.setRawIcd(null);
        detail.setAngle(detail.getAngle() == null ? 0 : detail.getAngle());
        detail.setUsePhaseIndex(detail.getUsePhaseIndex() == null ? 1 : detail.getUsePhaseIndex());
        if (isStandardType(detail.getType())) {
            detail.setType(TYPE_UPSTREAM_STANDARD);
            detail.setReferenceIcdId(null);
            return;
        }
        detail.setType(TYPE_UPSTREAM_NON_STANDARD);
    }
}
