package com.rj.qiwei;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.rj.common.DictItemConstants;
import com.rj.common.PasswordUtil;
import com.rj.dto.QiWeiConfig;
import com.rj.entity.DictItem;
import com.rj.mapper.DictItemMapper;
import com.rj.tenant.TenantContextHolder;
import cn.felord.DefaultAgent;
import cn.felord.WeComException;
import cn.felord.WeComTokenCacheable;
import cn.felord.api.UserApi;
import cn.felord.domain.contactbook.user.DeptUserListResponse;
import cn.felord.domain.contactbook.user.DeptUser;
import cn.felord.domain.contactbook.user.UserInfoResponse;
import cn.felord.domain.contactbook.user.SimpleUser;
import cn.felord.domain.GenericResponse;
import cn.felord.retrofit.AccessTokenApi;
import cn.felord.retrofit.WorkWechatRetrofitFactory;
import okhttp3.ConnectionPool;
import okhttp3.logging.HttpLoggingInterceptor;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Assumptions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

import static org.junit.jupiter.api.Assertions.*;

@SpringBootTest
@ActiveProfiles("test")
public class QiWeiContactsReadTest {

    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

    @Autowired
    private DictItemMapper dictItemMapper;

    @Test
    public void readContactsFromWeCom() throws Exception {
        QiWeiConfig cfg = loadQiWeiConfigFromDbOrSystemProps();

        // 支持通过 JVM 参数覆盖 DB 配置，便于快速联调：
        // -Dqiwei.corpid=xxx -Dqiwei.contactSecret=xxx -Dqiwei.appSecret=xxx
        String corpId = requireNonBlank(firstNonBlank(System.getProperty("qiwei.corpid"), cfg.getCorpid()),
                "qiwei_config.corpid 不能为空");
        String appSecret = trimToNull(firstNonBlank(System.getProperty("qiwei.appSecret"), cfg.getAppSecret()));
        String contactSecret = trimToNull(firstNonBlank(System.getProperty("qiwei.contactSecret"), cfg.getContractSecret()));
        assertTrue(appSecret != null || contactSecret != null, "qiwei_config.appSecret/contractSecret 至少一个不能为空");

        // 通讯录读取优先使用通讯录 secret，若失败再尝试 app secret，便于自动命中有权限的一组凭证。
        List<SecretCandidate> candidates = buildSecretCandidates(contactSecret, appSecret);
        List<String> failures = new ArrayList<>();
        UserApi userApi = null;
        DeptUserListResponse idListResp = null;
        String selectedSecretType = null;

        // 1) 拉取成员ID列表（适用于通讯录同步类 secret）
        for (SecretCandidate candidate : candidates) {
            System.out.println("qiwei test trying secretType=" + candidate.type
                    + ", corpId=" + corpId
                    + ", secretTail=" + maskSecretTail(candidate.secret));
            userApi = createUserApi(corpId, candidate.secret);
            try {
                idListResp = userApi.userList("", 100);
                selectedSecretType = candidate.type;
                break;
            } catch (WeComException e) {
                failures.add("secretType=" + candidate.type + ", error=" + e.getMessage());
            }
        }

        if (idListResp == null) {
            fail("读取通讯录失败：所有可用 secret 均不可用。corpId=" + corpId
                    + "；排查建议：1) 若报 e=60020 请把出口 IP 36.143.223.91 加入该 secret 的可信 IP；"
                    + "2) 若报 e=48002 请给对应应用开通通讯录读取权限并放开可见范围；"
                    + "3) 确认优先使用的是通讯录 secret。失败明细=" + failures);
        }
        assertNotNull(userApi, "UserApi 初始化失败");
        assertNotNull(idListResp, "企微 user/list_id 响应不应为空");
        System.out.println("qiwei test selected secretType=" + selectedSecretType);
        System.out.println("userList resp: " + OBJECT_MAPPER.writeValueAsString(idListResp));

        List<DeptUser> deptUsersFromList = idListResp.getDeptUser();
        List<String> userIds = deptUsersFromList == null ? null : deptUsersFromList.stream()
                .map(DeptUser::getUserid)
                .toList();
        if (userIds != null && !userIds.isEmpty()) {
            String firstUserId = userIds.get(0);
            UserInfoResponse user = tryGetUserWithFallback(userApi, firstUserId, corpId, appSecret);
            if (user != null) {
                System.out.println("first user(" + firstUserId + "): " + OBJECT_MAPPER.writeValueAsString(user));
            }
        }

        // 2) 拉取部门 1 的成员简要信息（需要应用对该部门有查看权限）
        GenericResponse<List<SimpleUser>> deptUsers = tryGetDeptUsersWithFallback(userApi, corpId, appSecret);
        if (deptUsers != null) {
            System.out.println("dept(1) simple users: " + OBJECT_MAPPER.writeValueAsString(deptUsers));
        }
    }

    private QiWeiConfig loadQiWeiConfigFromDbOrSystemProps() throws Exception {
        // 可选：通过 -DtenantId=xxx 指定租户；不传则使用默认测试租户
        String tenantId = System.getProperty("tenantId", "TENANT_ID_CST_2026");

        if (tenantId != null && !tenantId.trim().isEmpty()) {
            TenantContextHolder.setTenantId(tenantId.trim());
            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()) {
                    String json = PasswordUtil.decryptReversibleWithDefaultSalt(item.getValue());
                    QiWeiConfig cfg = OBJECT_MAPPER.readValue(json, QiWeiConfig.class);
                    if (cfg != null) {
                        return cfg;
                    }
                }
            } finally {
                TenantContextHolder.clear();
            }
        }
        Assumptions.assumeTrue(false,
                "未找到 qiwei_config（且未提供 -Dqiwei.corpid/-Dqiwei.contractSecret 或 -Dqiwei.appSecret），跳过该测试");
        return null; // never reached
    }

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

    private static UserApi createUserApi(String corpId, String secret) {
        String agentId = "0";
        WeComTokenCacheable cacheable = new InMemoryWeComTokenCacheable();
        AccessTokenApi tokenApi = new AccessTokenApi(cacheable, DefaultAgent.of(corpId, secret, agentId));
        var retrofit = WorkWechatRetrofitFactory.create(
                tokenApi,
                new ConnectionPool(),
                HttpLoggingInterceptor.Level.NONE
        );
        return retrofit.create(UserApi.class);
    }

    private static String firstNonBlank(String a, String b) {
        if (a != null && !a.trim().isEmpty()) {
            return a.trim();
        }
        if (b != null && !b.trim().isEmpty()) {
            return b.trim();
        }
        return null;
    }

    private static List<SecretCandidate> buildSecretCandidates(String contactSecret, String appSecret) {
        List<SecretCandidate> candidates = new ArrayList<>(2);
        if (contactSecret != null) {
            candidates.add(new SecretCandidate("contact", contactSecret));
        }
        if (appSecret != null && (contactSecret == null || !appSecret.equals(contactSecret))) {
            candidates.add(new SecretCandidate("app", appSecret));
        }
        return candidates;
    }

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

    private static UserInfoResponse tryGetUserWithFallback(UserApi currentApi, String userId, String corpId, String appSecret) {
        try {
            UserInfoResponse user = currentApi.getUser(userId);
            assertNotNull(user, "读取成员信息响应不应为空");
            return user;
        } catch (WeComException e) {
            if (!isContactAssistantForbidden(e)) {
                throw e;
            }
            System.out.println("[WARN] 当前 secret 调用 user/get 被限制(48009)，尝试使用 appSecret 重试"+e.getMessage());
            if (appSecret == null) {
                System.out.println("[WARN] appSecret 为空，跳过 user/get");
                return null;
            }
            UserApi appApi = createUserApi(corpId, appSecret);
            try {
                UserInfoResponse user = appApi.getUser(userId);
                assertNotNull(user, "读取成员信息响应不应为空");
                return user;
            } catch (WeComException ex) {
                System.out.println("[WARN] appSecret 调用 user/get 仍失败，跳过。error=" + ex.getMessage());
                return null;
            }
        }
    }

    private static GenericResponse<List<SimpleUser>> tryGetDeptUsersWithFallback(UserApi currentApi, String corpId, String appSecret) {
        try {
            GenericResponse<List<SimpleUser>> deptUsers = currentApi.getDeptUsers(1L);
            assertNotNull(deptUsers, "企微 user/simplelist 响应不应为空");
            return deptUsers;
        } catch (WeComException e) {
            if (!isContactAssistantForbidden(e)) {
                throw e;
            }
            System.out.println("[WARN] 当前 secret 调用 user/simplelist 被限制(48009)，尝试使用 appSecret 重试");
            if (appSecret == null) {
                System.out.println("[WARN] appSecret 为空，跳过 user/simplelist");
                return null;
            }
            UserApi appApi = createUserApi(corpId, appSecret);
            try {
                GenericResponse<List<SimpleUser>> deptUsers = appApi.getDeptUsers(1L);
                assertNotNull(deptUsers, "企微 user/simplelist 响应不应为空");
                return deptUsers;
            } catch (WeComException ex) {
                System.out.println("[WARN] appSecret 调用 user/simplelist 仍失败，跳过。error=" + ex.getMessage());
                return null;
            }
        }
    }

    private static boolean isContactAssistantForbidden(WeComException e) {
        String msg = e.getMessage();
        return msg != null && (msg.contains("e=48009") || msg.contains("api forbidden for contact assistant"));
    }

    private static String maskSecretTail(String secret) {
        int n = Math.min(6, secret.length());
        return "***" + secret.substring(secret.length() - n);
    }

    private record SecretCandidate(String type, String secret) {
    }

    /**
     * 测试用内存缓存：避免引入 Redis 依赖/配置。
     */
    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);
        }
    }
}

