package com.heyu.api.controller.car;

import com.heyu.api.baidu.request.traffic.BLicensePlateRequest;
import com.heyu.api.controller.BaseController;
import com.heyu.api.data.annotation.EbAuthentication;
import com.heyu.api.data.annotation.NotIntercept;
import com.heyu.api.data.constants.ApiConstants;
import com.heyu.api.data.utils.MapUtils;
import com.heyu.api.data.utils.R;
import com.heyu.api.data.utils.StringUtils;
import com.heyu.api.request.car.LicensePlateRecognizeRequest;
import com.heyu.api.resp.car.RecognizeLicensePlateResp;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.List;
import java.util.Map;

/**
 * 车牌识别（百度OCR）
 *
 * 支持识别中国大陆机动车蓝牌、黄牌（单双行）、绿牌、大型新能源（黄绿）、领使馆车牌、警牌、武警牌（单双行）、
 * 军牌（单双行）、港澳出入境车牌、农用车牌、民航车牌、非机动车车牌（北京地区）的地域编号和车牌号，
 * 并能同时识别图像中的多张车牌。
 *
 * 百度文档：https://console.bce.baidu.com/support/#/api?product=AI&project=文字识别&parent=交通场景OCR&api=rest%2F2.0%2Focr%2Fv1%2Flicense_plate&method=post
 */
@Slf4j
@RestController
@RequestMapping("/car/license/plate")
@NotIntercept
@SuppressWarnings("unchecked")
public class RecognizeLicensePlateController extends BaseController {

    private static final String LICENSE_PLATE_URI = "/rest/2.0/ocr/v1/license_plate";

    @PostMapping("/recognize")
    @EbAuthentication(tencent = ApiConstants.TENCENT_AUTH)
    public R<RecognizeLicensePlateResp> recognize(@RequestBody LicensePlateRecognizeRequest request) {
        long start = System.currentTimeMillis();

        long t1 = System.currentTimeMillis();
        BLicensePlateRequest bRequest = toBaiduRequest(request);
        String checkMsg = checkRequest(bRequest);
        if (!isBlank(checkMsg)) {
            return R.error(checkMsg);
        }
        log.info("车牌识别-参数校验及构建请求耗时:{}ms", System.currentTimeMillis() - t1);

        long t2 = System.currentTimeMillis();
        Map<String, Object> data = requestBaidu(LICENSE_PLATE_URI, getContent(bRequest));
        long baiduCost = System.currentTimeMillis() - t2;
        log.info("车牌识别-调用百度OCR接口耗时:{}ms", baiduCost);

        if (data == null) {
            log.info("车牌识别-识别失败, 耗时:{}ms", System.currentTimeMillis() - start);
            return R.error("车牌识别失败");
        }

        long t3 = System.currentTimeMillis();
        List<Map<String, Object>> plates = MapUtils.getByExpr(data, "words_result");
        if (CollectionUtils.isEmpty(plates)) {
            log.info("车牌识别-未识别到车牌信息, 耗时:{}ms", System.currentTimeMillis() - start);
            return R.error("未识别到车牌信息");
        }

        // 取第一张车牌
        Map<String, Object> plate = plates.get(0);
        RecognizeLicensePlateResp resp = new RecognizeLicensePlateResp();
        resp.setPlateNumber((String) plate.get("number"));
        resp.setPlateType(mapPlateColor((String) plate.get("color")));

        // 百度返回字符级置信度列表，取最小值作为整体置信度（保守策略），平均值作为车牌类型置信度
        List<Number> probabilities = (List<Number>) plate.get("probability");
        if (CollectionUtils.isNotEmpty(probabilities)) {
            double minConf = probabilities.stream().mapToDouble(Number::doubleValue).min().orElse(0.0);
            resp.setConfidence((float) minConf);
            double avgConf = probabilities.stream().mapToDouble(Number::doubleValue).average().orElse(0.0);
            resp.setPlateTypeConfidence((float) avgConf);
        }
        log.info("车牌识别-结果映射耗时:{}ms", System.currentTimeMillis() - t3);

        log.info("车牌识别-总耗时:{}ms, 其中第三方(百度OCR):{}ms", System.currentTimeMillis() - start, baiduCost);
        return R.ok().setData(resp);
    }

    private BLicensePlateRequest toBaiduRequest(LicensePlateRecognizeRequest request) {
        BLicensePlateRequest bRequest = new BLicensePlateRequest();
        if (request == null) {
            return bRequest;
        }
        // base64编码后进行urlencode是百度API的要求，因为base64的+/=在form-urlencoded中有特殊含义
        if (StringUtils.isNotBlank(request.getImageBase64())) {
            bRequest.setImageBase64(encodeUrl(request.getImageBase64()));
        }
        // imageUrl 中可能包含 & 等特殊字符，需URL编码后再传给百度API，否则会被解析为form分隔符
        if (StringUtils.isNotBlank(request.getImageUrl())) {
            bRequest.setImageUrl(encodeUrl(request.getImageUrl()));
        }
        // detectComplete和detectRisk在BLicensePlateRequest中无默认值，下方check()要求必须为true/false
        bRequest.setDetectComplete("false");
        bRequest.setDetectRisk("false");
        return bRequest;
    }

    private String checkRequest(BLicensePlateRequest request) {
        if (!hasImage(request)) {
            return "imageUrl和imageBase64不能同时为空";
        }
        if (checkNotTrueFalse(request.getMultiDetect())) {
            return "multiDetect必须为true/false, 是否检测多张车牌，默认为false，当置为true的时候可以对一张图片内的多张车牌进行识别";
        }
        if (checkNotTrueFalse(request.getMultiScale())) {
            return "multiScale必须为true/false, 在高拍等车牌较小的场景下可开启，默认为false";
        }
        if (checkNotTrueFalse(request.getDetectComplete())) {
            return "detectComplete必须为true/false, 是否开启车牌遮挡检测功能";
        }
        if (checkNotTrueFalse(request.getDetectRisk())) {
            return "detectRisk必须为true/false, 是否开启车牌PS检测功能";
        }
        return null;
    }

    private boolean hasImage(BLicensePlateRequest request) {
        return request != null && (!isBlank(request.getImageUrl()) || !isBlank(request.getImageBase64()));
    }

    private String getContent(BLicensePlateRequest request) {
        StringBuffer sb = getImageContent(request);
        if (StringUtils.isNotBlank(request.getMultiDetect())) {
            sb.append("&multi_detect=").append(request.getMultiDetect());
        }
        if (StringUtils.isNotBlank(request.getMultiScale())) {
            sb.append("&multi_scale=").append(request.getMultiScale());
        }
        if (StringUtils.isNotBlank(request.getDetectComplete())) {
            sb.append("&detect_complete=").append(request.getDetectComplete());
        }
        if (StringUtils.isNotBlank(request.getDetectRisk())) {
            sb.append("&detect_risk=").append(request.getDetectRisk());
        }
        return sb.toString();
    }

    private StringBuffer getImageContent(BLicensePlateRequest request) {
        StringBuffer sb = new StringBuffer();
        if (StringUtils.isNotBlank(request.getImageBase64())) {
            sb.append("&image=").append(request.getImageBase64());
        }
        if (StringUtils.isNotBlank(request.getImageUrl())) {
            sb.append("&url=").append(request.getImageUrl());
        }
        return sb;
    }

    private boolean checkNotTrueFalse(String value) {
        if (value == null) {
            return false;
        }
        return !"true".equals(value) && !"false".equals(value);
    }

    private String encodeUrl(String url) {
        try {
            return URLEncoder.encode(url, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            log.error("URL编码失败:{}", url, e);
            return url;
        }
    }

    /**
     * 将百度车牌颜色映射为中文车牌类型
     */
    private String mapPlateColor(String color) {
        if (color == null) {
            return null;
        }
        switch (color.toLowerCase()) {
            case "blue":
                return "小型汽车";
            case "green":
                return "新能源车";
            case "yellow":
                return "大型汽车";
            case "white":
                return "警车";
            case "black":
                return "港澳车";
            default:
                return color;
        }
    }

}
