package org.springblade.thirdparty.ocr.service.impl;

import cn.hutool.core.lang.TypeReference;
import cn.hutool.json.JSONUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.Nullable;
import org.springblade.core.tool.utils.CollectionUtil;
import org.springblade.core.tool.utils.ObjectUtil;
import org.springblade.core.tool.utils.StringUtil;
import org.springblade.thirdparty.ocr.constant.OCRConstant;
import org.springblade.thirdparty.ocr.constant.OCRImageType;
import org.springblade.thirdparty.ocr.convert.AttachmentIndexConvert;
import org.springblade.thirdparty.ocr.exception.OCRException;
import org.springblade.thirdparty.ocr.feign.IOCRClient;
import org.springblade.thirdparty.ocr.feign.IOCROtherClient;
import org.springblade.thirdparty.ocr.pojo.dto.*;
import org.springblade.thirdparty.ocr.pojo.vo.*;
import org.springblade.thirdparty.ocr.service.IOCRService;
import org.springblade.thirdparty.ocr.util.ConvertUtil;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Supplier;

/**
 * @author bfhuange
 * @date 2024/11/6
 */
@Slf4j
@RequiredArgsConstructor
@Service
public class OCRServiceImpl implements IOCRService {
    /**
     *  限制 200，超过转为kg，否则为t
     */
    private static final BigDecimal WEIGHT_LIMIT = BigDecimal.valueOf(200);

    private final IOCRClient client;
    private final IOCROtherClient otherClient;

    @Override
    public TransportCertificateVO recognitionTransportCertificate(CertificateBatchRecognitionDTO param) {
        log.info("ocr 批量识别参数：{}", JSONUtil.toJsonStr(param));
        if (param == null) {
            throw new OCRException("图像链接不能为空");
        }
        if (hasTypedUrls(param)) {
            return convertTransportCertificate(recognitionTransportCertificateWithTypedUrls(param));
        }
        if (CollectionUtils.isEmpty(param.getUrls())) {
            throw new OCRException("图像链接不能为空");
        }
        List<OCRResultVO<OCRBigCommonCardVO>> result = this.getCardResult(
                () -> client.batchRecognition(
                        new OCRBigCommonCardBatchDTO(
                                param.getUrls(),
                                param.getProjectAbbreviation(),
                                param.getDirName()
                        ),
                        param.getCode()
                ),
                "批量磅单卡证识别"
        );
        List<OCRBigCommonCardVO> cards = result.stream()
                .map(OCRResultVO::getResult)
                .toList();
        return convertTransportCertificate(cards);
    }

    @Override
    public TransportCertificateVO recognitionTransportCertificate(CertificateRecognitionDTO param, String code) {
        log.info("ocr 单张识别参数：{}", JSONUtil.toJsonStr(param));
        if (param == null || ObjectUtil.isEmpty(param)) {
            throw new OCRException("凭证识别参数不能为空");
        }
        List<OCRBigCommonCardVO> cards = new ArrayList<>(4);
        if (StringUtil.isNotBlank(param.getPoundUrl())) {
            // 磅单
            OCRBigCommonCardVO card = this.getCardResult(() -> client.poundRecognition(new OCRSingleDTO(param.getPoundUrl(), param.getName()), code), "磅单单独识别");
            if (card != null) {
                cards.add(card);
            }
        }
        if (StringUtil.isNotBlank(param.getDriverLicenseUrl())) {
            // 驾驶证
            OCRBigCommonCardVO card = this.getCardResult(() -> client.singleRecognition(new OCRBigCommonCardDTO(param.getDriverLicenseUrl(), param.getName(),
                    // 驾驶证-正页
                    List.of(OCRImageType.DRIVER_LICENSE_MAIN.getId(),
                            // 驾驶证-副页
                            OCRImageType.DRIVER_LICENSE_SIDE.getId(),
                            // 驾驶证-电子版
                            OCRImageType.DRIVER_LICENSE_ELEC.getId())), code
                    ), "驾驶证识别");
            if (card != null) {
                cards.add(card);
            }
        }
        if (StringUtil.isNotBlank(param.getVehicleLicenseUrl())) {
            // 行驶证
            OCRBigCommonCardVO card = this.getCardResult(() -> client.singleRecognition(new OCRBigCommonCardDTO(param.getVehicleLicenseUrl(), param.getName(),
                    // ⾏驶证-正⻚-挂车
                    List.of(OCRImageType.VEHICLE_LICENSE_MAIN_GUA.getId(),
                            // ⾏驶证-正⻚
                            OCRImageType.VEHICLE_LICENSE_MAIN.getId(),
                            // ⾏驶证-副⻚
                            OCRImageType.VEHICLE_LICENSE_SIDE.getId())), code
            ), "行驶证识别");
            if (card != null) {
                cards.add(card);
            }
        }
        if (StringUtil.isNotBlank(param.getCarFrontUrl())) {
            // 车头
            OCRBigCommonCardVO card = this.getCardResult(() -> client.plateNoRecognition(new OCRSingleDTO(param.getCarFrontUrl(), param.getName()), code), "⻋牌单独识别");
            if (card != null) {
                cards.add(card);
            }
        }
        return CollectionUtil.isEmpty(cards) ? null : convertTransportCertificate(cards);
    }

    /**
     *  获取卡证结果
     * @param resultSupplier
     * @param name
     * @return
     */
    private <T> T getCardResult(Supplier<OCRResultVO<T>> resultSupplier, String name) {
        try {
            OCRResultVO<T> resultVO = resultSupplier.get();
            log.info("调用ocr接口 {} 响应参数：{}", name, JSONUtil.toJsonStr(resultVO));
            if (resultVO != null && OCRConstant.SUCCESS_CODE.equals(resultVO.getCode())) {
                // 成功
                return resultVO.getResult();
            }
            log.error("调用ocr接口 {} 异常响应参数：{}", name, JSONUtil.toJsonStr(resultVO));
            String message = getMessage(resultVO);
            throw new OCRException("调用ocr接口" + name + "异常 " + message);
        } catch (OCRException e) {
            throw e;
        } catch (Exception e) {
            log.error("调用ocr接口 {} 异常", name, e);
            throw new OCRException("调用ocr接口" + name + "异常");
        }
    }

    private List<OCRBigCommonCardVO> recognitionTransportCertificateWithTypedUrls(CertificateBatchRecognitionDTO param) {
        List<OCRBigCommonCardVO> cards = new ArrayList<>(4);
        addPoundRecognitionCards(cards, param);
        addDriverLicenseRecognitionCards(cards, param);
        addVehicleLicenseRecognitionCards(cards, param);
        addCarFrontRecognitionCards(cards, param);
        return cards;
    }

    private void addPoundRecognitionCards(List<OCRBigCommonCardVO> cards, CertificateBatchRecognitionDTO param) {
        for (String url : Optional.ofNullable(param.getPoundUrls()).orElse(List.of())) {
            if (StringUtil.isBlank(url)) {
                continue;
            }
            OCRBigCommonCardVO card = this.getCardResult(
                    () -> client.poundRecognition(
                            new OCRSingleDTO(url, param.getProjectAbbreviation()), param.getCode()
                    ),
                    "磅单单独识别"
            );
            addCard(cards, card);
        }
    }

    private void addDriverLicenseRecognitionCards(List<OCRBigCommonCardVO> cards, CertificateBatchRecognitionDTO param) {
        List<String> driverLicenseUrls = Optional.ofNullable(param.getDriverLicenseUrls()).orElse(List.of());
        for (String url : driverLicenseUrls) {
            if (StringUtil.isBlank(url)) {
                continue;
            }
            OCRBigCommonCardVO card = this.getCardResult(
                    () -> client.singleRecognition(
                            new OCRBigCommonCardDTO(
                                    url,
                                    param.getProjectAbbreviation(),
                                    List.of(
                                            OCRImageType.DRIVER_LICENSE_MAIN.getId(),
                                            OCRImageType.DRIVER_LICENSE_SIDE.getId(),
                                            OCRImageType.DRIVER_LICENSE_ELEC.getId()
                                    )
                            ),
                            param.getCode()
                    ),
                    "驾驶证识别"
            );
            addCard(cards, card);
        }
    }

    private void addVehicleLicenseRecognitionCards(List<OCRBigCommonCardVO> cards, CertificateBatchRecognitionDTO param) {
        List<String> vehicleLicenseUrls = Optional.ofNullable(param.getVehicleLicenseUrls()).orElse(List.of());
        for (String url : vehicleLicenseUrls) {
            if (StringUtil.isBlank(url)) {
                continue;
            }
            OCRBigCommonCardVO card = this.getCardResult(
                    () -> client.singleRecognition(
                            new OCRBigCommonCardDTO(
                                    url,
                                    param.getProjectAbbreviation(),
                                    List.of(
                                            OCRImageType.VEHICLE_LICENSE_MAIN_GUA.getId(),
                                            OCRImageType.VEHICLE_LICENSE_MAIN.getId(),
                                            OCRImageType.VEHICLE_LICENSE_SIDE.getId()
                                    )
                            ),
                            param.getCode()
                    ),
                    "行驶证识别"
            );
            addCard(cards, card);
        }
    }

    private void addCarFrontRecognitionCards(List<OCRBigCommonCardVO> cards, CertificateBatchRecognitionDTO param) {
        for (String url : Optional.ofNullable(param.getCarFrontUrls()).orElse(List.of())) {
            if (StringUtil.isBlank(url)) {
                continue;
            }
            OCRBigCommonCardVO card = this.getCardResult(
                    () -> client.plateNoRecognition(
                            new OCRSingleDTO(url, param.getProjectAbbreviation()), param.getCode()
                    ),
                    "⻋牌单独识别"
            );
            addCard(cards, card);
        }
    }

    private static boolean hasTypedUrls(CertificateBatchRecognitionDTO param) {
        return CollectionUtil.isNotEmpty(param.getPoundUrls())
                || CollectionUtil.isNotEmpty(param.getCarFrontUrls())
                || CollectionUtil.isNotEmpty(param.getDriverLicenseUrls())
                || CollectionUtil.isNotEmpty(param.getVehicleLicenseUrls());
    }

    private static void addCard(List<OCRBigCommonCardVO> cards, OCRBigCommonCardVO card) {
        if (card != null) {
            cards.add(card);
        }
    }

    /**
     *  转换车辆凭证
     * @param cards
     * @return
     */
    private static TransportCertificateVO convertTransportCertificate(List<OCRBigCommonCardVO> cards) {
        TransportCertificateVO result = new TransportCertificateVO();
        if (CollectionUtils.isEmpty(cards)) {
            return result;
        }
        OCRBigCommonCardVO card = null;
        for (int index = 0; index < cards.size(); index++) {
            card = cards.get(index);
            convert(result, card, ConvertUtil::getTransportCertificateSetter, index);
        }
        // 处理重量单位
        handleWeightUnit(result);
        return result;
    }

    /**
     *  处理重量单位
     * @param result
     */
    private static void handleWeightUnit(TransportCertificateVO result) {
        if (result == null) {
            return;
        }
        // 重量超过200，默认kg，否则t
        if (overLimit(result.getGrossWeight()) || overLimit(result.getSkinWeight()) || overLimit(result.getNetWeight())) {
            // 毛重、皮重、净重 超过限制，是kg
            result.setGrossWeight(divideThousand(result.getGrossWeight()));
            result.setSkinWeight(divideThousand(result.getSkinWeight()));
            result.setNetWeight(divideThousand(result.getNetWeight()));
            result.setMiscellaneousWeight(divideThousand(result.getMiscellaneousWeight()));
            result.setRealityWeight(divideThousand(result.getRealityWeight()));
            return;
        }
        String unitMeasure = result.getUnitMeasure();
        if (OCRConstant.UNIT_T.equalsIgnoreCase(unitMeasure) || OCRConstant.UNIT_T_ZH.equals(unitMeasure)) {
            // t或者吨，无需处理
            return;
        }
        if (OCRConstant.UNIT_KG.equalsIgnoreCase(unitMeasure)) {
            // kg，转成吨保留5位小数
            result.setGrossWeight(divideThousand(result.getGrossWeight()));
            result.setSkinWeight(divideThousand(result.getSkinWeight()));
            result.setNetWeight(divideThousand(result.getNetWeight()));
            result.setMiscellaneousWeight(divideThousand(result.getMiscellaneousWeight()));
            result.setRealityWeight(divideThousand(result.getRealityWeight()));
            return;
        }
        if (StringUtil.isNotBlank(unitMeasure)) {
            log.warn("未知的单位类型：{}", unitMeasure);
        }
    }

    /**
     *  是否超过限制
     * @param weight
     * @return
     */
    private static boolean overLimit(BigDecimal weight) {
        return weight != null && weight.compareTo(WEIGHT_LIMIT) > 0;
    }

    /**
     *  除以1000保留5位小数
     * @param num
     * @return
     */
    private static BigDecimal divideThousand(BigDecimal num) {
        if (num == null) {
            return null;
        }
        return num.divide(BigDecimal.valueOf(1000), 5, RoundingMode.HALF_UP);
    }

    @Override
    public IDCardVO recognitionIDCard(List<String> urls) {
        if (CollectionUtils.isEmpty(urls)) {
            throw new OCRException("图像链接不能为空");
        }
        IDCardVO result = new IDCardVO();
        try {
            for (String url : urls) {
                OCRCardBankDetRecDTO param = new OCRCardBankDetRecDTO(url);
//				param.setDataType("IDCard-Main");
                OCRResultVO<OCRBigCommonCardVO> resultVO = otherClient.cardBankDetRec(param);
                if (resultVO != null && OCRConstant.SUCCESS_CODE.equals(resultVO.getCode())) {
                    // 成功
                    OCRBigCommonCardVO card = resultVO.getResult();
                    convertIDCard(result, card);
                    continue;
                }
                log.error("调用ocr接口证件定位+结构化信息提取异常响应参数：{}", JSONUtil.toJsonStr(resultVO));
                String message = getMessage(resultVO);
                throw new OCRException("调用ocr接口证件定位+结构化信息提取异常 " + message);
            }
        } catch (OCRException e) {
            throw e;
        } catch (Exception e) {
            log.error("调用ocr接口证件定位+结构化信息提取异常", e);
            throw new OCRException("调用ocr接口证件定位+结构化信息提取异常");
        }
        return result;
    }

    /**
     *  转换身份证
     * @param result
     * @param card
     * @return
     */
    private void convertIDCard(IDCardVO result, OCRBigCommonCardVO card) {
        convert(result, card, ConvertUtil::getIDCardSetter, null);
    }

    /**
     * 转换
     *
     * @param <T>
     * @param result
     * @param card
     * @param setterGetter
     * @param index
     */
    private static <T> void convert(T result, OCRBigCommonCardVO card, BiFunction<String, String, BiConsumer<T, String>> setterGetter, Integer index) {
        if (result == null || card == null || setterGetter == null) {
            return;
        }

        List<OCRRecognitionResultVO> objects = card.getObjects();
        if (CollectionUtils.isEmpty(objects)) {
            return;
        }
        // 排序类型
        sortByTypeId(objects);
        for (OCRRecognitionResultVO object : objects) {
            OCRStructureVO structured = object.getStructured();
            String typeId = getTypeId(object);
            if (typeId == null) {
                continue;
            }
            // 是否处理数据
            boolean handleData = true;
            if (index != null && result instanceof TransportCertificateVO transportCertificate) {
                // 车辆凭证类型，添加索引到list
                // 添加到默认list要处理数据，添加到其他list，不处理数据
                handleData = AttachmentIndexConvert.getConvertByTypeId(typeId)
                        .addIndex2list(transportCertificate, index);
            }
            if (structured == null || !handleData) {
                // 提取的数据为空或添加到了其他附件，不处理数据了，保持图片和数据一致
                continue;
            }
            List<OCRKeyInfoVO> keyInfos = structured.getKeyInfos();
            if (CollectionUtils.isEmpty(keyInfos)) {
                continue;
            }
            for (OCRKeyInfoVO keyInfo : keyInfos) {
                // 根据typeId和key查找setter，找到了执行
                Optional.ofNullable(setterGetter.apply(typeId, keyInfo.getKey()))
                        .ifPresent(consumer -> consumer.accept(result, keyInfo.getValue()));
            }
        }
    }

    /**
     *  按类型排序
     * @param objects
     */
    private static void sortByTypeId(List<OCRRecognitionResultVO> objects) {
        // 行驶证类型的，把 VehicleLicense-Main 放到第一个，优先取它的数据
        List<String> imageTypeIds = AttachmentIndexConvert.VEHICLE_LICENSE.getImageTypeIds();
        boolean vehicleLicense = objects.stream()
                .map(OCRServiceImpl::getTypeId)
                .filter(Objects::nonNull)
                .anyMatch(imageTypeIds::contains);
        if (vehicleLicense) {
            objects.sort(Comparator.comparing(OCRServiceImpl::getTypeId, Comparator.comparing(typeId -> {
                if (typeId == null) {
                    return Integer.MAX_VALUE;
                }
                int index = imageTypeIds.indexOf(typeId);
                if (index < 0) {
                    return Integer.MAX_VALUE - 1;
                }
                return index;
            })));
        }
    }

    @Nullable
    private static String getTypeId(OCRRecognitionResultVO object) {
        OCRStructureVO structured = object.getStructured();
        String typeId = Optional.ofNullable(structured)
                .map(OCRStructureVO::getTypeId)
                .orElse(null);
        if (typeId == null) {
            // typeId为空，从 imageInfo 中取
            OCRRecognitionImageInfoVO imageInfo = object.getImageInfo();
            typeId = Optional.ofNullable(imageInfo)
                    .map(OCRRecognitionImageInfoVO::getTypeId)
                    .orElse(null);
        }
        return typeId;
    }

    /**
     *  获取错误消息
     * @param resultVO
     * @return
     * @param <T>
     */
    private <T> String getMessage(OCRResultVO<T> resultVO) {
        return resultVO == null ? "" : resultVO.getMessage();
    }

    public static void main(String[] args) {
        String json = "";
        OCRResultVO<List<OCRResultVO<OCRBigCommonCardVO>>> resultVO = JSONUtil.toBean(json, new TypeReference<OCRResultVO<List<OCRResultVO<OCRBigCommonCardVO>>>>() {
        }, true);
        if (resultVO != null && OCRConstant.SUCCESS_CODE.equals(resultVO.getCode())) {
            // 成功
            List<OCRResultVO<OCRBigCommonCardVO>> result = resultVO.getResult();
            List<OCRBigCommonCardVO> cards = result.stream()
                    .map(OCRResultVO::getResult)
                    .toList();
            TransportCertificateVO transportCertificateVO = convertTransportCertificate(cards);
            System.out.println(JSONUtil.toJsonStr(transportCertificateVO));
        }
    }
}
