package com.malk.lianan.controller;

import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSON;
import com.malk.lianan.model.McInvoiceDto;
import com.malk.server.aliwork.YDConf;
import com.malk.server.aliwork.YDParam;
import com.malk.server.common.FilePath;
import com.malk.server.common.McException;
import com.malk.server.common.McR;
import com.malk.server.dingtalk.DDR_New;
import com.malk.service.aliwork.YDClient;
import com.malk.utils.*;
import com.malk.lianan.service.IvYdService;
import com.malk.lianan.service.TXYInvoice;
import com.spire.pdf.PdfCompressionLevel;
import com.spire.pdf.PdfDocument;
import com.spire.pdf.PdfPageBase;
import com.spire.pdf.exporting.PdfImageInfo;
import com.spire.pdf.graphics.PdfBitmap;
import com.tencentcloudapi.common.exception.TencentCloudSDKException;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import net.coobird.thumbnailator.Thumbnails;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.util.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.InputStream;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.net.URL;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 错误抛出与拦截详见CatchException
 */
@Slf4j
@RestController
@RequestMapping("/iv/")
public class IVController {

    @Autowired
    private TXYInvoice txyInvoice;

    @Autowired
    private IvYdService ivYdService;

    @Autowired
    private YDClient ydClient;

    /// 优先获取字段, 新版本接口已支持字段返回
    private String findValue(List<Map<String, String>> infos, String... names) {
        for (String name : names) {
            Optional optional = infos.stream().filter(info -> info.get("Name").equals(name)).findAny();
            if (optional.isPresent()) {
                return String.valueOf(((Map) optional.get()).get("Value"));
            }
        }
        return "";
    }

    // 兼容历史配置, 格式 (谷元)
    private String guyuanNameRepalce(String name) {
        if (name.contains("谷元")) {
            return UtilString.replaceBracketIsWhole(name);
        } else {
            return UtilString.replaceBracketIsSemiangle(name);
        }
    }

    /// url压缩转base64
    @SneakyThrows
    private String imageUrlConvertBase64(String imageUrl) {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        // scale(比例), outputQuality(质量)
        Thumbnails.fromURLs(Arrays.asList(new URL(imageUrl))).scale(0.5f).outputQuality(0.25f).toOutputStream(out);
        InputStream inputStream = new ByteArrayInputStream(out.toByteArray());
        //转换为base64
        byte[] bytes = IOUtils.toByteArray(inputStream);
        return Base64.getEncoder().encodeToString(bytes);
    }

    @Autowired
    private FilePath filePath;

    /// PDF压缩转base64
    @SneakyThrows
    private String pdfUrlConvertBase64(String pdfUrl) {
        String fileName = "tmp_" + new Date().getTime() + ".pdf";
        // 下载文件
        File file = UtilFile.mkdirIfNot(fileName, filePath.getPath().getTmp());
        UtilHttp.doDownload(pdfUrl, file);

        // PDF压缩
        PdfDocument doc = new PdfDocument(); // 创建PdfDocument类的对象
        doc.loadFromFile(file.getAbsolutePath()); // 加载PDF文档
        doc.getFileInfo().setIncrementalUpdate(false); // 禁用增量更新
        doc.setCompressionLevel(PdfCompressionLevel.Normal); // 将压缩级别设置为最佳

        // 遍历文档页面
        for (int i = 0; i < doc.getPages().getCount(); i++) {
            PdfPageBase page = doc.getPages().get(i);  // 获取指定页面
            PdfImageInfo[] images = page.getImagesInfo(); // 获取每个页面的图像信息集合
            // 遍历集合中的所有项目
            if (images != null && images.length > 0)
                for (int j = 0; j < images.length; j++) {
                    PdfImageInfo image = images[j];  // 获取指定图片
                    PdfBitmap bp = new PdfBitmap(image.getImage());
                    bp.setQuality(30); // 设置压缩质量
                    page.replaceImage(j, bp); // 将原始图像替换为压缩图像
                }
        }
        // 将结果文档保存至另一个PDF文档中: 覆盖
        doc.saveToFile(file.getAbsolutePath());
        doc.close();
        // PDF转base64, 无需透出本地文件地址
        String base64 = UtilFile.fileToBase64(file.getAbsolutePath());
        // 删除临时PDF文件
        UtilFile.deleteFile(file.getAbsolutePath());
        return base64;
    }

    // prd 校验发票抬头, 购买方范围
    private void validateBuyer(String BuyerName, String tips) {
//        List<String> corpNames = Arrays.asList(
//                "珠海金魁新能源科技有限公司",
//                "珠海能魁新能源科技有限公司",
//                "珠海创伟新能源有限公司",
//                "河南能辉绿电科技有限公司",
//                "河南省绿色生态新能源科技有限公司",
//                "贵州能辉智慧能源科技有限公司",
//                "上海能辉科技股份有限公司",
//                "浙江数实通信息技术有限公司");
//        McException.assertAccessException(!corpNames.contains(BuyerName), tips + ", 购买方名称不合法!");
    }

    /**
     * 混票识别 [新版本]
     */
    @PostMapping("invoice-iv2")
    McR invoice_iv2(@RequestBody Map<String, String> data) throws TencentCloudSDKException {

        McException.assertParamException_Null(data, "url");
        String image = ydClient.convertTemporaryUrl(data.get("url"),6000);
        log.info("混票识别, 免登地址, {}", image);
        // 非PDF, 且内存大于3M, 压缩后上传
        if (UtilMap.getFloat(data, "size") > 3.0f && !UtilMap.getBoolean(data, "isPdf")) {
            image = imageUrlConvertBase64(image);
        }
        if (UtilMap.getFloat(data, "size") > 6.0f && UtilMap.getBoolean(data, "isPdf")) {
            image = pdfUrlConvertBase64(image);
        }
        List<Map> invoices = (List<Map>) txyInvoice.doRecognizeGeneralInvoice(image).get("MixedInvoiceItems");
        List<String> nos=new ArrayList<>();
        List<McInvoiceDto> result = invoices.stream().map(item -> {
            Map prop = UtilMap.getMap(UtilMap.getMap(item, "SingleInvoiceInfos"), UtilMap.getString(item, "SubType"));
            // 2025.5.12 去除小计金额，并去除发票编号重复数据
            String no=UtilMap.getString(prop, "Number");
            if(nos.contains(no)){
                return null;
            }
            nos.add(no);
            String kind = UtilMap.getString(item, "TypeDescription");
            String invoiceName = UtilMap.getString(item, "SubTypeDescription");
            if (kind.equals("全电发票")) {
                if(invoiceName.contains("铁路电子客票")){
                    kind="火车票";
                } else if(invoiceName.contains("机票行程单")){
                    kind="机票行程单";
                } else if (invoiceName.contains("专用发票")) {
                    kind="全电专用发票";
                }else {
                    kind="全电普通发票";
                }
            }
            if (kind.equals("增值税发票")) {
                kind = invoiceName.contains("增值税专用发票") ? "增值税专用发票" : "增值税普通发票";
                if (invoiceName.contains("增值税电子")) {
                    kind = invoiceName.contains("专用发票") ? "增值税电子专用发票" : "增值税电子普通发票";
                }
            }
            // ppExt: 通用字段定义
            McInvoiceDto invoiceDto = McInvoiceDto.builder()
                    .name(UtilMap.getString(item, "SubTypeDescription"))
                    .kindName(kind)
                    .kind(UtilMap.getInt(item, "Type"))
                    .code(UtilMap.getString(prop, "Code"))
                    .serial(UtilMap.getString(prop, "Number"))
                    .date(UtilString.replaceDateZH_cn(UtilMap.getString(prop, "Date")))
                    .checkCode(UtilMap.getString(prop, "CheckCode"))
                    // ppExt: 多明细行时, 优先取值合计 [全电票返回了subTotal字段, 但值为空] // 2025.5.12 去除小计金额，并去除发票编号重复数据
                    .amount(UtilNumber.setBigDecimal(UtilMap.getString_first(prop,  "Total", "Fare"))) // "SubTotal",
                    .tax(UtilNumber.setBigDecimal(UtilMap.getString_first(prop,  "Tax"))) // "SubTax",
                    .excludingTax(UtilNumber.setBigDecimal(UtilMap.getString(prop, "PretaxAmount")))
                    .buyerName(StringUtils.isBlank(guyuanNameRepalce(UtilMap.getString(prop, "Buyer")))?"":guyuanNameRepalce(UtilMap.getString(prop, "Buyer")))
                    // ppExt: 中央非税未返回税号官方说明: 非税发票理论是没有税号的，图片中属于信用代码
                    .buyerTaxId(StringUtils.isBlank(UtilMap.getString(prop, "BuyerTaxID"))?"": UtilMap.getString(prop, "BuyerTaxID"))
                    .sellerName(guyuanNameRepalce(UtilMap.getString_first(prop, "Seller", "Issuer")))                            // 行程单: 填开单位
                    .sellerTaxId(UtilMap.getString_first(prop, "SellerTaxID", "AgentCode"))                       // 行程单: 销售单位代号
                    .passengerName(UtilMap.getString_first(prop, "Name", "UserName"))                             // 火车票, 行程单
                    // 交通出行
                    .seatType(UtilMap.getString(prop, "Seat"))
                    .departureTime(UtilString.replaceDateZH_cn(UtilMap.getString(prop, "DateGetOn")) + " " + UtilMap.getString(prop, "TimeGetOn"))
                    .departurePort(UtilMap.getString_first(prop, "StationGetOn", "Entrance", "Place"))            // 火车票: 出发车站, 过路过桥费: 入口, 出租车: 发票所在地
                    .arrivePort(UtilMap.getString_first(prop, "StationGetOff", "Exit"))                           // 行程单, 火车票: 到达车站, 过路过桥费: 出口
                    .trainNo(UtilMap.getString_first(prop, "TrainNumber", "LicensePlate"))                        // 火车票: 车次, 出租车: 车牌号
                    .insuranceCosts(UtilNumber.setBigDecimal((UtilMap.getString(prop, "Insurance"))))               // 行程单: 保险费
                    .fuelCosts(UtilNumber.setBigDecimal((UtilMap.getString(prop, "FuelSurcharge"))))                // 行程单: 燃油附加费
                    .constructionCosts(UtilNumber.setBigDecimal((UtilMap.getString(prop, "AirDevelopmentFund"))))   // 行程单: 民航发展基金
                    .build();
            // ppExt: 机票行程单, 行程与座位信息在明细内
            if("火车票".equals(kind)){
                invoiceDto.setTax(calculateTax(invoiceDto.getAmount(), new BigDecimal("0.09"), 2));
                invoiceDto.setExcludingTax(invoiceDto.getAmount().subtract(invoiceDto.getTax()));
            }
            if ("机票行程单".equals(kind)) {
                invoiceDto.setSellerName(UtilMap.getString_first(prop, "Issuer"));
                invoiceDto.setSellerTaxId(UtilMap.getString_first(prop, "Seller"));
                Map flight = (Map) UtilMap.getList(prop, "FlightItems").get(0);
                invoiceDto.setDepartureTime(UtilString.replaceDateZH_cn(UtilMap.getString(item, "DateGetOn")) + " " + UtilMap.getString(prop, "TimeGetOn"));
                invoiceDto.setDeparturePort(UtilMap.getString(flight, "StationGetOn"));
                invoiceDto.setArrivePort(UtilMap.getString(flight, "StationGetOff"));
                invoiceDto.setSeatType(UtilMap.getString(flight, "Seat"));
            }
            if ("出租车发票".equals(item.get("TypeDescription"))) {
                // 上下车时间
                invoiceDto.setDepartureTime(UtilMap.getString(prop, "TimeGetOn") + " ~ " + UtilMap.getString(prop, "TimeGetOff"));
            }
            return invoiceDto;
        }).filter(item -> item!=null).collect(Collectors.toList());
        return McR.success(McInvoiceDto.formatResponse(result));
    }

    /**
     * 计算税额方法
     * @param faceValue 票面金额
     * @param taxRate 税率(如9%传0.09)
     * @param scale 小数位数
     * @return 税额
     */
    public static BigDecimal calculateTax(BigDecimal faceValue, BigDecimal taxRate, int scale) {
        if (faceValue == null || taxRate == null) {
            throw new IllegalArgumentException("参数不能为null");
        }
        BigDecimal denominator = BigDecimal.ONE.add(taxRate);
        return faceValue.divide(denominator, scale, RoundingMode.HALF_UP)
                .multiply(taxRate)
                .setScale(scale, RoundingMode.HALF_UP);
    }

    /**
     * 发票查重, 验真
     */
    @PostMapping("invoice-va")
    McR invoice_va(@RequestBody Map data) {
        McException.assertParamException_Null(data, "param");
        List<McInvoiceDto> invoices = JSON.parseArray(JSON.toJSONString(data.get("param")), McInvoiceDto.class);

        log.info("发票查重, 验真, {}", invoices);
        invoices.forEach(UtilMc.consumerWithIndex((item, index) -> {

            McInvoiceDto dto = (McInvoiceDto) item;
            String invoiceNo = dto.getSerial(); // 唯一标识, 发票号码

            String serial = "第【" + (index + 1) + "】张发票";
//            validateBuyer(dto.getBuyerName(), serial + "有疑问");
            McException.assertAccessException(StringUtils.isBlank(invoiceNo), serial + ", 识别结果为空, 请检查!");
            YDParam ydParam = YDParam.builder()
                    .formUuid("FORM-FE4634E6D01745C7B26DC58D049EAADC6RP4")
                    .searchFieldJson(JSON.toJSONString(UtilMap.map("radioField_liihyrtb, textField_liihyrt8", "否", invoiceNo)))
                    .build();
            List<String> idList = (List<String>) ydClient.queryData(ydParam, YDConf.FORM_QUERY.retrieve_search_form_id).getData();
            if (idList.size() > 0) {
                McException.exceptionAccess(serial + "已存在, 请勿重复提交!");
            }
            List<String> yzType=Arrays.asList("增值税普通发票",
                    "增值税专用发票",
                    "增值税电子专用发票",
                    "增值税电子普通发票",
                    "全电普通发票",
                    "全电专用发票",
                    "机票行程单");
            // prd 仅仅识别 报销 用途的发票
            if(!dto.getType().contains("报销")||!yzType.contains(dto.getKindName())){
                return;
            }
            String serialTips = serial + "有疑问";
            try {
                // ppExt: 识别与验真后抬头对比 [全电票, 新版本识别接口, 返回名称为: 电子发票(普通发票) 不包含全电标识, 发类型为: 全电发票. 注意取值]
                Map rsp = txyInvoice.doVatInvoiceVerifyNew(dto.getKindName(), dto.getCode(), invoiceNo, dto.getDate(), String.valueOf(dto.getAmount()), dto.getCheckCode(), String.valueOf(dto.getExcludingTax()), serialTips,dto.getSellerTaxId());
                Map invoice = (Map) rsp.get("Invoice");
                McException.assertAccessException(!dto.getBuyerName().equals(guyuanNameRepalce(invoice.get("BuyerName").toString())), serialTips + ", 购买方名称不匹配!");
                if(!PublicUtil.isNull(invoice.get("BuyerTaxCode"))){
                    McException.assertAccessException(!dto.getBuyerTaxId().equals(invoice.get("BuyerTaxCode")), serialTips + ", 购买方税号不匹配!");
                }
                if(!dto.getKindName().equals("机票行程单")){
                    McException.assertAccessException(!dto.getSellerName().equals(guyuanNameRepalce(invoice.get("SellerName").toString())), serialTips + ", 销售方名称不匹配!");
                    McException.assertAccessException(!dto.getSellerTaxId().equals(invoice.get("SellerTaxCode")), serialTips + ", 销售方税号不匹配!");
                }
            } catch (TencentCloudSDKException e) {
                log.error(e.getMessage(), e);
                // prd: 上传发票为假发票时，提示：该发票有疑问，请联系财务人员
                String message = e.getMessage();
                // ppExt: 已经是新版本接口, 过滤提示 [官方答复: 提示不会检测您是否使用的是新版，所有的用户都会提示, 忽略即可]
                if (message.contains("温馨提示")) {
                    message = message.split("温馨提示")[0];
                }
                if (message.contains("发票不存在")) {
                    message = "有疑问，请联系财务人员";
                }
                McException.exceptionAccess(serial + message);
            }
        }));
        return McR.success();
    }


    /**
     * 发票状态更新: 服务注册
     */
    @PostMapping("invoice-up")
    McR invoice_va(HttpServletRequest request) {
        Map data = UtilServlet.getParamMap(request);
        log.info("发票状态更新: 服务注册, {}", data);

        String compId = UtilMap.getString(data, "compId");
        String status = UtilMap.getString(data, "status");

        // 读取关联表单
        String formUUid="";
        List<String> formInstanceIds = new ArrayList<>();
        if(compId.equals("selectField_lzs0bpk2")){
            // 采购表单
            formUUid="FORM-B5A7B20013AE4CD09AD87FAB9A3E145FS3P6";
            List<Map> associationData = (List<Map>) JSON.parse(UtilMap.getString(data, "multiAssociation"));
            formInstanceIds.addAll(associationData.stream().map(form -> UtilMap.getString(form, "instanceId")).collect(Collectors.toList()));
        }else{
            formUUid="FORM-FE4634E6D01745C7B26DC58D049EAADC6RP4";
            List<String> associationForm = (List<String>) JSON.parse(UtilMap.getString(data, "multiAssociation"));
            for (String record : associationForm) {
                // 解析关联表单
                List<Map> associationData = (List<Map>) JSON.parse(record);
                formInstanceIds.addAll(associationData.stream().map(form -> UtilMap.getString(form, "instanceId")).collect(Collectors.toList()));
            }
        }
        // 宜搭批量更新
        Map update = UtilMap.map(compId, status);
        if (compId.equals("selectField_liihyrt6")) {
            update.put("radioField_liw7rb2q", "否"); // 提交后, 更新是否退回标识为否
        }

        // prd 9.10 更新报销单, 关联到发票:: ppExt 宜搭服务注册, 提交规则系统默认字段 [详见 YDService]
        ivYdService.operateData(data, update, YDParam.builder()
                .formUuid(formUUid)
                .formInstanceIdList(formInstanceIds)
                .updateFormDataJson(JSON.toJSONString(update))
                .build(), YDConf.FORM_OPERATION.multi_update);

        return McR.success();
    }

    /**
     * 发票状态更新: 退回提交
     */
    @PostMapping("invoice-zy")
    McR invoice_zy(@RequestBody Map data) {
        log.info("发票状态更新: 退回提交, {}", data);

        List<String> pre_ids = (List<String>) data.get("pre_ids"); // 释放修改前
        List<String> cur_ids = (List<String>) data.get("cur_ids"); // 占用修改后

        // [前端调用添加] 退回为监听宜搭dom事件, 先执行接口调用, 才会校验宜搭必填, 过滤无效调用
        if (cur_ids.size() == 0) {
            return McR.success();
        }

        Map pre_update = (Map) data.get("pre_update");
        Map cur_update = (Map) data.get("cur_update");

        // 宜搭批量更新
        ydClient.operateData(YDParam.builder()
                .formUuid("FORM-FE4634E6D01745C7B26DC58D049EAADC6RP4")
                .formInstanceIdList(pre_ids)
                .updateFormDataJson(JSON.toJSONString(pre_update))
                .build(), YDConf.FORM_OPERATION.multi_update);

        ydClient.operateData(YDParam.builder()
                .formUuid("FORM-FE4634E6D01745C7B26DC58D049EAADC6RP4")
                .formInstanceIdList(cur_ids)
                .updateFormDataJson(JSON.toJSONString(cur_update))
                .build(), YDConf.FORM_OPERATION.multi_update);

        return McR.success();
    }

    /**
     * 全局查询(不匹配子表单)
     */
    @PostMapping("validate")
    McR queryAll(HttpServletRequest request) {
        Map<String, ?> param = UtilServlet.getParamMap(request);
        log.info("全局查询(不匹配子表单), {}", param);
        if (ObjectUtil.isNull(param.get("uniques"))) {
            return McR.success();
        }
        McException.assertParamException_Null(param, "uniques", "formUuid", "compId");
        // 容错 - 尾部分号的空格会被输入框忽略
        String[] uniques = String.valueOf(param.get("uniques")).replace("; ", ";").split(";");
        for (String val : uniques) {
            // 查重校验: 单张发票唯一标识 + 审批已通过 / 审批中
            List<Map> conditions = new ArrayList<>();
            Map unique = new HashMap();
            unique.put("key", param.get("compId"));
            unique.put("value", val.split(": ")[1]);
            unique.put("type", "TEXT");
            unique.put("operator", "like");
            unique.put("componentName", "TextField");
            conditions.add(unique);

            Map approve = new HashMap();
            approve.put("key", "processApprovedResult");
            approve.put("value", new String[]{"agree"});
            approve.put("type", "ARRAY");
            approve.put("operator", "in");
            approve.put("componentName", "SelectField");
            conditions.add(approve);
            YDParam ydParam = YDParam.builder()
                    .formUuid(String.valueOf(param.get("formUuid")))
                    .searchCondition(JSON.toJSONString(conditions))
                    .build();
            DDR_New ddr_new = ydClient.queryData(ydParam, YDConf.FORM_QUERY.retrieve_list);
            log.info("审批通过匹配结果, {}, {}", ddr_new.getTotalCount(), ddr_new.getData());
            if (ddr_new.getTotalCount() > 0) {
                return McR.errorAccess("发票已被使用, 请勿重复提交!");
            }

            conditions.remove(approve);
            Map status = new HashMap();
            status.put("key", "processInstanceStatus");
            status.put("value", new String[]{"RUNNING"});
            status.put("type", "ARRAY");
            status.put("operator", "in");
            status.put("componentName", "SelectField");
            conditions.add(status);
            ydParam.setSearchCondition(JSON.toJSONString(conditions));
            ddr_new = ydClient.queryData(ydParam, YDConf.FORM_QUERY.retrieve_list);
            log.info("审批通过匹配结果, {}, {}", ddr_new.getTotalCount(), ddr_new.getData());
            if (ddr_new.getTotalCount() > 0) {
                return McR.errorAccess("发票已在流程中, 请勿重复提交!");
            }
        }
        return McR.success();
    }

    @PostMapping("test")
    McR test() {


//        List<Map> process = (List<Map>) ydClient.queryData(YDParam.builder()
//                .formUuid("FE4634E6D01745C7B26DC58D049EAADC6RP4")
//                .formInstId("FINST-NGA66WA1FV4EB7QJC3OATA3EV8MK35Z9COEMLFR22")
//                .build(), YDConf.FORM_QUERY.retrieve_id).getData();

        List<Map> process = (List<Map>) ydClient.queryData(YDParam.builder()
                .formUuid("FORM-0IA66C71F6NBAETREO8DE9SSN43D3YIZ0AYILC")
                .searchFieldJson(JSON.toJSONString(UtilMap.map("textField_lmewsobs", "Y16668919W4E4FHQ6123ADDHB8XK3S709YEMLXWF")))
                .build(), YDConf.FORM_QUERY.retrieve_search_form).getData();

        return McR.success();
    }

    @GetMapping("test2")
    McR test2() throws TencentCloudSDKException {
        Map map=txyInvoice.doVatInvoiceVerifyNew("增值税普通发票", "144032509110", "13052133", "2025-04-28", "300", "5e8db", "283.02", "","91440300708461136T");
        return McR.success(map);
    }

}
