package com.ruoyi.jarvis.service.impl;

import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.jarvis.config.TencentDocConfig;
import com.ruoyi.jarvis.domain.JDOrder;
import com.ruoyi.jarvis.service.ITencentDocService;
import com.ruoyi.jarvis.util.TencentDocApiUtil;
import com.ruoyi.jarvis.util.TencentDocDataParser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.text.SimpleDateFormat;
import java.util.List;

/**
 * 腾讯文档服务实现类
 *
 * @author system
 */
@Service
public class TencentDocServiceImpl implements ITencentDocService {

    private static final Logger log = LoggerFactory.getLogger(TencentDocServiceImpl.class);

    @Autowired
    private TencentDocConfig tencentDocConfig;

    @Autowired
    private com.ruoyi.common.core.redis.RedisCache redisCache;

    @Override
    public String getAuthUrl() {
        if (tencentDocConfig == null) {
            throw new RuntimeException("腾讯文档配置未加载，请检查TencentDocConfig是否正确注入");
        }

        String appId = tencentDocConfig.getAppId();
        String redirectUri = tencentDocConfig.getRedirectUri();
        String oauthUrl = tencentDocConfig.getOauthUrl();

        log.debug("获取授权URL - appId: {}, redirectUri: {}, oauthUrl: {}", appId, redirectUri, oauthUrl);

        // 验证配置参数
        if (appId == null || appId.trim().isEmpty()) {
            throw new RuntimeException("腾讯文档应用ID未配置，请检查application-dev.yml中的tencent.doc.app-id配置");
        }
        if (redirectUri == null || redirectUri.trim().isEmpty()) {
            throw new RuntimeException("腾讯文档回调地址未配置，请检查application-dev.yml中的tencent.doc.redirect-uri配置");
        }
        if (oauthUrl == null || oauthUrl.trim().isEmpty()) {
            oauthUrl = "https://docs.qq.com/oauth/v2/authorize"; // 使用默认值
            log.warn("OAuth URL未配置，使用默认值: {}", oauthUrl);
        }

        // 构建授权URL（根据腾讯文档官方文档：https://docs.qq.com/open/document/app/oauth2/authorize.html）
        StringBuilder authUrl = new StringBuilder();
        authUrl.append(oauthUrl);
        authUrl.append("?client_id=").append(appId);
        try {
            // 确保redirectUri不为null后再编码
            String encodedRedirectUri = java.net.URLEncoder.encode(redirectUri, "UTF-8");
            authUrl.append("&redirect_uri=").append(encodedRedirectUri);
        } catch (java.io.UnsupportedEncodingException e) {
            log.error("URL编码失败", e);
            authUrl.append("&redirect_uri=").append(redirectUri);
        } catch (Exception e) {
            log.error("构建授权URL失败", e);
            throw new RuntimeException("构建授权URL失败: " + e.getMessage(), e);
        }
        authUrl.append("&response_type=code");
        authUrl.append("&scope=all");  // 根据官方文档，scope固定为all

        // 添加state参数（用于防CSRF攻击，可选但建议带上）
        String state = java.util.UUID.randomUUID().toString();
        authUrl.append("&state=").append(state);

        String result = authUrl.toString();
        log.info("生成授权URL: {}", result);
        return result;
    }

    @Override
    public JSONObject getAccessTokenByCode(String code) {
        try {
            return TencentDocApiUtil.getAccessToken(
                tencentDocConfig.getAppId(),
                tencentDocConfig.getAppSecret(),
                code,
                tencentDocConfig.getRedirectUri(),
                tencentDocConfig.getTokenUrl()
            );
        } catch (Exception e) {
            log.error("通过授权码获取访问令牌失败", e);
            throw new RuntimeException("获取访问令牌失败: " + e.getMessage(), e);
        }
    }

    @Override
    public JSONObject refreshAccessToken(String refreshToken) {
        try {
            return TencentDocApiUtil.refreshAccessToken(
                tencentDocConfig.getAppId(),
                tencentDocConfig.getAppSecret(),
                refreshToken,
                tencentDocConfig.getRefreshTokenUrl()
            );
        } catch (Exception e) {
            log.error("刷新访问令牌失败", e);
            throw new RuntimeException("刷新访问令牌失败: " + e.getMessage(), e);
        }
    }

    @Override
    public JSONObject uploadLogisticsToSheet(String accessToken, String fileId, String sheetId, List<JDOrder> orders) {
        try {
            if (orders == null || orders.isEmpty()) {
                throw new IllegalArgumentException("订单列表不能为空");
            }

            // 获取用户信息（包含Open-Id）
            // 官方响应格式：{ "ret": 0, "msg": "Succeed", "data": { "openID": "xxx", ... } }
            JSONObject userInfo = TencentDocApiUtil.getUserInfo(accessToken);
            JSONObject data = userInfo.getJSONObject("data");
            if (data == null) {
                throw new RuntimeException("无法获取用户数据，请检查Access Token是否有效");
            }
            String openId = data.getString("openID");  // 注意：官方返回的字段名是 openID（大写ID）
            if (openId == null || openId.isEmpty()) {
                throw new RuntimeException("无法获取Open-Id，请检查Access Token是否有效");
            }

            // 构建要写入的数据（二维数组格式）
            JSONArray values = new JSONArray();
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

            for (JDOrder order : orders) {
                JSONArray row = new JSONArray();
                // 根据表格列顺序添加数据
                // 假设列顺序为：内部单号、订单号、下单时间、型号、地址、物流链接、下单人、付款金额、后返金额、备注
                row.add(order.getRemark() != null ? order.getRemark() : "");
                row.add(order.getOrderId() != null ? order.getOrderId() : "");
                row.add(order.getOrderTime() != null ? sdf.format(order.getOrderTime()) : "");
                row.add(order.getModelNumber() != null ? order.getModelNumber() : "");
                row.add(order.getAddress() != null ? order.getAddress() : "");
                row.add(order.getLogisticsLink() != null ? order.getLogisticsLink() : "");
                row.add(order.getBuyer() != null ? order.getBuyer() : "");
                row.add(order.getPaymentAmount() != null ? order.getPaymentAmount().toString() : "");
                row.add(order.getRebateAmount() != null ? order.getRebateAmount().toString() : "");
                row.add(order.getStatus() != null ? order.getStatus() : "");

                values.add(row);
            }

            // 追加数据到表格
            return TencentDocApiUtil.appendSheetData(
                accessToken,
                tencentDocConfig.getAppId(),
                openId,
                fileId,
                sheetId,
                values,
                tencentDocConfig.getApiBaseUrl()
            );
        } catch (Exception e) {
            log.error("上传物流信息到表格失败", e);
            throw new RuntimeException("上传物流信息失败: " + e.getMessage(), e);
        }
    }

    @Override
    public JSONObject appendLogisticsToSheet(String accessToken, String fileId, String sheetId, Integer startRow, JDOrder order) {
        try {
            if (order == null) {
                throw new IllegalArgumentException("订单信息不能为空");
            }

            log.info("录单自动写入腾讯文档 - fileId: {}, sheetId: {}, startRow: {}, 订单单号: {}",
                fileId, sheetId, startRow, order.getThirdPartyOrderNo());

            // 1. 读取表头（从Redis或配置获取headerRow）
            final String CONFIG_KEY_PREFIX = "tencent:doc:auto:config:";
            Integer headerRowNum = redisCache.getCacheObject(CONFIG_KEY_PREFIX + "headerRow");
            if (headerRowNum == null) {
                headerRowNum = tencentDocConfig.getHeaderRow();
            }

            String headerRange = String.format("A%d:Z%d", headerRowNum, headerRowNum);
            JSONObject headerData = readSheetData(accessToken, fileId, sheetId, headerRange);

            if (headerData == null || !headerData.containsKey("values")) {
                throw new RuntimeException("无法读取表头数据");
            }

            JSONArray headerValues = headerData.getJSONArray("values");
            if (headerValues == null || headerValues.isEmpty()) {
                throw new RuntimeException("表头数据为空");
            }

            JSONArray headerCells = headerValues.getJSONArray(0);

            // 2. 识别列位置（根据表头）
            Integer dateColumn = null;
            Integer companyColumn = null;
            Integer jdPlaceOrderNoColumn = null;
            Integer orderNoColumn = null;
            Integer modelColumn = null;
            Integer quantityColumn = null;
            Integer nameColumn = null;
            Integer phoneColumn = null;
            Integer addressColumn = null;
            Integer priceColumn = null;
            Integer remarkColumn = null;
            Integer arrangedColumn = null;  // 是否安排列
            Integer logisticsColumn = null;

            for (int i = 0; i < headerCells.size(); i++) {
                String cellText = headerCells.getString(i);
                if (cellText == null) {
                    continue;
                }
                if (jdPlaceOrderNoColumn == null && TencentDocDataParser.isJdPlaceOrderNoHeader(cellText)) {
                    jdPlaceOrderNoColumn = i;
                } else if (cellText.contains("日期")) {
                    dateColumn = i;
                } else if (cellText.contains("公司")) {
                    companyColumn = i;
                } else if (TencentDocDataParser.headerEquals(cellText, "单号")) {
                    orderNoColumn = i;
                } else if (orderNoColumn == null && TencentDocDataParser.headerEquals(cellText, "客户单号")) {
                    orderNoColumn = i;
                } else if (orderNoColumn == null && TencentDocDataParser.headerEquals(cellText, "第三方单号")) {
                    orderNoColumn = i;
                } else if (cellText.contains("型号")) {
                    modelColumn = i;
                } else if (cellText.contains("数量")) {
                    quantityColumn = i;
                } else if (cellText.contains("姓名")) {
                    nameColumn = i;
                } else if (cellText.contains("电话")) {
                    phoneColumn = i;
                } else if (cellText.contains("地址")) {
                    addressColumn = i;
                } else if (cellText.contains("价格")) {
                    priceColumn = i;
                } else if (TencentDocDataParser.headerEquals(cellText, "备注")) {
                    remarkColumn = i;
                } else if (TencentDocDataParser.headerEquals(cellText, "是否安排")) {
                    arrangedColumn = i;
                } else if (TencentDocDataParser.headerEquals(cellText, "物流单号")) {
                    logisticsColumn = i;
                } else if (logisticsColumn == null && TencentDocDataParser.headerEquals(cellText, "物流链接")) {
                    logisticsColumn = i;
                }
            }

            if (orderNoColumn == null) {
                throw new RuntimeException("未找到「单号」「客户单号」或「第三方单号」列，请检查表头配置");
            }

            log.info("表头识别完成 - 单号列: {}, 京东下单订单号列: {}, 物流列: {}", orderNoColumn, jdPlaceOrderNoColumn, logisticsColumn);

            // 3. 读取数据区域，查找第一个空行（单号列为空）
            String dataRange = String.format("A%d:Z%d", startRow, startRow + 999);
            JSONObject sheetData = readSheetData(accessToken, fileId, sheetId, dataRange);

            if (sheetData == null || !sheetData.containsKey("values")) {
                throw new RuntimeException("无法读取数据区域");
            }

            JSONArray dataRows = sheetData.getJSONArray("values");
            int targetRow = -1;

            // 查找第一个单号列为空的行
            if (dataRows == null || dataRows.isEmpty()) {
                // 数据区域完全为空，使用startRow
                targetRow = startRow;
            } else {
                for (int i = 0; i < dataRows.size(); i++) {
                    JSONArray row = dataRows.getJSONArray(i);
                    if (row == null || row.size() <= orderNoColumn) {
                        targetRow = startRow + i;
                        break;
                    }
                    String orderNo = row.getString(orderNoColumn);
                    if (orderNo == null || orderNo.trim().isEmpty()) {
                        targetRow = startRow + i;
                        break;
                    }
                }

                // 如果没找到空行，追加到数据区域末尾
                if (targetRow == -1) {
                    targetRow = startRow + dataRows.size();
                }
            }

            log.info("找到空行位置：第 {} 行", targetRow);

            // 4. 构建要写入的数据（按表头列顺序）
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyMMdd");
            String today = dateFormat.format(new java.util.Date());

            JSONArray requests = new JSONArray();
            int rowIndex = targetRow - 1; // 转为0索引

            // 写入各列数据
            if (dateColumn != null) {
                requests.add(buildUpdateRequest(sheetId, rowIndex, dateColumn, today, false));
            }
            if (companyColumn != null && order.getDistributionMark() != null) {
                requests.add(buildUpdateRequest(sheetId, rowIndex, companyColumn, order.getDistributionMark(), false));
            }
            if (orderNoColumn != null && order.getThirdPartyOrderNo() != null) {
                requests.add(buildUpdateRequest(sheetId, rowIndex, orderNoColumn, order.getThirdPartyOrderNo(), false));
            }
            if (jdPlaceOrderNoColumn != null && order.getOrderId() != null && !order.getOrderId().trim().isEmpty()) {
                requests.add(buildUpdateRequest(sheetId, rowIndex, jdPlaceOrderNoColumn, order.getOrderId().trim(), false));
            }
            if (modelColumn != null && order.getModelNumber() != null) {
                requests.add(buildUpdateRequest(sheetId, rowIndex, modelColumn, order.getModelNumber(), false));
            }
            // 数量列 - JDOrder 没有 quantity 字段，暂时跳过
            // if (quantityColumn != null) { ... }

            if (nameColumn != null && order.getBuyer() != null) {
                requests.add(buildUpdateRequest(sheetId, rowIndex, nameColumn, order.getBuyer(), false));
            }
            // 电话列 - JDOrder 没有 phone 字段，暂时跳过
            // if (phoneColumn != null) { ... }

            if (addressColumn != null && order.getAddress() != null) {
                requests.add(buildUpdateRequest(sheetId, rowIndex, addressColumn, order.getAddress(), false));
            }
            if (priceColumn != null && order.getPaymentAmount() != null) {
                requests.add(buildUpdateRequest(sheetId, rowIndex, priceColumn, order.getPaymentAmount().toString(), false));
            }
            // 备注列固定填写"有货"
            if (remarkColumn != null) {
                requests.add(buildUpdateRequest(sheetId, rowIndex, remarkColumn, "有货", false));
            }
            // 是否安排列固定填写"2"
            if (arrangedColumn != null) {
                requests.add(buildUpdateRequest(sheetId, rowIndex, arrangedColumn, "2", false));
            }
            if (logisticsColumn != null && order.getLogisticsLink() != null && !order.getLogisticsLink().isEmpty()) {
                requests.add(buildUpdateRequest(sheetId, rowIndex, logisticsColumn, order.getLogisticsLink(), true)); // 超链接
            }

            // 5. 使用 batchUpdate 写入
            if (requests.isEmpty()) {
                throw new RuntimeException("没有数据可以写入");
            }

            JSONObject batchUpdateBody = new JSONObject();
            batchUpdateBody.put("requests", requests);

            JSONObject result = batchUpdate(accessToken, fileId, batchUpdateBody);

            log.info("✓ 订单成功写入腾讯文档 - 行: {}, 单号: {}", targetRow, order.getThirdPartyOrderNo());

            JSONObject response = new JSONObject();
            response.put("row", targetRow);
            response.put("orderNo", order.getThirdPartyOrderNo());
            response.put("success", true);

            return response;
        } catch (Exception e) {
            log.error("追加物流信息到表格失败", e);
            throw new RuntimeException("追加物流信息失败: " + e.getMessage(), e);
        }
    }

    /**
     * 构建单元格更新请求
     */
    private JSONObject buildUpdateRequest(String sheetId, int rowIndex, int columnIndex, String value, boolean isLink) {
        JSONObject updateRangeRequest = new JSONObject();
        updateRangeRequest.put("sheetId", sheetId);

        JSONObject gridData = new JSONObject();
        gridData.put("startRow", rowIndex);
        gridData.put("startColumn", columnIndex);

        JSONArray rows = new JSONArray();
        JSONObject rowData = new JSONObject();
        JSONArray cellValues = new JSONArray();

        JSONObject cellData = new JSONObject();
        JSONObject cellValue = new JSONObject();

        if (isLink) {
            JSONObject link = new JSONObject();
            link.put("url", value);
            link.put("text", value);
            cellValue.put("link", link);
        } else {
            cellValue.put("text", value);
        }

        cellData.put("cellValue", cellValue);
        cellValues.add(cellData);

        rowData.put("values", cellValues);
        rows.add(rowData);
        gridData.put("rows", rows);

        updateRangeRequest.put("gridData", gridData);

        JSONObject request = new JSONObject();
        request.put("updateRangeRequest", updateRangeRequest);

        return request;
    }

    @Override
    public JSONObject readSheetData(String accessToken, String fileId, String sheetId, String range) {
        try {
            log.info("Service层 - 开始读取表格数据: fileId={}, sheetId={}, range={}", fileId, sheetId, range);

            // 获取用户信息（包含Open-Id）
            // 官方响应格式：{ "ret": 0, "msg": "Succeed", "data": { "openID": "xxx", ... } }
            log.debug("正在获取用户信息...");
            JSONObject userInfo = TencentDocApiUtil.getUserInfo(accessToken);
            log.debug("用户信息响应: {}", userInfo != null ? userInfo.toJSONString() : "null");

            if (userInfo == null) {
                throw new RuntimeException("getUserInfo 返回 null，Access Token 可能无效");
            }

            JSONObject data = userInfo.getJSONObject("data");
            if (data == null) {
                log.error("用户信息响应中没有 data 字段，完整响应: {}", userInfo.toJSONString());
                throw new RuntimeException("无法获取用户数据，请检查Access Token是否有效。响应: " + userInfo.toJSONString());
            }

            String openId = data.getString("openID");  // 注意：官方返回的字段名是 openID（大写ID）
            if (openId == null || openId.isEmpty()) {
                log.error("data 对象中没有 openID 字段，data内容: {}", data.toJSONString());
                throw new RuntimeException("无法获取Open-Id，请检查Access Token是否有效。data: " + data.toJSONString());
            }

            log.info("成功获取 Open ID: {}", openId);
            log.info("准备调用API - appId: {}, apiBaseUrl: {}", tencentDocConfig.getAppId(), tencentDocConfig.getApiBaseUrl());

            JSONObject result = TencentDocApiUtil.readSheetData(
                accessToken,
                tencentDocConfig.getAppId(),
                openId,
                fileId,
                sheetId,
                range,
                tencentDocConfig.getApiBaseUrl()
            );

            //log.info("API调用成功，原始返回结果: {}", result != null ? result.toJSONString() : "null");

            // 检查API响应中的错误码
            // 根据官方文档，成功响应包含 ret=0，错误响应包含 code!=0
            // 参考：https://docs.qq.com/open/document/app/openapi/v3/sheet/get/get_range.html
            if (result != null) {
                // 检查错误码（code字段）
                if (result.containsKey("code")) {
                    Integer code = result.getInteger("code");
                    if (code != null && code != 0) {
                        String message = result.getString("message");
                        log.error("腾讯文档API返回错误 - code: {}, message: {}", code, message);
                        throw new RuntimeException("腾讯文档API错误: " + message + " (code: " + code + ")");
                    }
                }
                // 检查业务返回码（ret字段）
                if (result.containsKey("ret")) {
                    Integer ret = result.getInteger("ret");
                    if (ret != null && ret != 0) {
                        String msg = result.getString("msg");
                        log.error("腾讯文档API业务错误 - ret: {}, msg: {}", ret, msg);
                        throw new RuntimeException("腾讯文档API业务错误: " + msg + " (ret: " + ret + ")");
                    }
                }
            }

            // 解析数据为统一的简单格式
            JSONArray parsedValues = TencentDocDataParser.parseToSimpleArray(result);
            log.info("解析后的数据行数: {}", parsedValues != null ? parsedValues.size() : 0);
            if (parsedValues != null && !parsedValues.isEmpty()) {
                TencentDocDataParser.printDataStructure(result, 3);
            }

            // 返回包含简化格式的响应
            JSONObject response = new JSONObject();
            response.put("values", parsedValues);
            response.put("_原始数据", result);  // 保留原始数据供调试

            return response;

        } catch (Exception e) {
            log.error("读取表格数据失败 - fileId: {}, sheetId: {}, range: {}", fileId, sheetId, range, e);
            throw new RuntimeException("读取表格数据失败: " + e.getMessage(), e);
        }
    }

    @Override
    public JSONObject writeSheetData(String accessToken, String fileId, String sheetId, String range, Object values) {
        try {
            // 获取用户信息（包含Open-Id）
            // 官方响应格式：{ "ret": 0, "msg": "Succeed", "data": { "openID": "xxx", ... } }
            JSONObject userInfo = TencentDocApiUtil.getUserInfo(accessToken);
            JSONObject data = userInfo.getJSONObject("data");
            if (data == null) {
                throw new RuntimeException("无法获取用户数据，请检查Access Token是否有效");
            }
            String openId = data.getString("openID");  // 注意：官方返回的字段名是 openID（大写ID）
            if (openId == null || openId.isEmpty()) {
                throw new RuntimeException("无法获取Open-Id，请检查Access Token是否有效");
            }

            return TencentDocApiUtil.writeSheetData(
                accessToken,
                tencentDocConfig.getAppId(),
                openId,
                fileId,
                sheetId,
                range,
                values,
                tencentDocConfig.getApiBaseUrl()
            );
        } catch (Exception e) {
            log.error("写入表格数据失败", e);
            throw new RuntimeException("写入表格数据失败: " + e.getMessage(), e);
        }
    }

    @Override
    public JSONObject getFileInfo(String accessToken, String fileId) {
        try {
            // 获取用户信息（包含Open-Id）
            // 官方响应格式：{ "ret": 0, "msg": "Succeed", "data": { "openID": "xxx", ... } }
            JSONObject userInfo = TencentDocApiUtil.getUserInfo(accessToken);
            JSONObject data = userInfo.getJSONObject("data");
            if (data == null) {
                throw new RuntimeException("无法获取用户数据，请检查Access Token是否有效");
            }
            String openId = data.getString("openID");  // 注意：官方返回的字段名是 openID（大写ID）
            if (openId == null || openId.isEmpty()) {
                throw new RuntimeException("无法获取Open-Id，请检查Access Token是否有效");
            }

            return TencentDocApiUtil.getFileInfo(
                accessToken,
                tencentDocConfig.getAppId(),
                openId,
                fileId,
                tencentDocConfig.getApiBaseUrl()
            );
        } catch (Exception e) {
            log.error("获取文件信息失败", e);
            throw new RuntimeException("获取文件信息失败: " + e.getMessage(), e);
        }
    }

    @Override
    public JSONObject getSheetList(String accessToken, String fileId) {
        try {
            // 获取用户信息（包含Open-Id）
            // 官方响应格式：{ "ret": 0, "msg": "Succeed", "data": { "openID": "xxx", ... } }
            JSONObject userInfo = TencentDocApiUtil.getUserInfo(accessToken);
            JSONObject data = userInfo.getJSONObject("data");
            if (data == null) {
                throw new RuntimeException("无法获取用户数据，请检查Access Token是否有效");
            }
            String openId = data.getString("openID");  // 注意：官方返回的字段名是 openID（大写ID）
            if (openId == null || openId.isEmpty()) {
                throw new RuntimeException("无法获取Open-Id，请检查Access Token是否有效");
            }

            return TencentDocApiUtil.getSheetList(
                accessToken,
                tencentDocConfig.getAppId(),
                openId,
                fileId,
                tencentDocConfig.getApiBaseUrl()
            );
        } catch (Exception e) {
            log.error("获取工作表列表失败", e);
            throw new RuntimeException("获取工作表列表失败: " + e.getMessage(), e);
        }
    }

    @Override
    public int getSheetRowTotal(String accessToken, String fileId, String sheetId) {
        if (accessToken == null || fileId == null || sheetId == null) {
            return 0;
        }
        try {
            JSONObject result = getFileInfo(accessToken, fileId);
            if (result == null) {
                return 0;
            }
            // 兼容多种返回结构：data.properties[]、properties[]、sheets[].properties
            com.alibaba.fastjson2.JSONArray list = null;
            if (result.containsKey("data")) {
                com.alibaba.fastjson2.JSONObject data = result.getJSONObject("data");
                if (data != null && data.containsKey("properties")) {
                    list = data.getJSONArray("properties");
                } else if (data != null && data.containsKey("sheets")) {
                    list = data.getJSONArray("sheets");
                }
            }
            if (list == null && result.containsKey("properties")) {
                list = result.getJSONArray("properties");
            }
            if (list == null && result.containsKey("sheets")) {
                list = result.getJSONArray("sheets");
            }
            if (list == null) {
                log.debug("getSheetRowTotal 未找到 properties/sheets 列表 - fileId: {}, sheetId: {}, resultKeys: {}",
                    fileId, sheetId, result != null ? result.keySet() : "null");
                return 0;
            }
            for (int i = 0; i < list.size(); i++) {
                com.alibaba.fastjson2.JSONObject item = list.getJSONObject(i);
                com.alibaba.fastjson2.JSONObject props = item.containsKey("properties") ? item.getJSONObject("properties") : item;
                if (props == null) {
                    continue;
                }
                String sid = props.getString("sheetId");
                if (sid == null || !sheetId.equals(sid)) {
                    continue;
                }
                // 优先使用 rowCount（表格实际有数据的行数，用于操作范围），其次 rowTotal（表格容量上限）
                if (props.containsKey("rowCount")) {
                    return Math.max(0, props.getIntValue("rowCount"));
                }
                if (props.containsKey("rowTotal")) {
                    return Math.max(0, props.getIntValue("rowTotal"));
                }
                if (props.containsKey("row_total")) {
                    return Math.max(0, props.getIntValue("row_total"));
                }
                log.debug("getSheetRowTotal 找到 sheetId 但无 rowCount/rowTotal - fileId: {}, sheetId: {}, propsKeys: {}",
                    fileId, sheetId, props.keySet());
            }
            return 0;
        } catch (Exception e) {
            log.warn("获取工作表 rowTotal 失败 - fileId: {}, sheetId: {}", fileId, sheetId, e);
            return 0;
        }
    }

    @Override
    public JSONObject getUserInfo(String accessToken) {
        try {
            return TencentDocApiUtil.getUserInfo(accessToken);
        } catch (Exception e) {
            log.error("获取用户信息失败", e);
            throw new RuntimeException("获取用户信息失败: " + e.getMessage(), e);
        }
    }

    @Override
    public JSONObject batchUpdate(String accessToken, String fileId, JSONObject requestBody) {
        try {
            // 获取用户信息（包含Open-Id）
            JSONObject userInfo = TencentDocApiUtil.getUserInfo(accessToken);
            JSONObject data = userInfo.getJSONObject("data");
            if (data == null) {
                throw new RuntimeException("无法获取用户数据，请检查Access Token是否有效");
            }
            String openId = data.getString("openID");
            if (openId == null || openId.isEmpty()) {
                throw new RuntimeException("无法获取Open-Id，请检查Access Token是否有效");
            }

            return TencentDocApiUtil.batchUpdate(
                accessToken,
                tencentDocConfig.getAppId(),
                openId,
                fileId,
                requestBody,
                tencentDocConfig.getApiBaseUrl()
            );
        } catch (Exception e) {
            log.error("批量更新表格失败 - fileId: {}", fileId, e);
            throw new RuntimeException("批量更新表格失败: " + e.getMessage(), e);
        }
    }
}

