package com.ruoyi.cms.util.oauth;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.alibaba.fastjson2.TypeReference;
import com.ruoyi.common.core.domain.entity.tymh.authority.QxUserRole;
import com.ruoyi.common.core.domain.entity.tymh.nwToken.NwTokenResult;
import com.ruoyi.common.core.domain.entity.tymh.nwToken.NwUserInfoResult;
import com.ruoyi.common.core.domain.entity.tymh.wwToken.WwTokenResult;
import com.ruoyi.common.core.domain.entity.tymh.wwToken.WwTyInfo;
import com.ruoyi.common.core.domain.entity.tymh.wwToken.WwUserLogin;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.crypto.CryptoUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * OAuth2.0 认证工具类（合并相似方法）
 */
@Component
public class OauthClient {

    private static final Logger log = LoggerFactory.getLogger(OauthClient.class);
    /*====================== 内网 ======================*/
    @Value("${oauth.usptnw.nwGatewayGetTokenUrl}")
    private String nwGatewayGetTokenUrl;
    @Value("${oauth.usptnw.nwGatewayGetUserInfoUrl}")
    private String nwGatewayGetUserInfoUrl;

    /*====================== 外网 ======================*/
    @Value("${oauth.usptww.wwRegisterPostUrl}")
    private String wwRegisterPostUrl;
    @Value("${oauth.usptww.wwTokenPostUrl}")
    private String wwTokenPostUrl;
    @Value("${oauth.usptww.wwQueryWebUserInfo}")
    private String wwQueryWebUserInfo;
    @Value("${oauth.usptww.wwQueryWebPersonalInfoPostUrl}")
    private String wwQueryWebPersonalInfoPostUrl;
    @Value("${oauth.usptww.wwQueryWebEnterpriseInfoPostUrl}")
    private String wwQueryWebEnterpriseInfoPostUrl;

    /*====================== 统一门户 ======================*/
    @Value("${oauth.tyAddUserUrl}")
    private String tyAddUserUrl;
    @Value("${oauth.tyQueryUserSysListUrl}")
    private String tyQueryUserSysListUrl;
    @Value("${oauth.tyQueryUserRoleListUrl}")
    private String tyQueryUserRoleListUrl;
    @Value("${oauth.tyQueryRoleInfoUrl}")
    private String tyQueryRoleInfoUrl;
    @Value("${oauth.tyQueryUserInfo}")
    private String tyQueryUserInfo;
    @Value("${oauth.tyQueryUnitInfo}")
    private String tyQueryUnitInfo;
    @Value("${oauth.appid}")
    private String appid;
    @Value("${oauth.clientsecretkey}")
    private String clientsecretkey;
    @Value("${oauth.connect-timeout:10}")
    private int connectTimeout;
    @Value("${oauth.read-timeout:30}")
    private int readTimeout;
    @Value("${oauth.write-timeout:30}")
    private int writeTimeout;

    @Autowired
    private RedisCache redisCache;

    private final String MH_JBD_TOKEN="mhjdb:token:";

    /**
     * 获取经办段token
     */
    public NwTokenResult nwGetToken(String code) throws IOException, TimeoutException {
        if (StringUtils.isEmpty(code)) {
            log.error("获取Token失败：授权码code不能为空");
            throw new IllegalArgumentException("授权码code不能为空");
        }

        String decryptedCode;
        try {
            decryptedCode = CryptoUtil.sm2Decrypt(clientsecretkey, code);
            if (StringUtils.isEmpty(decryptedCode)) {
                throw new RuntimeException("授权码解密后为空");
            }
        } catch (Exception e) {
            log.error("授权码解密失败 | 原始code: {}", code, e);
            throw new RuntimeException("授权码解密失败：" + e.getMessage(), e);
        }

        //判断redis中是否存在获取token
        String redisKey=MH_JBD_TOKEN+decryptedCode;
        NwTokenResult cacheResult=redisCache.getCacheObject(redisKey);
        if(cacheResult!=null){
            long ttlSeconds = redisCache.getExpire(redisKey);
            if (ttlSeconds > 60) {
                return cacheResult;
            } else {
                redisCache.deleteObject(redisKey);
                throw new RuntimeException("令牌剩余时间不足（" + ttlSeconds + "秒），已清理旧数据");
            }
        }

        Map<String, Object> params = new HashMap<>(2);
        params.put("appid", appid);
        params.put("code", decryptedCode);

        NwTokenResult result = executePostRequest(
                nwGatewayGetTokenUrl,
                params,
                new TypeReference<Response<NwTokenResult>>() {},
                "获取Token"
        );

        if (StringUtils.isEmpty(result.getAccessToken())) {
            redisCache.deleteObject(decryptedCode);
            throw new RuntimeException("Token获取失败：返回的accessToken为空");
        }

        redisCache.setCacheObject(redisKey,result,safeLongToInt(result.getExpiresIn()), TimeUnit.MINUTES);
        return result;
    }

    /**
     * 获取经办段用户id
     */
    public NwUserInfoResult nwGetUserInfo(String accessToken) throws IOException, TimeoutException {
        Map<String, Object> params = new HashMap<>(2);
        params.put("appid", appid);
        params.put("access_token", accessToken);

        return executePostRequest(
                nwGatewayGetUserInfoUrl,
                params,
                new TypeReference<Response<NwUserInfoResult>>() {},
                "获取用户信息"
        );
    }

    /**
     * 根据用户id查询用户角色列表
     * @param userid
     * @return
     * @throws IOException
     * @throws TimeoutException
     */
    public List<QxUserRole> getUserRoleList(Long userid) throws IOException, TimeoutException {
        Map<String, Object> params = new HashMap<>(2);
        params.put("appid", appid);
        params.put("userid", String.valueOf(userid));

        return executePostRequestResultList(
                tyQueryUserRoleListUrl,
                params,
                QxUserRole.class,
                "根据用户id获取用户角色列表"
        );
    }

    /**
     * 公共POST请求执行方法
     */
    private <T> T executePostRequest(String url, Map<String, Object> params,
                                     TypeReference<Response<T>> typeReference, String operationName)
            throws IOException, TimeoutException {
        String responseJson = HttpUtils.doPostJson(
                url,
                params,
                connectTimeout,
                readTimeout,
                writeTimeout
        );
        return validateResponse(responseJson, operationName, typeReference);
    }

    /**
     * 公共POST请求执行方法处理List
     */
    private <T> List<T> executePostRequestResultList(String url, Map<String, Object> params,
                                              Class<T> clazz, String operationName)
            throws IOException, TimeoutException {
        String responseJson = HttpUtils.doPostJson(
                url,
                params,
                connectTimeout,
                readTimeout,
                writeTimeout
        );
        return parseMapOrDataArray(responseJson, operationName, clazz);
    }

    /**
     * 传递heades
     * @param url
     * @param params
     * @param typeReference
     * @param operationName
     * @return
     * @param <T>
     * @throws IOException
     * @throws TimeoutException
     */
    private <T> T executePostRequestHeaders(String url, Map<String, Object> params,Map<String,String> headers,
                                     TypeReference<Response<T>> typeReference, String operationName)
            throws IOException, TimeoutException {
        String responseJson = HttpUtils.doPostJson(
                url,
                params,
                headers,
                connectTimeout,
                readTimeout,
                writeTimeout
        );
        return validateResponse(responseJson, operationName, typeReference);
    }

    /**
     * 简化后的响应校验方法（已移除网关返回码判断）
     */
    private <T> T validateResponse(String responseJson, String operationName, TypeReference<Response<T>> typeReference) {
        // 1. 校验响应空值
        if (StringUtils.isEmpty(responseJson)) {
            String errorMsg = operationName + "失败：接口返回空";
            log.error(errorMsg);
            throw new RuntimeException(errorMsg);
        }
        log.debug("{}返回数据: {}", operationName, responseJson);

        // 2. 解析响应并校验基础格式
        Response<T> gatewayResponse = JSON.parseObject(responseJson, typeReference);
        if (gatewayResponse == null) {
            String errorMsg = operationName + "失败：响应格式错误";
            log.error("{} | 原始响应: {}", errorMsg, responseJson);
            throw new RuntimeException(errorMsg);
        }

        // 3. 校验业务数据（移除了网关appcode的判断逻辑）
        T data = gatewayResponse.getData();
        if (data == null) {
            String errorMsg = operationName + "失败：业务数据为空";
            log.error("{} | 原始响应: {}", errorMsg, responseJson);
            throw new RuntimeException(errorMsg);
        }

        return data;
    }

    /**
     * 解析List
     * @param jsonStr
     * @param clazz
     * @return
     */
    public static <T> List<T> parseMapOrDataArray(String jsonStr, String operationName, Class<T> clazz) {
        if (StringUtils.isEmpty(jsonStr)) {
            String errorMsg = operationName + "失败：接口返回空";
            log.error(errorMsg);
            throw new RuntimeException(errorMsg);
        }
        JSONObject jsonObject;
        try {
            jsonObject = JSON.parseObject(jsonStr);
        } catch (Exception e) {
            String errorMsg = operationName + "失败：响应格式错误（非合法 JSON）";
            log.error("{} | 原始响应: {}", errorMsg, jsonStr, e);
            throw new RuntimeException(errorMsg, e);
        }

        if (jsonObject == null) {
            String errorMsg = operationName + "失败：响应格式错误（解析后为 null）";
            log.error("{} | 原始响应: {}", errorMsg, jsonStr);
            throw new RuntimeException(errorMsg);
        }

        JSONArray targetArray = getJSONArraySafe(jsonObject, "map");
        if (targetArray == null || targetArray.isEmpty()) {
            targetArray = getJSONArraySafe(jsonObject, "data");
        }

        if (targetArray == null || targetArray.isEmpty()) {
            String errorMsg = operationName + "失败：业务数据为空（map/data 字段无有效数组）";
            log.error("{} | 原始响应: {}", errorMsg, jsonStr);
            throw new RuntimeException(errorMsg);
        }

        List<T> dataList;
        try {
            dataList = JSON.parseArray(targetArray.toJSONString(), clazz);
        } catch (Exception e) {
            String errorMsg = operationName + "失败：数据解析失败（数组元素与目标类不匹配）";
            log.error("{} | 原始响应: {} | 目标类: {}", errorMsg, jsonStr, clazz.getName(), e);
            throw new RuntimeException(errorMsg, e);
        }

        if (dataList == null || dataList.isEmpty()) {
            String errorMsg = operationName + "失败：解析后业务数据为空";
            log.error("{} | 原始响应: {}", errorMsg, jsonStr);
            throw new RuntimeException(errorMsg);
        }

        return dataList;
    }

    /**
     * 避免空值和类型转换异常
     * @param jsonObject
     * @param key
     * @return
     */
    private static JSONArray getJSONArraySafe(JSONObject jsonObject, String key) {
        if (jsonObject == null || StringUtils.isEmpty(key)) {
            return null;
        }
        if (!jsonObject.containsKey(key)) {
            return null;
        }
        Object value = jsonObject.get(key);
        return (value instanceof JSONArray) ? (JSONArray) value : null;
    }

    /**
     * 从响应头中提取Cookie（工具方法）
     */
    private String extractCookieFromHeaders(Map<String, List<String>> headers) {
        if (headers == null || headers.isEmpty()) {
            log.warn("响应头为空，无法提取Cookie");
            return "";
        }

        List<String> cookies = headers.get("Set-Cookie");
        if (cookies == null || cookies.isEmpty()) {
            log.warn("响应头中未包含Set-Cookie信息");
            return "";
        }

        // 拼接Cookie（只保留name=value部分，忽略路径、过期时间等附加信息）
        StringBuilder cookieSb = new StringBuilder();
        for (String cookie : cookies) {
            if (cookieSb.length() > 0) {
                cookieSb.append("; ");
            }
            int semicolonIndex = cookie.indexOf(';');
            if (semicolonIndex > 0) {
                cookieSb.append(cookie.substring(0, semicolonIndex));
            } else {
                cookieSb.append(cookie);
            }
        }
        return cookieSb.toString();
    }

    /**
     * 互联网-获取token
     * @param wwUserLogin
     * @return
     * @throws Exception
     */
    public WwTokenResult wwGetToken(WwUserLogin wwUserLogin) throws Exception {
        if (wwUserLogin == null) {
            log.error("获取互联网Token失败：登录参数不能为空");
            throw new IllegalArgumentException("登录参数不能为空");
        }
        if (StringUtils.isEmpty(wwUserLogin.getUsername())) {
            log.error("获取互联网Token失败：身份证号/统一社会信用代码不能为空");
            throw new IllegalArgumentException("身份证号/统一社会信用代码不能为空");
        }
        if (StringUtils.isEmpty(wwUserLogin.getUsertype())) {
            log.error("获取互联网Token失败：用户类型（usertype）不能为空");
            throw new IllegalArgumentException("用户类型（usertype）不能为空");
        }

        Map<String, Object> params = new HashMap<>(4);
        params.put("username", wwUserLogin.getUsername());
        params.put("usertype", wwUserLogin.getUsertype());
        if (StringUtils.isNotEmpty(wwUserLogin.getLogontype())) {
            params.put("logontype", wwUserLogin.getLogontype());
        }
        if (StringUtils.isNotEmpty(wwUserLogin.getLogonchannel())) {
            params.put("logonchannel", wwUserLogin.getLogonchannel());
        }

        // 发送请求并获取响应体+响应头（包含Cookie）
        HttpUtils.ResponseWrapper responseWrapper = HttpUtils.doPostJsonWithHeaders(
                wwTokenPostUrl,
                params,
                connectTimeout,
                readTimeout,
                writeTimeout
        );

        String responseJson = responseWrapper.getResponseBody();
        WwTokenResult tokenResult = validateResponse(
                responseJson,
                "获取互联网Token",
                new TypeReference<Response<WwTokenResult>>() {}
        );

        if (tokenResult == null) {
            log.error("获取互联网Token失败：接口返回业务数据为空");
            throw new Exception("获取互联网Token失败：返回数据异常");
        }

        // 提取Cookie并设置到tokenResult中
        String cookie = extractCookieFromHeaders(responseWrapper.getHeaders());
        tokenResult.setSessionCookie(cookie);

        log.info("获取互联网Token及Cookie成功 | username：{} | accessToken：{} | cookie：{}",
                wwUserLogin.getUsername(), tokenResult.getAccessToken(), cookie);

        return tokenResult;
    }

    /**
     * 互联网-端根据token获取用户信息
     * @return
     * @throws IOException
     * @throws TimeoutException
     */
    public WwTyInfo wwGetUserInfo(WwTokenResult wwTokenResult) throws IOException, TimeoutException{
        if(wwTokenResult==null){
            throw new IllegalArgumentException("wwTokenResult不能为空");
        }
        //headers
        Map<String, String> headers = new HashMap<>();
        /*headers.put("Access-Token", wwTokenResult.getAccessToken());
        headers.put("Cookie", wwTokenResult.getSessionCookie());*/


        //parm
        Map<String, Object> params = new HashMap<>(1);
        params.put("Access-Token",wwTokenResult.getAccessToken());
        return executePostRequestHeaders(
                wwQueryWebUserInfo,
                params,
                headers,
                new TypeReference<OauthClient.Response<WwTyInfo>>() {},
                "获取互联网用户信息"
        );
    }

    /**
     * Long 转 int
     */
    private int safeLongToInt(Long value) {
        if (value == null) {
            throw new ServiceException("门户 Token 有效期不能为空");
        }
        // 校验是否超过 int 最大值（实际场景几乎不会触发）
        if (value > Integer.MAX_VALUE) {
            return Integer.MAX_VALUE;
        }
        // 校验是否为负数（无效有效期）
        if (value < 0) {
            throw new ServiceException("门户 Token 有效期不能为负数");
        }
        return value.intValue();
    }

    /**
     * 静态返回解析
     * @param <T>
     */
    public static class Response<T> {
        private String appcode;
        private String msg;
        private T data;

        public String getAppcode() { return appcode; }
        public String getMsg() { return msg; }
        public T getData() { return data; }

        public void setAppcode(String appcode) { this.appcode = appcode; }
        public void setMsg(String msg) { this.msg = msg; }
        public void setData(T data) { this.data = data; }
    }
}