package com.writeoff.module.ocr.service;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.writeoff.common.exception.BusinessException;
import com.writeoff.common.exception.ErrorCodes;
import com.writeoff.module.file.service.OssService;
import com.writeoff.module.ocr.config.BaiduOcrProperties;
import com.writeoff.module.ocr.dto.IdCardOcrResponse;
import org.springframework.stereotype.Service;

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Map;

@Service
public class BaiduIdCardOcrService {
    private final BaiduOcrProperties props;
    private final BaiduOcrTokenService tokenService;
    private final OssService ossService;
    private final ObjectMapper objectMapper = new ObjectMapper();

    public BaiduIdCardOcrService(BaiduOcrProperties props, BaiduOcrTokenService tokenService, OssService ossService) {
        this.props = props;
        this.tokenService = tokenService;
        this.ossService = ossService;
    }

    public IdCardOcrResponse recognize(String objectKey, String idCardSide) {
        String key = String.valueOf(objectKey == null ? "" : objectKey).trim();
        String side = String.valueOf(idCardSide == null ? "" : idCardSide).trim().toLowerCase();
        if (key.isEmpty()) {
            throw new BusinessException(ErrorCodes.VALIDATION_ERROR, "objectKey不能为空");
        }
        if (!"front".equals(side) && !"back".equals(side)) {
            throw new BusinessException(ErrorCodes.VALIDATION_ERROR, "idCardSide仅支持front或back");
        }
        if (key.toLowerCase().endsWith(".pdf")) {
            throw new BusinessException(ErrorCodes.VALIDATION_ERROR, "身份证OCR仅支持图片文件");
        }

        byte[] bytes = downloadFromOss(key);
        if (bytes.length <= 0) {
            throw new BusinessException(ErrorCodes.INTERNAL_ERROR, "文件内容为空");
        }
        Map<String, Object> raw = callIdCardOcr(side, bytes);

        IdCardOcrResponse resp = new IdCardOcrResponse();
        resp.setRaw(raw);
        resp.setNormalized(normalize(raw, side));
        return resp;
    }

    private byte[] downloadFromOss(String objectKey) {
        String signedUrl = ossService.generateDownloadUrl(objectKey);
        if (signedUrl == null || signedUrl.trim().isEmpty()) {
            throw new BusinessException(ErrorCodes.INTERNAL_ERROR, "生成OSS下载链接失败");
        }
        try {
            URL url = new URL(signedUrl);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setRequestMethod("GET");
            conn.setConnectTimeout(Math.max(1000, props.getConnectTimeoutMs()));
            conn.setReadTimeout(Math.max(1000, props.getReadTimeoutMs()));
            int status = conn.getResponseCode();
            InputStream is = status >= 200 && status < 300 ? conn.getInputStream() : conn.getErrorStream();
            byte[] data = readAllBytesWithLimit(is, props.getMaxBytes());
            if (status < 200 || status >= 300) {
                throw new BusinessException(ErrorCodes.INTERNAL_ERROR, "拉取OSS文件失败: HTTP " + status);
            }
            return data;
        } catch (BusinessException be) {
            throw be;
        } catch (Exception e) {
            throw new BusinessException(ErrorCodes.INTERNAL_ERROR, "拉取OSS文件异常: " + e.getMessage());
        }
    }

    private Map<String, Object> callIdCardOcr(String side, byte[] bytes) {
        String accessToken = tokenService.getAccessToken();
        String apiUrl = props.getIdCardUrl();
        if (apiUrl == null || apiUrl.trim().isEmpty()) {
            throw new BusinessException(ErrorCodes.INTERNAL_ERROR, "百度OCR idCardUrl未配置");
        }
        String b64 = Base64.getEncoder().encodeToString(bytes);
        try {
            String form = "id_card_side=" + URLEncoder.encode(side, "UTF-8")
                + "&image=" + URLEncoder.encode(b64, "UTF-8");
            String url = apiUrl + "?access_token=" + URLEncoder.encode(accessToken, "UTF-8");
            HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
            conn.setRequestMethod("POST");
            conn.setDoOutput(true);
            conn.setConnectTimeout(Math.max(1000, props.getConnectTimeoutMs()));
            conn.setReadTimeout(Math.max(1000, props.getReadTimeoutMs()));
            conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
            try (OutputStream os = conn.getOutputStream()) {
                os.write(form.getBytes(StandardCharsets.UTF_8));
            }
            int status = conn.getResponseCode();
            InputStream is = status >= 200 && status < 300 ? conn.getInputStream() : conn.getErrorStream();
            String body = new String(readAllBytesWithLimit(is, 2 * 1024 * 1024), StandardCharsets.UTF_8);
            JsonNode json = objectMapper.readTree(body == null ? "" : body);
            if (json.has("error_code")) {
                String errMsg = json.has("error_msg") ? json.get("error_msg").asText() : "unknown";
                String logId = json.has("log_id") ? json.get("log_id").asText() : "";
                throw new BusinessException(ErrorCodes.INTERNAL_ERROR, "身份证OCR识别失败: " + errMsg + (logId.isEmpty() ? "" : " (log_id=" + logId + ")"));
            }
            if (status < 200 || status >= 300) {
                throw new BusinessException(ErrorCodes.INTERNAL_ERROR, "身份证OCR识别失败: HTTP " + status);
            }
            return objectMapper.convertValue(json, new TypeReference<Map<String, Object>>() {
            });
        } catch (BusinessException be) {
            throw be;
        } catch (Exception e) {
            throw new BusinessException(ErrorCodes.INTERNAL_ERROR, "身份证OCR调用异常: " + e.getMessage());
        }
    }

    @SuppressWarnings("unchecked")
    private IdCardOcrResponse.Normalized normalize(Map<String, Object> raw, String side) {
        IdCardOcrResponse.Normalized n = new IdCardOcrResponse.Normalized();
        n.setIdCardSide(side);
        Object wr = raw == null ? null : raw.get("words_result");
        Map<String, Object> words = wr instanceof Map ? (Map<String, Object>) wr : null;
        if (words == null) {
            return n;
        }
        n.setName(extractWord(words, "姓名"));
        n.setIdNo(extractWord(words, "公民身份号码"));
        n.setGender(extractWord(words, "性别"));
        n.setEthnicity(extractWord(words, "民族"));
        n.setBirth(extractWord(words, "出生"));
        n.setAddress(extractWord(words, "住址"));
        n.setIssueAuthority(extractWord(words, "签发机关"));
        n.setSignDate(extractWord(words, "签发日期"));
        n.setExpiryDate(extractWord(words, "失效日期"));
        return n;
    }

    @SuppressWarnings("unchecked")
    private static String extractWord(Map<String, Object> wordsResult, String key) {
        Object v = wordsResult.get(key);
        if (!(v instanceof Map)) {
            return null;
        }
        Object word = ((Map<String, Object>) v).get("words");
        String s = String.valueOf(word == null ? "" : word).trim();
        return s.isEmpty() ? null : s;
    }

    private static byte[] readAllBytesWithLimit(InputStream is, long maxBytes) throws java.io.IOException {
        if (is == null) {
            return new byte[0];
        }
        long limit = maxBytes <= 0 ? 0 : maxBytes;
        try (InputStream in = is; ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
            byte[] buf = new byte[8192];
            long total = 0;
            int n;
            while ((n = in.read(buf)) >= 0) {
                if (n == 0) {
                    continue;
                }
                total += n;
                if (limit > 0 && total > limit) {
                    throw new BusinessException(ErrorCodes.VALIDATION_ERROR, "文件过大，超过限制");
                }
                bos.write(buf, 0, n);
            }
            return bos.toByteArray();
        }
    }
}

