package com.rj.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import cn.felord.DefaultAgent;
import cn.felord.WeComTokenCacheable;
import cn.felord.api.ExternalContactUserApi;
import cn.felord.api.WorkWeChatApi;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.rj.common.DictItemConstants;
import com.rj.common.PasswordUtil;
import com.rj.common.QiWeiApiConstants;
import com.rj.dto.QiWeiConfig;
import com.rj.entity.DictItem;
import com.rj.entity.CustomerManagement;
import com.rj.entity.QweiUser;
import com.rj.mapper.DictItemMapper;
import com.rj.mapper.QweiUserMapper;
import com.rj.service.ICustomerManagementService;
import com.rj.service.IQweiUserService;
import com.rj.tenant.TenantContextHolder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import okhttp3.logging.HttpLoggingInterceptor;

import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

@Service
public class QweiUserServiceImpl extends ServiceImpl<QweiUserMapper, QweiUser> implements IQweiUserService {

    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
    private static final HttpClient HTTP_CLIENT = HttpClient.newHttpClient();

    @Autowired
    private DictItemMapper dictItemMapper;
    @Autowired
    private ICustomerManagementService customerManagementService;

    @Override
    public Map<String, Object> add(QweiUser entity) {
        Map<String, Object> result = new HashMap<>();
        try {
            String tenantId = resolveTenantId(entity.getTenantId());
            if (entity.getUserid() == null || entity.getUserid().trim().isEmpty()) {
                result.put("success", false);
                result.put("message", "userid不能为空");
                return result;
            }
            LambdaQueryWrapper<QweiUser> dup = new LambdaQueryWrapper<QweiUser>()
                    .eq(QweiUser::getUserid, entity.getUserid().trim());
            if (this.count(dup) > 0) {
                result.put("success", false);
                result.put("message", "userid已存在");
                return result;
            }

            entity.setId(UUID.randomUUID().toString());
            entity.setTenantId(tenantId);
            entity.setUserid(entity.getUserid().trim());
            if (entity.getIsDeleted() == null) {
                entity.setIsDeleted(0);
            }
            LocalDateTime now = LocalDateTime.now();
            entity.setCreatedAt(now);
            entity.setUpdatedAt(now);
            boolean ok = this.save(entity);
            result.put("success", ok);
            result.put("message", ok ? "添加成功" : "添加失败");
            if (ok) {
                result.put("data", entity);
            }
            return result;
        } catch (Exception e) {
            result.put("success", false);
            result.put("message", "添加异常：" + e.getMessage());
            return result;
        }
    }

    @Override
    public Map<String, Object> update(QweiUser entity) {
        Map<String, Object> result = new HashMap<>();
        try {
            if (entity.getId() == null || entity.getId().trim().isEmpty()) {
                result.put("success", false);
                result.put("message", "id不能为空");
                return result;
            }
            // 更新时不允许把 tenantId 改掉；若传了就忽略
            entity.setTenantId(null);
            if (entity.getUserid() != null) {
                entity.setUserid(entity.getUserid().trim());
            }
            entity.setUpdatedAt(LocalDateTime.now());
            boolean ok = this.updateById(entity);
            result.put("success", ok);
            result.put("message", ok ? "更新成功" : "更新失败");
            if (ok) {
                result.put("data", this.getById(entity.getId()));
            }
            return result;
        } catch (Exception e) {
            result.put("success", false);
            result.put("message", "更新异常：" + e.getMessage());
            return result;
        }
    }

    @Override
    public Map<String, Object> deleteById(String id) {
        Map<String, Object> result = new HashMap<>();
        try {
            boolean ok = this.removeById(id);
            result.put("success", ok);
            result.put("message", ok ? "删除成功" : "删除失败");
            return result;
        } catch (Exception e) {
            result.put("success", false);
            result.put("message", "删除异常：" + e.getMessage());
            return result;
        }
    }

    @Override
    public Map<String, Object> pageQuery(Integer current,
                                         Integer size,
                                         String userid,
                                         String userName,
                                         Long mainDepartmentId,
                                         Integer status) {
        Map<String, Object> result = new HashMap<>();
        try {
            if (current == null || current < 1) {
                current = 1;
            }
            if (size == null || size < 1) {
                size = 10;
            }

            Page<QweiUser> page = new Page<>(current, size);
            LambdaQueryWrapper<QweiUser> q = new LambdaQueryWrapper<>();
            if (userid != null && !userid.trim().isEmpty()) {
                q.eq(QweiUser::getUserid, userid.trim());
            }
            if (userName != null && !userName.trim().isEmpty()) {
                q.like(QweiUser::getUserName, userName.trim());
            }
            if (mainDepartmentId != null) {
                q.eq(QweiUser::getMainDepartmentId, mainDepartmentId);
            }
            if (status != null) {
                q.eq(QweiUser::getStatus, status);
            }
            q.orderByDesc(QweiUser::getUpdatedAt);

            Page<QweiUser> data = this.page(page, q);
            result.put("success", true);
            result.put("message", "查询成功");
            result.put("data", data.getRecords());
            result.put("total", data.getTotal());
            result.put("current", data.getCurrent());
            result.put("size", data.getSize());
            result.put("pages", data.getPages());
            return result;
        } catch (Exception e) {
            result.put("success", false);
            result.put("message", "查询异常：" + e.getMessage());
            return result;
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Map<String, Object> syncFromQiWei(String tenantIdParam) {
        Map<String, Object> result = new HashMap<>();
        String tenantId = null;
        try {
            tenantId = resolveTenantId(tenantIdParam);
            TenantContextHolder.setTenantId(tenantId);

            QiWeiConfig cfg = loadQiWeiConfig();
            String corpId = requireNonBlank(cfg.getCorpid(), "qiwei_config.corpid不能为空");
            String contactSecret = requireNonBlank(cfg.getContractSecret(), "qiwei_config.contractSecret不能为空");
            String appSecret = trimToNull(cfg.getAppSecret());
            if (appSecret == null) {
                throw new IllegalStateException("qiwei_config.appSecret不能为空（user/get 需要 appSecret）");
            }

            String contactToken = getAccessToken(corpId, contactSecret);
            String appToken = getAccessToken(corpId, appSecret);

            int inserted = 0;
            int updated = 0;

            String cursor = "";
            int limit = 100;
            while (true) {
                JsonNode listId = fetchUserListId(contactToken, cursor, limit);
                JsonNode deptUser = listId.path("dept_user");
                if (deptUser.isArray()) {
                    for (JsonNode du : deptUser) {
                        String userid = du.path("userid").asText(null);
                        if (userid == null || userid.isBlank()) {
                            continue;
                        }
                        QweiUser mapped = fetchAndMapUser(appToken, userid.trim(), tenantId);
                        if (upsertUser(mapped, tenantId)) {
                            inserted++;
                        } else {
                            updated++;
                        }
                    }
                }

                JsonNode nextCursor = listId.get("next_cursor");
                if (nextCursor == null || nextCursor.isNull()) {
                    break;
                }
                String nc = nextCursor.asText("");
                if (nc.isBlank()) {
                    break;
                }
                cursor = nc;
            }

            result.put("success", true);
            result.put("message", "同步成功");
            result.put("inserted", inserted);
            result.put("updated", updated);
            result.put("tenantId", tenantId);
            return result;
        } catch (Exception e) {
            result.put("success", false);
            result.put("message", "同步异常：" + e.getMessage());
            if (tenantId != null) {
                result.put("tenantId", tenantId);
            }
            return result;
        } finally {
            TenantContextHolder.clear();
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Map<String, Object> syncCustomerFromQiWei(String tenantIdParam) {
        Map<String, Object> result = new HashMap<>();
        String tenantId = null;
        try {
            tenantId = resolveTenantId(tenantIdParam);
            TenantContextHolder.setTenantId(tenantId);

            QiWeiConfig cfg = loadQiWeiConfig();
            String corpId = requireNonBlank(cfg.getCorpid(), "qiwei_config.corpid不能为空");
            String externalContactSecret = resolveExternalContactSecret(cfg);
            String contactToken = getAccessToken(corpId, externalContactSecret);
            ExternalContactUserApi externalContactUserApi = createExternalContactUserApi(corpId, externalContactSecret);

            int inserted = 0;
            int updated = 0;

            JsonNode followUsers = fetchCustomerContactFollowUsers(contactToken);
            printFollowUsersToConsole(followUsers);
            if (followUsers.isArray()) {
                for (JsonNode followUser : followUsers) {
                    try {
                        String followUserId = trimToNull(followUser.asText(null));
                        if (followUserId == null) {
                            continue;
                        }
                        SyncCounter counter = syncCustomerByFollowUser(externalContactUserApi, contactToken, followUserId, tenantId);
                        inserted += counter.inserted;
                        updated += counter.updated;
                    }catch (Exception e) {
                        log.error("syncCustomerFromQiWei: {}", e);
                        e.printStackTrace();
                    }

                }
            }

            result.put("success", true);
            result.put("message", "同步企微客户成功");
            result.put("inserted", inserted);
            result.put("updated", updated);
            result.put("total", inserted + updated);
            result.put("tenantId", tenantId);
            return result;
        } catch (Exception e) {
            result.put("success", false);
            result.put("message", "同步企微客户异常：" + e.getMessage());
            if (tenantId != null) {
                result.put("tenantId", tenantId);
            }
            return result;
        } finally {
            TenantContextHolder.clear();
        }
    }

    private boolean upsertUser(QweiUser user, String tenantId) {
        LambdaQueryWrapper<QweiUser> q = new LambdaQueryWrapper<QweiUser>()
                .eq(QweiUser::getUserid, user.getUserid());
        QweiUser exists = this.getOne(q, false);
        LocalDateTime now = LocalDateTime.now();
        if (exists == null) {
            user.setId(UUID.randomUUID().toString());
            user.setTenantId(tenantId);
            user.setIsDeleted(0);
            user.setCreatedAt(now);
            user.setUpdatedAt(now);
            try {
                this.save(user);
                return true;
            } catch (Exception insertEx) {
                // 若唯一索引仍是 userid 单列，且历史数据 tenant_id 为空，会导致撞唯一键
                if (!isDuplicateUserid(insertEx)) {
                    throw insertEx;
                }
                QweiUser legacy = this.baseMapper.selectByUseridIgnoreTenant(user.getUserid());
                if (legacy == null) {
                    throw insertEx;
                }
                String legacyTenant = trimToNull(legacy.getTenantId());
                if (legacyTenant == null) {
                    this.baseMapper.updateLegacyTenantAndFieldsByIdIgnoreTenant(
                            legacy.getId(),
                            tenantId,
                            user.getUserName(),
                            user.getMobile(),
                            user.getEmail(),
                            user.getBizEmail(),
                            user.getTelephone(),
                            user.getPosition(),
                            user.getGender(),
                            user.getStatus(),
                            user.getMainDepartmentId(),
                            user.getDepartmentIdsJson(),
                            user.getAliasName(),
                            user.getAvatar(),
                            user.getThumbAvatar(),
                            user.getAddress(),
                            0,
                            now
                    );
                    return false;
                }
                if (!tenantId.equals(legacyTenant)) {
                    throw new IllegalStateException("用户userid=" + user.getUserid()
                            + " 已存在且属于其他租户(" + legacyTenant + ")。请把唯一索引改为(tenant_id, userid)。");
                }
                // 同租户兜底更新
                exists = legacy;
            }
        }

        exists.setTenantId(null); // 不允许改租户
        exists.setUserName(user.getUserName());
        exists.setMobile(user.getMobile());
        exists.setEmail(user.getEmail());
        exists.setBizEmail(user.getBizEmail());
        exists.setTelephone(user.getTelephone());
        exists.setPosition(user.getPosition());
        exists.setGender(user.getGender());
        exists.setStatus(user.getStatus());
        exists.setMainDepartmentId(user.getMainDepartmentId());
        exists.setDepartmentIdsJson(user.getDepartmentIdsJson());
        exists.setAliasName(user.getAliasName());
        exists.setAvatar(user.getAvatar());
        exists.setThumbAvatar(user.getThumbAvatar());
        exists.setAddress(user.getAddress());
        exists.setIsDeleted(0);
        exists.setUpdatedAt(now);
        this.updateById(exists);
        return false;
    }

    private QweiUser fetchAndMapUser(String appToken, String userid, String tenantId) throws Exception {
        String url = QiWeiApiConstants.USER_GET + "?access_token="
                + URLEncoder.encode(appToken, StandardCharsets.UTF_8)
                + "&userid=" + URLEncoder.encode(userid, StandardCharsets.UTF_8);
        HttpRequest request = HttpRequest.newBuilder(URI.create(url)).GET().build();
        String body = HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString()).body();
        JsonNode json = OBJECT_MAPPER.readTree(body);
        int errCode = json.path("errcode").asInt(-1);
        if (errCode != 0) {
            throw new IllegalStateException("获取用户信息失败: " + body);
        }

        QweiUser user = new QweiUser();
        user.setTenantId(tenantId);
        user.setUserid(userid);
        user.setUserName(json.path("name").asText(null));
        user.setMobile(json.path("mobile").asText(null));
        user.setEmail(json.path("email").asText(null));
        user.setBizEmail(json.path("biz_email").asText(null));
        user.setTelephone(json.path("telephone").asText(null));
        user.setPosition(json.path("position").asText(null));
        user.setGender(json.hasNonNull("gender") ? json.path("gender").asInt() : null);
        user.setStatus(json.hasNonNull("status") ? json.path("status").asInt() : null);
        user.setAvatar(json.path("avatar").asText(null));
        user.setThumbAvatar(json.path("thumb_avatar").asText(null));
        user.setAliasName(json.path("alias").asText(null));
        user.setAddress(json.path("address").asText(null));
        user.setMainDepartmentId(json.hasNonNull("main_department") ? json.path("main_department").asLong() : null);
        JsonNode dep = json.path("department");
        if (dep.isArray()) {
            user.setDepartmentIdsJson(OBJECT_MAPPER.writeValueAsString(dep));
        }
        return user;
    }

    private SyncCounter syncCustomerByFollowUser(ExternalContactUserApi externalContactUserApi,
                                                 String contactToken,
                                                 String followUserId,
                                                 String tenantId) throws Exception {
        SyncCounter counter = new SyncCounter();
        JsonNode listJson = fetchExternalContactList(externalContactUserApi, followUserId);
        JsonNode externalUserIds = listJson.path("external_userid");
        if (!externalUserIds.isArray()) {
            return counter;
        }
        for (JsonNode externalUserNode : externalUserIds) {
            String externalUserId = trimToNull(externalUserNode.asText(null));
            if (externalUserId == null) {
                continue;
            }
            JsonNode externalDetail = null;
            try {
                externalDetail = fetchExternalContactDetail(contactToken, externalUserId);
            } catch (IllegalStateException ex) {
                if (isNoExternalContactRelationError(ex.getMessage())) {
                    // 官方建议：关系不存在(84061)时应跳过该客户，继续处理后续数据
                    System.out.println("[QiWeiCustomerSync] skip external user due to no relation. externalUserId="
                            + externalUserId + ", followUserId=" + followUserId + ", reason=" + ex.getMessage());
                    continue;
                }
            }
            printExternalCustomerToConsole(externalDetail, followUserId);
            CustomerManagement customer = mapToCustomerManagement(externalDetail, followUserId, tenantId);
            if (customer == null) {
                continue;
            }
            if (upsertCustomer(customer, tenantId)) {
                counter.inserted++;
            } else {
                counter.updated++;
            }
        }
        return counter;
    }

    private JsonNode fetchExternalContactList(ExternalContactUserApi externalContactUserApi, String followUserId) throws Exception {
        Object sdkResp = externalContactUserApi.listByUserId(followUserId);
        JsonNode json = OBJECT_MAPPER.valueToTree(sdkResp);
        int errCode = json.path("errcode").asInt(0);
        if (errCode != 0) {
            throw new IllegalStateException("获取外部联系人列表失败: " + json);
        }
        if (!json.has("external_userid") && json.has("data")) {
            ObjectNode adapted = OBJECT_MAPPER.createObjectNode();
            adapted.set("external_userid", json.path("data"));
            return adapted;
        }
        return json;
    }

    private void printExternalCustomerToConsole(JsonNode externalDetail, String followUserId) {
        JsonNode externalContact = externalDetail.path("external_contact");
        String externalUserId = trimToNull(externalContact.path("external_userid").asText(null));
        String customerName = trimToNull(externalContact.path("name").asText(null));
        String phone = null;
        JsonNode followInfo = externalDetail.path("follow_user");
        if (followInfo.isArray() && followInfo.size() > 0) {
            JsonNode firstFollow = followInfo.get(0);
            JsonNode remarkMobiles = firstFollow.path("remark_mobiles");
            if (remarkMobiles.isArray() && remarkMobiles.size() > 0) {
                phone = trimToNull(remarkMobiles.get(0).asText(null));
            }
        }
        System.out.println("[QiWeiCustomerSync] externalUserId=" + (externalUserId == null ? "" : externalUserId)
                + ", customerName=" + (customerName == null ? "" : customerName)
                + ", phone=" + (phone == null ? "" : phone)
                + ", followUserId=" + (followUserId == null ? "" : followUserId));
    }

    private JsonNode fetchExternalContactDetail(String contactToken, String externalUserId) throws Exception {
        String url = QiWeiApiConstants.EXTERNAL_CONTACT_GET + "?access_token="
                + URLEncoder.encode(contactToken, StandardCharsets.UTF_8)
                + "&external_userid=" + URLEncoder.encode(externalUserId, StandardCharsets.UTF_8);
        HttpRequest request = HttpRequest.newBuilder(URI.create(url)).GET().build();
        String body = HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString()).body();
        JsonNode json = OBJECT_MAPPER.readTree(body);
        int errCode = json.path("errcode").asInt(-1);
        if (errCode != 0) {
            throw new IllegalStateException("获取外部联系人详情失败: " + body);
        }
        return json;
    }

    private CustomerManagement mapToCustomerManagement(JsonNode externalDetail, String followUserId, String tenantId) {
        JsonNode externalContact = externalDetail.path("external_contact");
        if (externalContact.isMissingNode() || externalContact.isNull()) {
            return null;
        }
        String name = trimToNull(externalContact.path("name").asText(null));
        if (name == null) {
            return null;
        }
        String phone = null;
        JsonNode followInfo = externalDetail.path("follow_user");
        if (followInfo.isArray() && followInfo.size() > 0) {
            JsonNode firstFollow = followInfo.get(0);
            JsonNode remarkMobiles = firstFollow.path("remark_mobiles");
            if (remarkMobiles.isArray() && remarkMobiles.size() > 0) {
                phone = trimToNull(remarkMobiles.get(0).asText(null));
            }
        }
        CustomerManagement customer = new CustomerManagement();
        customer.setTenantId(tenantId);
        customer.setCustomerName(name);
        customer.setContact(phone);
        customer.setCustomerSource("QIWEI");
        customer.setSalesId(followUserId);
        customer.setSalesPhone(followUserId);

        LambdaQueryWrapper<QweiUser> salesQuery = new LambdaQueryWrapper<QweiUser>()
                .eq(QweiUser::getUserid, followUserId);
        QweiUser sales = this.getOne(salesQuery, false);
        if (sales != null) {
            customer.setSalesName(sales.getUserName());
            customer.setSalesPhone(trimToNull(sales.getMobile()) == null ? followUserId : sales.getMobile());
            customer.setDealershipId(sales.getMainDepartmentId() == null ? null : String.valueOf(sales.getMainDepartmentId()));
        }
        return customer;
    }

    private boolean upsertCustomer(CustomerManagement customer, String tenantId) {
        LambdaQueryWrapper<CustomerManagement> queryWrapper = new LambdaQueryWrapper<CustomerManagement>()
                .eq(CustomerManagement::getCustomerName, customer.getCustomerName());
        if (trimToNull(customer.getContact()) != null) {
            queryWrapper.eq(CustomerManagement::getContact, customer.getContact());
        }
        CustomerManagement existing = customerManagementService.getOne(queryWrapper, false);
        LocalDateTime now = LocalDateTime.now();
        if (existing == null) {
            customer.setId(UUID.randomUUID().toString());
            customer.setTenantId(tenantId);
            customer.setRecordingCount(0);
            customer.setContactCount(0);
            customer.setCreateTime(now);
            customer.setUpdateTime(now);
            customerManagementService.save(customer);
            return true;
        }

        existing.setTenantId(null);
        existing.setCustomerName(customer.getCustomerName());
        if (trimToNull(customer.getContact()) != null) {
            existing.setContact(customer.getContact());
        }
        existing.setSalesId(customer.getSalesId());
        existing.setSalesName(customer.getSalesName());
        existing.setSalesPhone(customer.getSalesPhone());
        existing.setDealershipId(customer.getDealershipId());
        existing.setCustomerSource("QIWEI");
        existing.setUpdateTime(now);
        customerManagementService.updateById(existing);
        return false;
    }

    private JsonNode fetchUserListId(String contactToken, String cursor, int limit) throws Exception {
        String url = QiWeiApiConstants.USER_LIST_ID + "?access_token="
                + URLEncoder.encode(contactToken, StandardCharsets.UTF_8);
        String payload = OBJECT_MAPPER.writeValueAsString(Map.of(
                "cursor", cursor == null ? "" : cursor,
                "limit", limit
        ));
        HttpRequest request = HttpRequest.newBuilder(URI.create(url))
                .header("Content-Type", "application/json")
                .POST(HttpRequest.BodyPublishers.ofString(payload))
                .build();
        String body = HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString()).body();
        JsonNode json = OBJECT_MAPPER.readTree(body);
        int errCode = json.path("errcode").asInt(-1);
        if (errCode != 0) {
            throw new IllegalStateException("获取成员ID列表失败: " + body);
        }
        return json;
    }

    private JsonNode fetchCustomerContactFollowUsers(String accessToken) throws Exception {
        String url = QiWeiApiConstants.EXTERNAL_CONTACT_FOLLOW_USER_LIST + "?access_token="
                + URLEncoder.encode(accessToken, StandardCharsets.UTF_8);
        HttpRequest request = HttpRequest.newBuilder(URI.create(url)).GET().build();
        String body = HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString()).body();
        JsonNode json = OBJECT_MAPPER.readTree(body);
        int errCode = json.path("errcode").asInt(-1);
        if (errCode != 0) {
            throw new IllegalStateException("获取客户联系可用成员列表失败: " + body);
        }
        return json.path("follow_user");
    }

    private void printFollowUsersToConsole(JsonNode followUsers) {
        if (followUsers == null || !followUsers.isArray()) {
            System.out.println("[QiWeiCustomerSync] follow_user list is empty or invalid.");
            return;
        }
        System.out.println("[QiWeiCustomerSync] follow_user count=" + followUsers.size());
        for (JsonNode followUser : followUsers) {
            String followUserId = trimToNull(followUser.asText(null));
            if (followUserId == null) {
                continue;
            }
            System.out.println("[QiWeiCustomerSync] followUserId=" + followUserId);
        }
    }

    private QiWeiConfig loadQiWeiConfig() throws Exception {
        String corpid = trimToNull(System.getProperty("qiwei.corpid"));
        String contactSecret = trimToNull(System.getProperty("qiwei.contactSecret"));
        String appSecret = trimToNull(System.getProperty("qiwei.appSecret"));
        if (corpid != null && contactSecret != null && appSecret != null) {
            QiWeiConfig cfg = new QiWeiConfig();
            cfg.setCorpid(corpid);
            cfg.setContractSecret(contactSecret);
            cfg.setAppSecret(appSecret);
            return cfg;
        }

        String tenantId = System.getProperty("tenantId", "TENANT_ID_CST_2026");
        TenantContextHolder.setTenantId(tenantId);
        try {
            LambdaQueryWrapper<DictItem> q = new LambdaQueryWrapper<DictItem>()
                    .eq(DictItem::getName, DictItemConstants.QIWEI_CONFIG)
                    .orderByDesc(DictItem::getUpdatedTime)
                    .orderByDesc(DictItem::getCreatedTime)
                    .last("limit 1");
            DictItem item = dictItemMapper.selectOne(q);
            if (item == null || item.getValue() == null || item.getValue().trim().isEmpty()) {
                throw new IllegalStateException("未找到 qiwei_config");
            }
            String json = PasswordUtil.decryptReversibleWithDefaultSalt(item.getValue());
            return OBJECT_MAPPER.readValue(json, QiWeiConfig.class);
        } finally {
            TenantContextHolder.clear();
        }
    }

    private String getAccessToken(String corpId, String secret) throws Exception {
        String url = QiWeiApiConstants.GET_TOKEN + "?corpid="
                + URLEncoder.encode(corpId, StandardCharsets.UTF_8)
                + "&corpsecret=" + URLEncoder.encode(secret, StandardCharsets.UTF_8);
        HttpRequest request = HttpRequest.newBuilder(URI.create(url)).GET().build();
        String body = HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString()).body();
        JsonNode json = OBJECT_MAPPER.readTree(body);
        int errCode = json.path("errcode").asInt(-1);
        if (errCode != 0) {
            throw new IllegalStateException("获取access_token失败: " + body);
        }
        return json.path("access_token").asText();
    }

    private String requireNonBlank(String value, String msg) {
        if (value == null || value.trim().isEmpty()) {
            throw new IllegalStateException(msg);
        }
        return value.trim();
    }

    private String resolveExternalContactSecret(QiWeiConfig cfg) {
        String fromProp = trimToNull(System.getProperty("qiwei.externalContactSecret"));
        if (fromProp != null) {
            return fromProp;
        }
        String appSecret = trimToNull(cfg.getAppSecret());
        if (appSecret != null) {
            return appSecret;
        }
        String contactSecret = trimToNull(cfg.getContractSecret());
        if (contactSecret != null) {
            return contactSecret;
        }
        throw new IllegalStateException("外部联系人secret不能为空（请配置 -Dqiwei.externalContactSecret 或 qiwei_config.appSecret/contractSecret）");
    }

    private boolean isNoExternalContactRelationError(String message) {
        if (message == null) {
            return false;
        }
        return message.contains("\"errcode\":84061")
                || message.contains("errcode\":84061")
                || message.contains("不存在外部联系人的关系");
    }

    private boolean isDuplicateUserid(Exception e) {
        String msg = e.getMessage();
        return msg != null && msg.contains("Duplicate entry");
    }

    private String trimToNull(String value) {
        if (value == null) {
            return null;
        }
        String t = value.trim();
        return t.isEmpty() ? null : t;
    }

    private String resolveTenantId(String tenantIdFromEntity) {
        String current = trimToNull(TenantContextHolder.getTenantId());
        if (current != null) {
            return current;
        }
        String t = trimToNull(tenantIdFromEntity);
        if (t != null) {
            return t;
        }
        String fromProp = trimToNull(System.getProperty("tenantId"));
        if (fromProp != null) {
            return fromProp;
        }
        throw new IllegalStateException("tenantId不能为空（请设置租户上下文或传入 tenantId 或 -DtenantId）");
    }

    private ExternalContactUserApi createExternalContactUserApi(String corpId, String contactSecret) {
        WeComTokenCacheable cacheable = new InMemoryWeComTokenCacheable();
        WorkWeChatApi workWeChatApi = new WorkWeChatApi(cacheable, HttpLoggingInterceptor.Level.NONE);
        return workWeChatApi.externalContactManager(DefaultAgent.of(corpId, contactSecret, "0"))
                .externalContactUserApi();
    }

    private static class SyncCounter {
        private int inserted;
        private int updated;
    }

    private static final class InMemoryWeComTokenCacheable implements WeComTokenCacheable {
        private final ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();

        @Override
        public String putCorpTicket(String corpId, String agentId, String corpTicket) {
            map.put("ticket:corp:" + corpId + ":" + agentId, corpTicket);
            return corpTicket;
        }

        @Override
        public String getCorpTicket(String corpId, String agentId) {
            return map.get("ticket:corp:" + corpId + ":" + agentId);
        }

        @Override
        public String putAgentTicket(String corpId, String agentId, String agentTicket) {
            map.put("ticket:agent:" + corpId + ":" + agentId, agentTicket);
            return agentTicket;
        }

        @Override
        public String getAgentTicket(String corpId, String agentId) {
            return map.get("ticket:agent:" + corpId + ":" + agentId);
        }

        @Override
        public String putAccessToken(String corpId, String agentId, String accessToken) {
            map.put("token:" + corpId + ":" + agentId, accessToken);
            return accessToken;
        }

        @Override
        public String getAccessToken(String corpId, String agentId) {
            return map.get("token:" + corpId + ":" + agentId);
        }

        @Override
        public void clearAccessToken(String corpId, String agentId) {
            map.remove("token:" + corpId + ":" + agentId);
        }
    }
}

