package com.hotwj.platform.integration.ocr.baidu;

import cn.hutool.core.codec.Base64;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.ReUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.hotwj.platform.config.vehicleType.domain.HotVehicleType;
import com.hotwj.platform.config.vehicleType.mapper.HotVehicleTypeMapper;
import com.hotwj.platform.integration.ocr.OcrStrategy;
import com.hotwj.platform.integration.ocr.baidu.domain.bo.HandwritingRecognizeBo;
import com.hotwj.platform.integration.ocr.baidu.domain.vo.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.service.DictService;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.system.domain.vo.SysOssVo;
import org.dromara.system.service.ISysOssService;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.math.BigDecimal;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

@Slf4j
@Service("baiduOcrStrategy")
@RequiredArgsConstructor
public class BaiduOcrServiceImpl implements BaiduOcrService, OcrStrategy {

    private final BaiduOcrProperties properties;
    private final ISysOssService ossService;
    private final DictService dictService;
    private final HotVehicleTypeMapper vehicleTypeMapper;
    private final BaiduOcrHttpClient httpClient;

    private volatile String cachedToken;
    private volatile long tokenExpireAt;

    @Override
    public String getType() {
        return "baidu";
    }

    @Override
    public BusinessLicenseVo recognizeBusinessLicense(MultipartFile file) {
        return uploadAndRecognize(file, properties.getBusinessLicenseUrl(), null, BusinessLicenseVo.class, this::processBusinessLicense);
    }

    @Override
    public IdCardVo recognizeIdCard(MultipartFile file, String side) {
        if (!"front".equals(side) && !"back".equals(side)) {
            throw new ServiceException("身份证识别类型错误，只能为front或back");
        }
        return uploadAndRecognize(file, properties.getIdCardUrl(), "id_card_side=" + side, IdCardVo.class,
                json -> "front".equals(side) ? processIdCardFront(json) : processIdCardBack(json));
    }

    @Override
    public VehicleLicenseVo recognizeVehicleLicense(MultipartFile file, String side) {
        if (!"front".equals(side) && !"back".equals(side)) {
            throw new ServiceException("行驶证识别类型错误，只能为front或back");
        }
        return uploadAndRecognize(file, properties.getVehicleLicenseUrl(), "vehicle_license_side=" + side, VehicleLicenseVo.class,
                json -> "front".equals(side) ? processVehicleLicenseFront(json) : processVehicleLicenseBack(json));
    }

    @Override
    public DrivingLicenseVo recognizeDrivingLicense(MultipartFile file, String side) {
        if (!"front".equals(side) && !"back".equals(side)) {
            throw new ServiceException("驾驶证识别类型错误，只能为front或back");
        }
        return uploadAndRecognize(file, properties.getDrivingLicenseUrl(), "driving_license_side=" + side, DrivingLicenseVo.class,
                json -> "front".equals(side) ? processDrivingLicenseFront(json) : processDrivingLicenseBack(json));
    }

    @Override
    public HandwritingOcrVo recognizeHandwriting(HandwritingRecognizeBo bo) {
        if (bo == null || StringUtils.isBlank(bo.getImage())) {
            throw new ServiceException("手写体图片不能为空");
        }
        String base64 = normalizeBase64(bo.getImage());
        String token = getAccessToken();
        String requestUrl = properties.getHandwritingUrl() + "?access_token=" + token;
        String body = "image=" + URLEncoder.encode(base64, StandardCharsets.UTF_8)
                + "&detect_direction=false&probability=false&detect_alteration=false";
        JSONObject json = executeBaiduRequest(requestUrl, body, properties.getHandwritingTimeoutMs(), true);
        HandwritingOcrVo vo = new HandwritingOcrVo();
        Integer wordsResultNum = json.getInt("words_result_num", 0);
        vo.setWordsResultNum(wordsResultNum);
        JSONArray wordsResult = json.getJSONArray("words_result");
        List<String> wordsList = new ArrayList<>();
        if (wordsResult != null) {
            for (int i = 0; i < wordsResult.size(); i++) {
                JSONObject item = wordsResult.getJSONObject(i);
                if (item != null) {
                    String words = item.getStr("words");
                    if (StringUtils.isNotBlank(words)) {
                        wordsList.add(words.trim());
                    }
                }
            }
        }
        vo.setWordsList(wordsList);
        String recognizeText = String.join("", wordsList);
        vo.setWords(recognizeText);
        fillMatchResult(vo, recognizeText, bo.getTargetName(), bo.getMatchMode());
        if (Boolean.TRUE.equals(bo.getSaveImage())) {
            vo.setStoredPath(storeImage(base64));
        }
        return vo;
    }

    private <T extends BaiduOcrBaseVo> T uploadAndRecognize(MultipartFile file, String url, String params, Class<T> clazz, OcrProcessor<T> processor) {
        // 1. Upload to OSS
        SysOssVo ossVo;
        try {
            ossVo = ossService.upload(file);
        } catch (Exception e) {
            log.error("文件上传失败", e);
            throw new ServiceException("文件上传失败: " + e.getMessage());
        }

        // 2. Read file content
        String base64;
        try {
            base64 = Base64.encode(file.getBytes());
        } catch (IOException e) {
            throw new ServiceException("读取文件失败");
        }

        // 3. Call Baidu OCR API
        String token = getAccessToken();
        if (StringUtils.isBlank(token)) {
            throw new ServiceException("获取百度API访问令牌失败");
        }

        String requestUrl = url + "?access_token=" + token;
        String body = "image=" + URLEncoder.encode(base64, StandardCharsets.UTF_8);
        if (StringUtils.isNotBlank(params)) {
            body += "&" + params;
        }

        try {
            JSONObject json = executeBaiduRequest(requestUrl, body, 10000, true);
            T result = processor.process(json);
            if (result == null) {
                try {
                    result = clazz.getDeclaredConstructor().newInstance();
                } catch (Exception e) {
                    throw new ServiceException("创建VO失败");
                }
            }

            result.setOssId(ossVo.getOssId().toString());
            result.setUrl(ossVo.getUrl());

            return result;
        } catch (Exception e) {
            log.error("调用百度OCR接口异常", e);
            if (e instanceof ServiceException) {
                throw e;
            }
            throw new ServiceException("识别服务异常");
        }
    }

    private String getAccessToken() {
        long now = System.currentTimeMillis();
        if (StringUtils.isNotBlank(cachedToken) && now < tokenExpireAt) {
            return cachedToken;
        }
        if (StringUtils.isBlank(properties.getApiKey()) || StringUtils.isBlank(properties.getSecretKey())) {
            throw new ServiceException("百度OCR密钥未配置");
        }
        String body = "grant_type=client_credentials"
                + "&client_id=" + URLEncoder.encode(properties.getApiKey(), StandardCharsets.UTF_8)
                + "&client_secret=" + URLEncoder.encode(properties.getSecretKey(), StandardCharsets.UTF_8);
        JSONObject json = executeBaiduRequest(properties.getTokenUrl(), body, properties.getTokenTimeoutMs(), false);
        cachedToken = json.getStr("access_token");
        Integer expiresIn = json.getInt("expires_in", 1800);
        tokenExpireAt = now + Math.max(expiresIn - 120, 60) * 1000L;
        return cachedToken;
    }

    private JSONObject executeBaiduRequest(String url, String body, Integer timeoutMs, boolean allowRetry) {
        int retryTimes = allowRetry ? Math.max(properties.getRetryTimes(), 0) : 0;
        int maxAttempts = retryTimes + 1;
        int attempt = 0;
        while (attempt < maxAttempts) {
            attempt++;
            try {
                long start = System.currentTimeMillis();
                BaiduOcrHttpResponse response = httpClient.postForm(url, body, timeoutMs);
                long cost = System.currentTimeMillis() - start;
                int status = response.getStatusCode();
                String responseBody = StringUtils.defaultString(response.getBody());
                log.info("百度OCR调用耗时={}ms,status={}", cost, status);
                if (status >= 500) {
                    if (attempt < maxAttempts) {
                        sleepBeforeRetry(attempt);
                        continue;
                    }
                    throw new ServiceException("第三方服务异常");
                }
                if (status >= 400) {
                    throw new ServiceException("第三方调用失败(" + status + ")");
                }
                JSONObject json = JSONUtil.parseObj(responseBody);
                if (json.containsKey("error_code")) {
                    int errorCode = json.getInt("error_code", -1);
                    String errorMsg = json.getStr("error_msg");
                    if (errorCode == 110 || errorCode == 111) {
                        cachedToken = null;
                        tokenExpireAt = 0L;
                    }
                    throw mapBaiduError(errorCode, errorMsg);
                }
                return json;
            } catch (ServiceException e) {
                if (attempt < maxAttempts && shouldRetryServiceException(e, allowRetry)) {
                    sleepBeforeRetry(attempt);
                    continue;
                }
                throw e;
            } catch (Exception e) {
                if (attempt < maxAttempts && allowRetry) {
                    sleepBeforeRetry(attempt);
                    continue;
                }
                throw new ServiceException("第三方调用异常");
            }
        }
        throw new ServiceException("第三方调用异常");
    }

    private boolean shouldRetryServiceException(ServiceException e, boolean allowRetry) {
        if (!allowRetry) {
            return false;
        }
        String msg = e.getMessage();
        if (StringUtils.isBlank(msg)) {
            return true;
        }
        return msg.contains("第三方服务异常") || msg.contains("第三方调用异常");
    }

    private ServiceException mapBaiduError(int errorCode, String errorMsg) {
        if (errorCode == 17 || errorCode == 18 || errorCode == 19) {
            return new ServiceException("OCR配额不足或限流");
        }
        if (errorCode == 110 || errorCode == 111) {
            return new ServiceException("OCR鉴权失败");
        }
        return new ServiceException("OCR识别失败:" + StringUtils.defaultString(errorMsg));
    }

    private void sleepBeforeRetry(int attempt) {
        long sleepMs = attempt == 1 ? 200L : 800L;
        try {
            Thread.sleep(sleepMs);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    private String normalizeBase64(String image) {
        String trimmed = image.trim();
        if (trimmed.startsWith("data:image")) {
            int idx = trimmed.indexOf(',');
            if (idx > -1) {
                trimmed = trimmed.substring(idx + 1);
            }
        }
        if (StringUtils.isBlank(trimmed)) {
            throw new ServiceException("手写体图片不能为空");
        }
        return trimmed;
    }

    private void fillMatchResult(HandwritingOcrVo vo, String recognizeText, String targetName, String matchMode) {
        if (StringUtils.isBlank(targetName)) {
            return;
        }
        String normalizedTarget = targetName.replaceAll("\\s+", "");
        String normalizedText = StringUtils.defaultString(recognizeText).replaceAll("\\s+", "");
        String mode = StringUtils.defaultIfBlank(matchMode, "contains").toLowerCase();
        boolean matched;
        if ("strict".equals(mode)) {
            matched = normalizedText.equals(normalizedTarget);
        } else {
            matched = normalizedText.contains(normalizedTarget);
        }
        vo.setMatched(matched);
        vo.setReason(matched ? "匹配成功" : "匹配失败");
    }

    private String storeImage(String base64Image) {
        try {
            byte[] bytes = Base64.decode(base64Image);
            LocalDate today = LocalDate.now();
            String dirName = today.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
            Path dir = Paths.get("upload_file", dirName);
            Files.createDirectories(dir);
            String fileName = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"))
                    + RandomUtil.randomNumbers(6) + ".png";
            Path filePath = dir.resolve(fileName);
            Files.write(filePath, bytes);
            return filePath.toString();
        } catch (Exception e) {
            throw new ServiceException("保存手写体图片失败");
        }
    }

    // --- Processors ---

    private BusinessLicenseVo processBusinessLicense(JSONObject json) {
        BusinessLicenseVo vo = new BusinessLicenseVo();
        JSONObject words = json.getJSONObject("words_result");
        if (words == null) return vo;

        vo.setEnterpriseName(getWords(words, "单位名称"));
        vo.setLegalRepresentative(getWords(words, "法人"));
        vo.setAddress(getWords(words, "地址"));
        vo.setEstablishmentDate(formatDate(getWords(words, "成立日期")));
        vo.setCreditCode(getWords(words, "社会信用代码"));
        String regCap = getWords(words, "注册资本");
        vo.setRegisteredCapital(extractNumber(regCap));

        return vo;
    }

    private IdCardVo processIdCardFront(JSONObject json) {
        IdCardVo vo = new IdCardVo();
        JSONObject words = json.getJSONObject("words_result");
        if (words == null) return vo;

        vo.setName(getWords(words, "姓名"));
        vo.setIdNumber(getWords(words, "公民身份号码"));
        vo.setAddress(getWords(words, "住址"));
        vo.setGender(getWords(words, "性别"));

        String nation = getWords(words, "民族");
        vo.setEthnicity(getNationCode(nation));

        vo.setBirthDate(formatDate(getWords(words, "出生")));

        return vo;
    }

    private IdCardVo processIdCardBack(JSONObject json) {
        IdCardVo vo = new IdCardVo();
        JSONObject words = json.getJSONObject("words_result");
        if (words == null) return vo;

        String expiryDate = getWords(words, "失效日期");
        vo.setExpiryDate(formatDate(expiryDate));

        return vo;
    }

    private VehicleLicenseVo processVehicleLicenseFront(JSONObject json) {
        VehicleLicenseVo vo = new VehicleLicenseVo();
        JSONObject words = json.getJSONObject("words_result");
        if (words == null) return vo;

        vo.setAddress(getWords(words, "住址"));
        vo.setUseCharacter(getWords(words, "使用性质"));
        vo.setEngineNumber(getWords(words, "发动机号码"));
        vo.setIssueDate(formatDate(getWords(words, "发证日期")));
        vo.setModel(getWords(words, "品牌型号"));
        vo.setOwner(getWords(words, "所有人"));
        vo.setRegisterDate(formatDate(getWords(words, "注册日期")));
        vo.setPlateNumber(getWords(words, "号牌号码"));
        vo.setVin(getWords(words, "车辆识别代号"));

        String vehicleType = getWords(words, "车辆类型");
        vo.setVehicleType(convertVehicleType(vehicleType));

        return vo;
    }

    private VehicleLicenseVo processVehicleLicenseBack(JSONObject json) {
        VehicleLicenseVo vo = new VehicleLicenseVo();
        JSONObject words = json.getJSONObject("words_result");
        if (words == null) return vo;

        String dimension = getWords(words, "外廓尺寸");
        if (StrUtil.isNotBlank(dimension)) {
            vo.setOverallDimension(dimension.replace("mm", "").trim());
        }

        String inspectionRecord = getWords(words, "检验记录");
        vo.setInspectionValidDate(extractDate(inspectionRecord));

        vo.setApprovedLoad(extractNumber(getWords(words, "核定载质量")));
        vo.setCurbWeight(extractNumber(getWords(words, "整备质量")));
        vo.setApprovedPassengerCapacity(extractNumber(getWords(words, "核定载人数")));
        vo.setGrossMass(extractNumber(getWords(words, "总质量")));
        vo.setTractionMass(extractNumber(getWords(words, "准牵引总质量")));

        String remarks = getWords(words, "备注");
        vo.setMandatoryScrapDate(extractDate(remarks));

        return vo;
    }

    private DrivingLicenseVo processDrivingLicenseFront(JSONObject json) {
        DrivingLicenseVo vo = new DrivingLicenseVo();
        JSONObject words = json.getJSONObject("words_result");
        if (words == null) return vo;

        vo.setInitialIssueDate(formatDate(getWords(words, "初次领证日期")));
        vo.setEndDate(formatDate(getWords(words, "至")));
        vo.setVehicleClass(getWords(words, "准驾车型"));
        vo.setIssuingAuthority(getWords(words, "发证单位"));
        vo.setLicenseNumber(getWords(words, "证号"));

        return vo;
    }

    private DrivingLicenseVo processDrivingLicenseBack(JSONObject json) {
        DrivingLicenseVo vo = new DrivingLicenseVo();
        JSONObject words = json.getJSONObject("words_result");
        if (words == null) return vo;

        vo.setArchiveNumber(getWords(words, "档案编号"));
        return vo;
    }

    // --- Helpers ---

    private String getWords(JSONObject words, String key) {
        if (words.containsKey(key)) {
            return words.getJSONObject(key).getStr("words");
        }
        return null;
    }

    private String formatDate(String dateStr) {
        if (StringUtils.isBlank(dateStr) || "长期".equals(dateStr)) {
            return dateStr;
        }
        try {
            String clean = dateStr.replaceAll("[年月日]", "-");
            if (clean.endsWith("-")) clean = clean.substring(0, clean.length() - 1);
            // Try parsing with Hutool
            Date date = DateUtil.parse(clean);
            return DateUtil.format(date, DatePattern.NORM_DATE_PATTERN);
        } catch (Exception e) {
            return dateStr;
        }
    }

    private String extractDate(String text) {
        if (StringUtils.isBlank(text)) return null;
        String pattern = "\\d{4}[\\-年]\\d{1,2}[\\-月]?(\\d{1,2}[日]?)?";
        String date = ReUtil.get(pattern, text, 0);
        if (date != null) {
            return formatDate(date);
        }
        return null;
    }

    private BigDecimal extractNumber(String text) {
        if (StringUtils.isBlank(text)) return null;
        String num = ReUtil.get("\\d+(\\.\\d+)?", text, 0);
        if (num != null) {
            return new BigDecimal(num);
        }
        return null;
    }

    private String getNationCode(String nationName) {
        if (StringUtils.isBlank(nationName)) return null;
        String label = nationName.replace("族", "");
        String val = dictService.getDictValue("nation", label, ",");
        if (StringUtils.isBlank(val)) {
            val = dictService.getDictValue("nation", nationName, ",");
        }
        return StringUtils.isNotBlank(val) ? val : nationName;
    }

    private String convertVehicleType(String typeName) {
        if (StringUtils.isBlank(typeName)) return null;
        LambdaQueryWrapper<HotVehicleType> lqw = new LambdaQueryWrapper<>();
        lqw.eq(HotVehicleType::getName, typeName);
        lqw.last("limit 1");
        HotVehicleType type = vehicleTypeMapper.selectOne(lqw);
        return type != null ? type.getName() : typeName;
    }

    @FunctionalInterface
    interface OcrProcessor<T> {
        T process(JSONObject json);
    }
}
