package com.malk.service.dingtalk.impl;

import cn.hutool.core.util.ObjectUtil;
import com.malk.server.dingtalk.DDConf;
import com.malk.server.dingtalk.DDR;
import com.malk.server.dingtalk.DDR_New;
import com.malk.service.dingtalk.DDClient_Contacts;
import com.malk.utils.UtilDateTime;
import com.malk.utils.UtilList;
import com.malk.utils.UtilMap;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Service
@Slf4j
public class DDImplClient_Contacts implements DDClient_Contacts {

    /**
     * 获取子部门ID列表 [获取企业部门下的所有直属子部门列表]
     *
     * @apiNote https://open.dingtalk.com/document/orgapp/obtain-a-sub-department-id-list-v2
     */
    @SneakyThrows
    @Override
    public List<Long> listSubDepartmentId(String access_token, long dept_id) {
        Map param = UtilMap.map("access_token", access_token);
        Map body = UtilMap.map("dept_id", dept_id);
        // ppExt:: 格屋临时添加, 后续上线限流重试功能
//        Thread.sleep(50);
        DDR r = DDR.doPost("https://oapi.dingtalk.com/topapi/v2/department/listsubid", null, param, body);
        // fixme: 失败时 result 为 null, 直接 .get("dept_id_list") 会 NPE; 先 assertSuccess 抛带 errcode 的业务异常
        r.assertSuccess();
        Map rsp = (Map) r.getResult();
        List<Number> list = (List<Number>) rsp.get("dept_id_list");
        // ppExt: 不要直接使用 Number 作为类型, 不同基本类型比较值时会有偏差 [可以作为父类接受数据, 避免直接强制类型转换错误, 再进行基本类型处理]
        return list.stream().map(item -> item.longValue()).collect(Collectors.toList());
    }

    /**
     * 获取部门列表
     *
     * @apiNote https://open.dingtalk.com/document/orgapp/obtain-the-department-list-v2
     */
    @Override
    public List<Map> listSubDepartmentDetail(String access_token, long dept_id) {
        Map param = UtilMap.map("access_token", access_token);
        Map body = UtilMap.map("dept_id", dept_id);
        return (List<Map>) DDR.doPost("https://oapi.dingtalk.com/topapi/v2/department/listsub", null, param, body).getResult();
    }

    /**
     * 获取全部架构内部门_全部 detail [包含一级部门]
     */
    @Override
    public List<Map> getDepartmentDetail_all(String access_token, boolean containsTop) {
        List<Map> deptList = containsTop ? UtilList.asList(getDepartmentInfo(access_token, DDConf.TOP_DEPARTMENT)) : new ArrayList<>();
        return _getDepartment_all(access_token, DDConf.TOP_DEPARTMENT, deptList, false);
    }

    /**
     * 获取全部架构内部门_全部 id [包含一级部门]
     */
    @Override
    public List<Long> getDepartmentId_all(String access_token, boolean containsTop) {
        List<Long> deptList = containsTop ? UtilList.asList(DDConf.TOP_DEPARTMENT) : new ArrayList<>();
        return (List<Long>) _getDepartment_all(access_token, DDConf.TOP_DEPARTMENT, deptList, true)
                .stream().map(detpId -> Long.valueOf(String.valueOf(detpId))).collect(Collectors.toList());
    }

    @Override
    public List<Long> getDepartmentId_all(String access_token, boolean containsTop, long deptId) {
        List<Long> deptList = containsTop ? UtilList.asList(deptId) : new ArrayList<>();
        return (List<Long>) _getDepartment_all(access_token, deptId, deptList, true)
                .stream().map(detpId -> Long.valueOf(String.valueOf(detpId))).collect(Collectors.toList());
    }

    /// 递归查询
    private List _getDepartment_all(String access_token, long deptId, List deptIdList, boolean isIdList) {
        List tList = isIdList ?
                listSubDepartmentId(access_token, deptId) :
                listSubDepartmentDetail(access_token, deptId);
        deptIdList.addAll(tList);
        // 当所有子部门都为空, 递归出口
        for (Object dept : tList) {
            _getDepartment_all(access_token, Long.valueOf(String.valueOf(isIdList ? dept : ((Map) dept).get("dept_id"))), deptIdList, isIdList);
        }
        return deptIdList;
    }

    /**
     * 获取部门用户userid列表 [无需分页]
     *
     * @apiNote https://open.dingtalk.com/document/orgapp/query-the-list-of-department-userids
     */
    @Override
    public List<String> listDepartmentUserId(String access_token, long dept_id) {
        Map param = UtilMap.map("access_token", access_token);
        Map body = UtilMap.map("dept_id", dept_id);
        DDR r = DDR.doPost("https://oapi.dingtalk.com/topapi/user/listid", null, param, body);
        // fixme: 失败时 result 为 null, 直接 .get("userid_list") 会 NPE; 先 assertSuccess 抛带 errcode 的业务异常
        r.assertSuccess();
        Map rsp = (Map) r.getResult();
        return (List<String>) rsp.get("userid_list");
    }

    /**
     * 查询用户详情
     *
     * @apiNote https://open.dingtalk.com/document/orgapp/query-user-details
     */
    @Override
    public Map getUserInfoById(String access_token, String userId) {
        Map param = UtilMap.map("access_token", access_token);
        Map body = UtilMap.map("userid", userId);
        return (Map) DDR.doPost("https://oapi.dingtalk.com/topapi/v2/user/get", null, param, body).getResult();
    }

    /**
     * 查询用户详情
     */
    @Override
    public Map getUserInfoByMobile(String access_token, String mobile) {
        Map param = UtilMap.map("access_token", access_token);
        Map body = UtilMap.map("mobile", mobile);
        Map result = (Map) DDR.doPost("https://oapi.dingtalk.com/topapi/v2/user/getbymobile", null, param, body).getResult();
        return getUserInfoById(access_token, String.valueOf(result.get("userid")));
    }

    /**
     * 删除用户
     *
     * @apiNote https://open.dingtalk.com/document/orgapp/delete-a-user
     */
    @Override
    public boolean deleteUser(String access_token, String userId) {
        DDR.doPost("https://oapi.dingtalk.com/topapi/v2/user/delete", null, DDConf.initTokenParams(access_token), UtilMap.map("userid", userId));
        return true;
    }

    /**
     * 查询离职记录列表
     *
     * @apiNote https://open.dingtalk.com/document/orgapp/query-the-details-of-employees-who-have-left-office
     */
    @Override
    public List<Map<String, String>> getLeaveEmployeeRecords(String access_token, Date startTime, Map extInfo) {
        return _getLeaveEmployeeRecords(access_token, startTime, extInfo, new ArrayList<>());
    }

    /// 递归所有离职人员
    public List<Map<String, String>> _getLeaveEmployeeRecords(String access_token, Date startTime, Map extInfo, List dataList) {
        String start = UtilDateTime.format(startTime, "yyyy-MM-dd'T'HH:mm:ss") + "Z"; // 字符格式
        Map body = UtilMap.map("startTime", start);
        if (ObjectUtil.isNotNull(extInfo)) {
            body.putAll(extInfo);
            if (body.containsKey("endTime")) {
                Date end = (Date) extInfo.get("endTime");
                body.put("endTime", UtilDateTime.format(end, "yyyy-MM-dd'T'HH:mm:ss") + "Z");
            }
        }
        if (!body.containsKey("maxResults")) {
            body.put("maxResults", 50);
        }
        DDR_New ddr_new = DDR_New.doGet("https://api.dingtalk.com/v1.0/contact/empLeaveRecords", DDConf.initTokenHeader(access_token), body);
        dataList.addAll(ddr_new.getRecords());
        if (StringUtils.isNotBlank(ddr_new.getNextToken())) {
            extInfo = UtilMap.putNotNull(extInfo, "nextToken", ddr_new.getNextToken());
            _getLeaveEmployeeRecords(access_token, startTime, extInfo, dataList);
        }
        return dataList;
    }

    /**
     * 获取指定用户的所有父部门列表
     */
    @Override
    public Map listParentByUser(String access_token, String userId) {
        DDR r = DDR.doPost("https://oapi.dingtalk.com/topapi/v2/department/listparentbyuser", null, DDConf.initTokenParams(access_token), UtilMap.map("userid", userId));
        return (Map) r.getResult();
    }

    /**
     * 创建用户
     *
     * @apiNote https://open.dingtalk.com/document/orgapp/user-information-creation
     */
    @Override
    public Map createUser(String access_token, String name, String mobile, List<String> dept_id_list, Map body_ext) {
        Map param = UtilMap.map("access_token", access_token);
        String deptIds = String.join(",", dept_id_list.stream().map(dept -> String.valueOf(dept)).collect(Collectors.toList()));
        Map body = UtilMap.map("name, mobile, dept_id_list", name, mobile, deptIds);
        if (ObjectUtil.isNotNull(body_ext)) {
            body.putAll(body_ext);
        }
        return (Map) DDR.doPost("https://oapi.dingtalk.com/topapi/v2/user/create", null, param, body).getResult();
    }

    /**
     * 创建部门
     *
     * @apiNote https://open.dingtalk.com/document/orgapp/create-a-department-v2
     */
    @Override
    public Map createDepartment(String access_token, String name, long parent_id, Map body_ext) {

        // 长度限制为1~64个字符，不允许包含字符"-"","以及","
        name = name.replaceAll("-", "").replaceAll(",", "");
        Map param = UtilMap.map("access_token", access_token);
        Map body = UtilMap.map("name, parent_id", name, parent_id);
        if (ObjectUtil.isNotNull(body_ext)) {
            body.putAll(body_ext);
        }
        return (Map) DDR.doPost("https://oapi.dingtalk.com/topapi/v2/department/create", null, param, body).getResult();
    }

    /**
     * 获取部门详情
     *
     * @apiNote https://open.dingtalk.com/document/orgapp/query-department-details0-v2
     */
    @Override
    public Map getDepartmentInfo(String access_token, long dept_id) {
        return (Map) DDR.doPost("https://oapi.dingtalk.com/topapi/v2/department/get", null, DDConf.initTokenParams(access_token), UtilMap.map("dept_id", dept_id)).getResult();
    }

    /**
     * 获取员工人数
     */
    @Override
    public int getUserCount(String access_token, boolean only_active) {
        Map result = (Map) DDR.doPost("https://oapi.dingtalk.com/topapi/user/count", null, DDConf.initTokenParams(access_token), UtilMap.map("only_active", only_active)).getResult();
        return UtilMap.getInt(result, "count");
    }

    ////////////////////////////// 企业账号 //////////////////////////////

    /**
     * 创建钉钉自建企业账号
     *
     * @apiNote https://open.dingtalk.com/document/orgapp/create-dingtalk-user-created-dedicated-account
     */
    @Override
    public Map createUser_dingTalk(String access_token, String login_id, String init_password, String name, List<Long> dept_id_list, Map body_ext) {
        Map param = UtilMap.map("access_token", access_token);
        String deptIds = String.join(",", dept_id_list.stream().map(dept -> String.valueOf(dept)).collect(Collectors.toList()));
        Map body = UtilMap.map("exclusive_account, exclusive_account_type, login_id, init_password, name, dept_id_list", true, "dingtalk", login_id, init_password, name, deptIds);
        if (ObjectUtil.isNotNull(body_ext)) {
            body.putAll(body_ext);
        }
        return (Map) DDR.doPost("https://oapi.dingtalk.com/topapi/v2/user/create", null, param, body).getResult();
    }

    /**
     * 更新钉钉自建企业账号
     *
     * @apiNote https://open.dingtalk.com/document/orgapp/update-dedicated-accounts-information
     */
    @Override
    public Map updateUser_dingTalk(String access_token, String userId, List<Long> dept_id_list, Map body_ext) {
        Map param = UtilMap.map("access_token", access_token);
        String deptIds = String.join(",", dept_id_list.stream().map(dept -> String.valueOf(dept)).collect(Collectors.toList()));
        Map body = UtilMap.map("userid, dept_id_list", userId, deptIds);
        body.putAll(body_ext);
        return (Map) DDR.doPost("https://oapi.dingtalk.com/topapi/v2/user/update", null, param, body).getResult();
    }

    /**
     * 邀请其他组织企业账号加入
     *
     * @apiNote https://open.dingtalk.com/document/orgapp/invite-other-organization-specific-accounts-to-join
     */
    @Override
    public Map inviteExclusiveUser(String access_token, String name, List<Long> dept_id_list, String outer_exclusive_corpid, String outer_exclusive_userid, Map body_ext) {

        Map param = UtilMap.map("access_token", access_token);
        String deptIds = String.join(",", dept_id_list.stream().map(dept -> String.valueOf(dept)).collect(Collectors.toList()));
        Map body = UtilMap.map("name, dept_id_list, outer_exclusive_corpid, outer_exclusive_userid", name, deptIds, outer_exclusive_corpid, outer_exclusive_userid);
        if (ObjectUtil.isNotNull(body_ext)) {
            body.putAll(body_ext);
        }
        return (Map) DDR.doPost("https://oapi.dingtalk.com/topapi/v2/user/create", null, param, body).getResult();
    }

    /**
     * 授权企业帐号可加入多组织
     *
     * @apiNote https://open.dingtalk.com/document/orgapp/authorize-a-dedicated-account-to-join-multiple-organizations
     */
    @Override
    public void multiOrgPermissions(String access_token, String joinCorpId, String grantDeptIdList) {
        Map body = UtilMap.map("joinCorpId, grantDeptIdList", joinCorpId, grantDeptIdList);
        DDR_New.doPost("https://api.dingtalk.com/v1.0/contact/orgAccounts/multiOrgPermissions/auth", DDConf.initTokenHeader(access_token), null, body).getResult();
    }

    // ================================================================================
    //  v2 对齐方法（capability: dingtalk-contacts-v2）
    //  统一模板：param = {access_token}; body 按 §3.4.2 必填显式 + body_ext 透传
    // ================================================================================

    private static final String OAPI = DDConf.OAPI_BASE;

    /** 统一构造含 access_token 的 param map */
    private Map qs(String access_token) {
        return UtilMap.map("access_token", access_token);
    }

    /** 合并 body 与 body_ext（body_ext 不覆盖显式字段）*/
    private Map merge(Map body, Map body_ext) {
        if (body_ext == null || body_ext.isEmpty()) {
            return body;
        }
        for (Object k : body_ext.keySet()) {
            if (!body.containsKey(k)) {
                body.put(k, body_ext.get(k));
            }
        }
        return body;
    }

    // ============================= 用户管理 User =============================

    @Override
    public Map createUser_v2(String access_token, String name, String mobile, List<String> dept_id_list, Map body_ext) {
        Map body = new java.util.HashMap();
        body.put("name", name);
        body.put("mobile", mobile);
        body.put("dept_id_list", dept_id_list == null ? "" : String.join(",", dept_id_list));
        merge(body, body_ext);
        return (Map) DDR.doPost(OAPI + "/topapi/v2/user/create", null, qs(access_token), body).getResult();
    }

    @Override
    public Map updateUser(String access_token, String userid, Map body_ext) {
        Map body = new java.util.HashMap();
        body.put("userid", userid);
        merge(body, body_ext);
        return (Map) DDR.doPost(OAPI + "/topapi/v2/user/update", null, qs(access_token), body).getResult();
    }

    @Override
    public Map getUser_v2(String access_token, String userid, Map body_ext) {
        Map body = new java.util.HashMap();
        body.put("userid", userid);
        merge(body, body_ext);
        return (Map) DDR.doPost(OAPI + "/topapi/v2/user/get", null, qs(access_token), body).getResult();
    }

    @Override
    public Map getUserByMobile_v2(String access_token, String mobile, Map body_ext) {
        Map body = new java.util.HashMap();
        body.put("mobile", mobile);
        merge(body, body_ext);
        return (Map) DDR.doPost(OAPI + "/topapi/v2/user/getbymobile", null, qs(access_token), body).getResult();
    }

    @Override
    public Map listUsersSimple(String access_token, long dept_id, Map body_ext) {
        Map body = new java.util.HashMap();
        body.put("dept_id", dept_id);
        merge(body, body_ext);
        return (Map) DDR.doPost(OAPI + "/topapi/user/listsimple", null, qs(access_token), body).getResult();
    }

    @Override
    public Map listDeptUserDetail_v2(String access_token, long dept_id, Map body_ext) {
        Map body = new java.util.HashMap();
        body.put("dept_id", dept_id);
        merge(body, body_ext);
        return (Map) DDR.doPost(OAPI + "/topapi/v2/user/list", null, qs(access_token), body).getResult();
    }

    @Override
    public Map listInactiveUsers(String access_token, boolean is_active, long offset, long size, Map body_ext) {
        Map body = new java.util.HashMap();
        body.put("is_active", is_active);
        body.put("offset", offset);
        body.put("size", size);
        merge(body, body_ext);
        return (Map) DDR.doPost(OAPI + "/topapi/inactive/user/v2/get", null, qs(access_token), body).getResult();
    }

    @Override
    public Map getUserByUnionId(String access_token, String union_id, Map body_ext) {
        Map body = new java.util.HashMap();
        body.put("unionid", union_id);
        merge(body, body_ext);
        return (Map) DDR.doPost(OAPI + "/topapi/user/getbyunionid", null, qs(access_token), body).getResult();
    }

    @Override
    public Map listAdmins(String access_token) {
        return (Map) DDR.doPost(OAPI + "/topapi/user/get_admin", null, qs(access_token), new java.util.HashMap()).getResult();
    }

    @Override
    public Map listDimissionEmployees_v2(String access_token, long offset, long size, Map body_ext) {
        Map body = new java.util.HashMap();
        body.put("offset", offset);
        body.put("size", size);
        merge(body, body_ext);
        return (Map) DDR.doPost(OAPI + "/topapi/smartwork/hrm/employee/listdimission", null, qs(access_token), body).getResult();
    }

    // ============================= 部门管理 Department =============================

    @Override
    public Map createDepartment_v2(String access_token, String name, long parent_id, Map body_ext) {
        Map body = new java.util.HashMap();
        body.put("name", name);
        body.put("parent_id", parent_id);
        merge(body, body_ext);
        return (Map) DDR.doPost(OAPI + "/topapi/v2/department/create", null, qs(access_token), body).getResult();
    }

    @Override
    public Map updateDepartment(String access_token, long dept_id, Map body_ext) {
        Map body = new java.util.HashMap();
        body.put("dept_id", dept_id);
        merge(body, body_ext);
        return (Map) DDR.doPost(OAPI + "/topapi/v2/department/update", null, qs(access_token), body).getResult();
    }

    @Override
    public Boolean deleteDepartment(String access_token, long dept_id, Map body_ext) {
        Map body = new java.util.HashMap();
        body.put("dept_id", dept_id);
        merge(body, body_ext);
        DDR r = DDR.doPost(OAPI + "/topapi/v2/department/delete", null, qs(access_token), body);
        return r != null && r.isSuccess();
    }

    @Override
    public Map getDepartment_v2(String access_token, long dept_id, Map body_ext) {
        Map body = new java.util.HashMap();
        body.put("dept_id", dept_id);
        merge(body, body_ext);
        return (Map) DDR.doPost(OAPI + "/topapi/v2/department/get", null, qs(access_token), body).getResult();
    }

    @Override
    public List<Map> listSubDepartments_v2(String access_token, long dept_id, Map body_ext) {
        Map body = new java.util.HashMap();
        body.put("dept_id", dept_id);
        merge(body, body_ext);
        Object result = DDR.doPost(OAPI + "/topapi/v2/department/listsub", null, qs(access_token), body).getResult();
        return result instanceof List ? (List<Map>) result : new ArrayList<>();
    }

    @Override
    public Map listParentByDept(String access_token, long dept_id, Map body_ext) {
        Map body = new java.util.HashMap();
        body.put("dept_id", dept_id);
        merge(body, body_ext);
        return (Map) DDR.doPost(OAPI + "/topapi/v2/department/listparentbydept", null, qs(access_token), body).getResult();
    }

    // ============================= 员工字段隐藏 EmployeeField =============================

    @Override
    public Map upsertHideField(String access_token, String name, String field, String userIds, String deptIds, Map body_ext) {
        Map body = new java.util.HashMap();
        body.put("name", name);
        body.put("field", field);
        if (userIds != null) body.put("userids", userIds);
        if (deptIds != null) body.put("dept_ids", deptIds);
        merge(body, body_ext);
        return (Map) DDR.doPost(OAPI + "/topapi/hide_field/add_or_update", null, qs(access_token), body).getResult();
    }

    @Override
    public Boolean removeHideField(String access_token, long id) {
        Map body = UtilMap.map("id", id);
        DDR r = DDR.doPost(OAPI + "/topapi/hide_field/remove", null, qs(access_token), body);
        return r != null && r.isSuccess();
    }

    @Override
    public Map listHideFields(String access_token, long size, long offset, Map body_ext) {
        Map body = new java.util.HashMap();
        body.put("size", size);
        body.put("offset", offset);
        merge(body, body_ext);
        return (Map) DDR.doPost(OAPI + "/topapi/hide_field/query", null, qs(access_token), body).getResult();
    }
}
