package com.hxhq.common.ad;

import com.hxhq.common.core.web.domain.AjaxResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import javax.naming.ldap.InitialLdapContext;
import javax.naming.ldap.LdapContext;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 纯JDK实现的AD域控核心接口（Spring Bean化）
 * 核心功能：1. 账号密码鉴权 2. 验证账号是否存在（基于只读账号）
 * 扩展功能：返回用户中文名、部门信息
 */
@Component
public class JdkADAuthUtil {
    private static final Logger log = LoggerFactory.getLogger(JdkADAuthUtil.class);
    private final ADProperties adProperties;
    private final String readOnlyUsername;
    private final String readOnlyPassword;

    @Value("${spring.profiles.active:dev}")
    private String activeProfile;

    // ====================== dev 环境多测试账号配置 ======================
    private static final Map<String, String> DEV_TEST_ACCOUNTS = new HashMap<>();
    // 测试账号的中文名和部门信息
    private static final Map<String, Map<String, String>> DEV_TEST_USER_INFO = new HashMap<>();

    static {
        DEV_TEST_ACCOUNTS.put("hxhq", "hxhq123");

        // 测试账号的扩展信息
        Map<String, String> hxhqInfo = new HashMap<>();
        hxhqInfo.put("displayName", "测试用户");
        hxhqInfo.put("mail", "hxhq@glpcd.com");
        DEV_TEST_USER_INFO.put("hxhq", hxhqInfo);
    }

    /**
     * 构造器注入AD配置
     */
    public JdkADAuthUtil(ADProperties adProperties) {
        this.adProperties = adProperties;
        this.readOnlyUsername = adProperties.getReadOnly().getUsername();
        this.readOnlyPassword = adProperties.getReadOnly().getPassword();
    }

    // ====================== 核心接口1：AD账号密码鉴权 ======================
    public AjaxResult validateAccount(String username, String password) {
        if (username == null || username.trim().isEmpty() || password == null || password.trim().isEmpty()) {
            log.debug("用户名或密码为空，鉴权失败");
            return AjaxResult.error("用户名或密码为空，鉴权失败");
        }

        // ====== dev 多账号校验 ======
        if ("dev".equals(activeProfile)) {
            String trimUser = username.trim();
            if (DEV_TEST_ACCOUNTS.containsKey(trimUser) && DEV_TEST_ACCOUNTS.get(trimUser).equals(password.trim())) {
                log.debug("DEV环境测试账号鉴权成功：{}", username);
                Map<String, Object> result = new HashMap<>();
                result.put("username", trimUser);
                result.put("displayName", DEV_TEST_USER_INFO.get(trimUser).get("displayName"));
                result.put("mail", DEV_TEST_USER_INFO.get(trimUser).get("mail"));
                result.put("message", "账号[" + username + "]鉴权成功");
                return AjaxResult.success(result);
            }else {
                log.debug("DEV环境测试账号鉴权失败：{}", username);
                return AjaxResult.error("账号[" + username + "]鉴权失败");
            }
        }

        LdapContext ldapContext = null;
        try {
            Hashtable<String, String> env = buildLDAPEnv(username, password);
            log.debug("开始鉴权，用户主体名：{}", env.get(Context.SECURITY_PRINCIPAL));

            // 1. 先验证账号密码
            ldapContext = new InitialLdapContext(env, null);

            // 2. 密码验证成功后，查询用户详细信息
            Map<String, Object> userInfo = getUserInfo(username);
            userInfo.put("message", "账号[" + username + "]鉴权成功");

            log.debug("账号[{}]鉴权成功，用户信息：{}", username, userInfo);
            return AjaxResult.success(userInfo);

        } catch (NamingException e) {
            String errorMsg = e.getMessage().toLowerCase();
            log.error("账号[{}]鉴权失败：{}", username, errorMsg);

            if (errorMsg.contains("data 525")) {
                return AjaxResult.error("鉴权失败原因：用户不存在");
            } else if (errorMsg.contains("data 52e")) {
                return AjaxResult.error("鉴权失败原因：密码错误");
            } else if (errorMsg.contains("data 532")) {
                return AjaxResult.error("鉴权失败原因：密码过期");
            } else if (errorMsg.contains("data 533")) {
                return AjaxResult.error("鉴权失败原因：账号禁用");
            } else if (errorMsg.contains("data 775")) {
                return AjaxResult.error("鉴权失败原因：账号锁定");
            }
            return AjaxResult.error("账号[" + username + "]鉴权失败");
        } finally {
            closeLdapContext(ldapContext);
        }
    }

    // ====================== 核心接口2：验证AD账号是否存在 ======================
    public AjaxResult checkAccountExists(String username) {
        if (username == null || username.trim().isEmpty()) {
            return AjaxResult.error("用户名不能为空，查询失败");
        }

        String trimUser = username.trim();

        // ====== dev 多账号存在校验 ======
        if ("dev".equals(activeProfile)) {
            // 支持纯账号 或 hxhq@xxx.com
            String pureUser = trimUser.contains("@") ? trimUser.split("@")[0] : trimUser;
            if (DEV_TEST_ACCOUNTS.containsKey(pureUser)) {
                log.debug("DEV环境测试账号存在：{}", username);
                Map<String, Object> result = new HashMap<>();
                result.put("username", pureUser);
                result.put("displayName", DEV_TEST_USER_INFO.get(pureUser).get("displayName"));
                result.put("mail", DEV_TEST_USER_INFO.get(pureUser).get("mail"));
                result.put("message", "账号[" + username + "]在AD域中存在");
                return AjaxResult.success(result);
            }else {
                return AjaxResult.error("账号[" + username + "]在AD域中不存在");

            }

        }

        LdapContext ldapContext = null;
        NamingEnumeration<SearchResult> results = null;
        try {
            Hashtable<String, String> env = buildLDAPEnvForQuery();
            ldapContext = new InitialLdapContext(env, null);

            SearchControls searchControls = new SearchControls();
            searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
            // 扩展返回属性：增加cn(中文名)、department(部门)、displayName(显示名)
            searchControls.setReturningAttributes(new String[]{
                    "sAMAccountName", "userPrincipalName", "cn", "department", "displayName", "mail"
            });
            searchControls.setTimeLimit(3000);

            String filter = trimUser.contains("@")
                    ? String.format("(userPrincipalName=%s)", escapeLDAPFilter(trimUser))
                    : String.format("(sAMAccountName=%s)", escapeLDAPFilter(trimUser));

            results = ldapContext.search(adProperties.getBaseDn(), filter, searchControls);

            if (results != null && results.hasMoreElements()) {
                SearchResult result = results.nextElement();
                Attributes attrs = result.getAttributes();

                // 封装用户信息
                Map<String, Object> userInfo = new HashMap<>();
                userInfo.put("username", trimUser);
                userInfo.put("displayName", getAttributeValue(attrs, "displayName"));
                userInfo.put("mail", getAttributeValue(attrs, "mail"));

                userInfo.put("message", "账号[" + username + "]在AD域中存在");

                return AjaxResult.success(userInfo);
            } else {
                return AjaxResult.error("账号[" + username + "]在AD域中不存在");
            }
        } catch (IllegalArgumentException e) {
            log.error("查询失败：{}", e.getMessage());
            return AjaxResult.error("查询失败：" + e.getMessage());
        } catch (NamingException e) {
            log.error("查询账号[{}]是否存在失败：{}", username, e.getMessage());
            if (e.getMessage().toLowerCase().contains("error code 49")) {
                return AjaxResult.error("查询失败原因：只读账号绑定失败");
            }
            return AjaxResult.error("查询账号[" + username + "]是否存在失败");
        } finally {
            closeNamingEnumeration(results);
            closeLdapContext(ldapContext);
        }
    }

    /**
     * 获取用户详细信息（鉴权成功后调用）
     */
    private Map<String, Object> getUserInfo(String username) throws NamingException {
        LdapContext ldapContext = null;
        NamingEnumeration<SearchResult> results = null;

        try {
            // 使用只读账号查询用户信息
            Hashtable<String, String> env = buildLDAPEnvForQuery();
            ldapContext = new InitialLdapContext(env, null);

            SearchControls searchControls = new SearchControls();
            searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
            searchControls.setReturningAttributes(new String[]{
                    "sAMAccountName", "cn", "department", "displayName", "mail",
            });
            searchControls.setTimeLimit(3000);

            String filter = username.contains("@")
                    ? String.format("(userPrincipalName=%s)", escapeLDAPFilter(username))
                    : String.format("(sAMAccountName=%s)", escapeLDAPFilter(username));

            results = ldapContext.search(adProperties.getBaseDn(), filter, searchControls);

            Map<String, Object> userInfo = new HashMap<>();
            userInfo.put("username", username);

            if (results != null && results.hasMoreElements()) {
                SearchResult result = results.nextElement();
                Attributes attrs = result.getAttributes();
                userInfo.put("displayName", getAttributeValue(attrs, "displayName"));
                userInfo.put("mail", getAttributeValue(attrs, "mail"));
            }

            return userInfo;
        } finally {
            closeNamingEnumeration(results);
            closeLdapContext(ldapContext);
        }
    }

    /**
     * 安全获取AD属性值（处理空值）
     */
    private String getAttributeValue(Attributes attrs, String attrName) {
        if (attrs == null) {
            return "";
        }

        Attribute attr = attrs.get(attrName);
        if (attr == null) {
            return "";
        }

        try {
            return attr.get() != null ? attr.get().toString() : "";
        } catch (NamingException e) {
            log.warn("获取属性[{}]失败", attrName, e);
            return "";
        }
    }

    // ====================== 以下是原有工具方法，完全不变 ======================
    private Hashtable<String, String> buildLDAPEnv(String username, String password) {
        Hashtable<String, String> env = new Hashtable<>();
        env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");

        String ldapUrl = adProperties.isUseSsl()
                ? "ldaps://" + adProperties.getServerHost() + ":" + adProperties.getServerPort()
                : "ldap://" + adProperties.getServerHost() + ":" + adProperties.getServerPort();
        env.put(Context.PROVIDER_URL, ldapUrl);
        env.put(Context.SECURITY_AUTHENTICATION, "simple");

        String userPrincipal = username;
        if (!username.contains("@")) {
            String domainSuffix = adProperties.getBaseDn().replaceAll("DC=", "").replace(",", ".");
            userPrincipal = username + "@" + domainSuffix;
        }
        env.put(Context.SECURITY_PRINCIPAL, userPrincipal);
        env.put(Context.SECURITY_CREDENTIALS, password);
        env.put("com.sun.jndi.ldap.connect.timeout", String.valueOf(adProperties.getConnectTimeout()));
        return env;
    }

    private Hashtable<String, String> buildLDAPEnvForQuery() {
        Hashtable<String, String> env = new Hashtable<>();
        env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");

        String ldapUrl = adProperties.isUseSsl()
                ? "ldaps://" + adProperties.getServerHost() + ":" + adProperties.getServerPort()
                : "ldap://" + adProperties.getServerHost() + ":" + adProperties.getServerPort();
        env.put(Context.PROVIDER_URL, ldapUrl);
        env.put(Context.SECURITY_AUTHENTICATION, "simple");

        if (readOnlyUsername == null || readOnlyUsername.trim().isEmpty()
                || readOnlyPassword == null || readOnlyPassword.trim().isEmpty()) {
            throw new IllegalArgumentException("只读账号未配置，无法执行AD账号查询！");
        }

        String domainSuffix = adProperties.getBaseDn().replaceAll("DC=", "").replace(",", ".");
        String readOnlyUpn = readOnlyUsername + "@" + domainSuffix;
        env.put(Context.SECURITY_PRINCIPAL, readOnlyUpn);
        env.put(Context.SECURITY_CREDENTIALS, readOnlyPassword);
        env.put("com.sun.jndi.ldap.connect.timeout", String.valueOf(adProperties.getConnectTimeout()));
        return env;
    }

    private String escapeLDAPFilter(String input) {
        if (input == null || input.isEmpty()) return input;
        return input.replace("\\", "\\5c")
                .replace("(", "\\28")
                .replace(")", "\\29")
                .replace("*", "\\2a")
                .replace("\0", "\\00");
    }

    private void closeLdapContext(LdapContext ctx) {
        if (ctx != null) {
            try { ctx.close(); } catch (NamingException e) { log.warn("关闭LDAP连接失败"); }
        }
    }

    private void closeNamingEnumeration(NamingEnumeration<?> ne) {
        if (ne != null) {
            try { ne.close(); } catch (NamingException e) { log.warn("关闭LDAP结果集失败"); }
        }
    }


    /**
     * 查询AD用户【所有属性】，返回完整Map，用于查看该用户到底有哪些字段
     */
    public AjaxResult getUserAllAttributes(String username) {
        if (username == null || username.trim().isEmpty()) {
            return AjaxResult.error("用户名不能为空");
        }

        String trimUser = username.trim();

        LdapContext ldapContext = null;
        NamingEnumeration<SearchResult> results = null;
        Map<String, Object> allAttrMap = new HashMap<>();

        try {
            Hashtable<String, String> env = buildLDAPEnvForQuery();
            ldapContext = new InitialLdapContext(env, null);

            SearchControls searchControls = new SearchControls();
            searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
            // 【关键】返回 *所有* 属性，不指定字段
            searchControls.setReturningAttributes(null);
            searchControls.setTimeLimit(5000);

            String filter = trimUser.contains("@")
                    ? String.format("(userPrincipalName=%s)", escapeLDAPFilter(trimUser))
                    : String.format("(sAMAccountName=%s)", escapeLDAPFilter(trimUser));

            results = ldapContext.search(adProperties.getBaseDn(), filter, searchControls);

            if (results != null && results.hasMoreElements()) {
                SearchResult searchResult = results.next();
                Attributes attributes = searchResult.getAttributes();

                // 遍历所有属性
                NamingEnumeration<String> ids = attributes.getIDs();
                while (ids.hasMoreElements()) {
                    String id = ids.next();
                    Attribute attr = attributes.get(id);
                    if (attr == null) {
                        allAttrMap.put(id, null);
                        continue;
                    }
                    try {
                        // 单个值
                        Object value = attr.get();
                        allAttrMap.put(id, value != null ? value.toString() : null);
                    } catch (Exception e) {
                        // 多值属性（如memberOf）
                        try {
                            NamingEnumeration<?> ne = attr.getAll();
                            StringBuilder sb = new StringBuilder();
                            while (ne.hasMoreElements()) {
                                sb.append(ne.next()).append("; ");
                            }
                            allAttrMap.put(id, sb.toString());
                        } catch (Exception ex) {
                            allAttrMap.put(id, "无法解析");
                        }
                    }
                }

                log.info("AD用户[{}]所有属性：{}", username, allAttrMap);
                return AjaxResult.success("获取成功", allAttrMap);
            } else {
                return AjaxResult.error("用户不存在");
            }

        } catch (Exception e) {
            log.error("获取用户所有属性异常", e);
            return AjaxResult.error("获取失败：" + e.getMessage());
        } finally {
            closeNamingEnumeration(results);
            closeLdapContext(ldapContext);
        }
    }


    public static void main(String[] args) {

        //        // 2. 初始化工具类（★核心★：传入只读账号，替换为你的实际只读账号/密码）
//        JdkADAuthUtil adAuthUtil = new JdkADAuthUtil(adConfig);
//
//        // 3. 测试账号
//        String existUsername = "ELNtest01";       // 存在的账号
//        String nonExistUsername = "test_not_exists"; // 不存在的账号
//        String testPassword = "Abcd1234";        // 测试账号密码
//
//        // 4. 测试接口2：验证账号是否存在（无需用户密码）
//        AjaxResult exists = adAuthUtil.checkAccountExists(existUsername);
//        System.out.println("===== 验证账号[" + existUsername + "]是否存在：" + (exists.isSuccess() ? "存在" : "不存在") + " =====");
//
//        AjaxResult exists1 = adAuthUtil.checkAccountExists("ELNtest01@glpcd.com");
//        System.out.println("===== 验证账号[" + existUsername + "]是否存在：" + (exists1.isSuccess()? "存在" : "不存在") + " =====");
//
//        AjaxResult nonExists = adAuthUtil.checkAccountExists(nonExistUsername);
//        System.out.println("===== 验证账号[" + nonExistUsername + "]是否存在：" + (nonExists.isSuccess() ? "存在" : "不存在") + " =====");
//
//        // 5. 测试接口1：账号密码鉴权
//        AjaxResult authSuccess = adAuthUtil.validateAccount(existUsername, testPassword);
//        System.out.println("===== 账号[" + existUsername + "]鉴权结果：" + (authSuccess.isSuccess() ? "成功" : "失败") + " =====");
//        AjaxResult authSuccess1 = adAuthUtil.validateAccount(existUsername+"@glpcd.com", testPassword);
//        System.out.println("===== 账号[" + existUsername + "]鉴权结果：" + (authSuccess1.isSuccess() ? "成功" : "失败") + " =====");
//
//
//        AjaxResult authFail = adAuthUtil.validateAccount(existUsername, "wrong_password");
//        System.out.println("===== 账号[" + existUsername + "]错误密码鉴权结果：" + (authFail.isSuccess() ? "成功" : "失败") + " =====");

    }
}