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.BankCardOcrResponse;
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 BaiduBankCardOcrService {
    private final BaiduOcrProperties props;
    private final BaiduOcrTokenService tokenService;
    private final OssService ossService;
    private final ObjectMapper objectMapper = new ObjectMapper();

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

    public BankCardOcrResponse recognize(String objectKey) {
        String key = String.valueOf(objectKey == null ? "" : objectKey).trim();
        if (key.isEmpty()) {
            throw new BusinessException(ErrorCodes.VALIDATION_ERROR, "objectKey不能为空");
        }
        if (key.toLowerCase().endsWith(".pdf")) {
            throw new BusinessException(ErrorCodes.VALIDATION_ERROR, "银行卡OCR仅支持图片文件");
        }
        byte[] bytes = downloadFromOss(key);
        Map<String, Object> raw = callBankCard(bytes);
        BankCardOcrResponse response = new BankCardOcrResponse();
        response.setRaw(raw);
        response.setNormalized(normalize(raw));
        return response;
    }

    private Map<String, Object> callBankCard(byte[] bytes) {
        try {
            String token = tokenService.getAccessToken();
            String apiUrl = props.getBankCardUrl();
            if (apiUrl == null || apiUrl.trim().isEmpty()) {
                throw new BusinessException(ErrorCodes.INTERNAL_ERROR, "百度OCR bankCardUrl未配置");
            }
            String endpoint = apiUrl + "?access_token=" + URLEncoder.encode(token, "UTF-8");
            HttpURLConnection connection = (HttpURLConnection) new URL(endpoint).openConnection();
            connection.setRequestMethod("POST");
            connection.setConnectTimeout(Math.max(1000, props.getConnectTimeoutMs()));
            connection.setReadTimeout(Math.max(1000, props.getReadTimeoutMs()));
            connection.setDoOutput(true);
            connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
            String imageBase64 = Base64.getEncoder().encodeToString(bytes);
            String body = "image=" + URLEncoder.encode(imageBase64, "UTF-8");
            try (OutputStream os = connection.getOutputStream()) {
                os.write(body.getBytes(StandardCharsets.UTF_8));
            }
            int code = connection.getResponseCode();
            InputStream stream = code >= 200 && code < 300 ? connection.getInputStream() : connection.getErrorStream();
            String text = new String(readAllBytesWithLimit(stream, 2 * 1024 * 1024), StandardCharsets.UTF_8);
            JsonNode json = objectMapper.readTree(text == null ? "" : text);
            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 (code < 200 || code >= 300) {
                throw new BusinessException(ErrorCodes.INTERNAL_ERROR, "银行卡OCR识别失败: HTTP " + code);
            }
            return objectMapper.convertValue(json, new TypeReference<Map<String, Object>>() {});
        } catch (Exception e) {
            if (e instanceof BusinessException) {
                throw (BusinessException) e;
            }
            throw new BusinessException(ErrorCodes.INTERNAL_ERROR, "调用百度银行卡OCR失败: " + e.getMessage());
        }
    }

    @SuppressWarnings("unchecked")
    private BankCardOcrResponse.Normalized normalize(Map<String, Object> raw) {
        BankCardOcrResponse.Normalized n = new BankCardOcrResponse.Normalized();
        Object resultObj = raw.get("result");
        if (!(resultObj instanceof Map)) {
            return n;
        }
        Map<String, Object> result = (Map<String, Object>) resultObj;
        n.setBankCardNumber(asText(result.get("bank_card_number")));
        n.setValidDate(asText(result.get("valid_date")));
        n.setBankCardType(asInt(result.get("bank_card_type")));
        n.setBankName(asText(result.get("bank_name")));
        n.setHolderName(asText(result.get("holder_name")));
        return n;
    }

    private static String asText(Object value) {
        return value == null ? null : String.valueOf(value).trim();
    }

    private static Integer asInt(Object value) {
        if (value == null) {
            return null;
        }
        if (value instanceof Number) {
            return ((Number) value).intValue();
        }
        try {
            return Integer.parseInt(String.valueOf(value).trim());
        } catch (Exception ignored) {
            return null;
        }
    }

    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 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[] buffer = new byte[8192];
            long total = 0;
            int n;
            while ((n = in.read(buffer)) >= 0) {
                if (n == 0) {
                    continue;
                }
                total += n;
                if (limit > 0 && total > limit) {
                    throw new BusinessException(ErrorCodes.VALIDATION_ERROR, "文件过大，超过限制");
                }
                bos.write(buffer, 0, n);
            }
            return bos.toByteArray();
        }
    }
}

