package com.backendsys.modules.common.config.security.utils;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.backendsys.exception.CustException;
import com.backendsys.modules.common.config.redis.utils.RedisUtil;
import com.backendsys.modules.common.config.security.entity.SecurityAppUserInfo;
import com.backendsys.modules.common.config.security.entity.SecurityUserInfo;
import com.backendsys.modules.common.enums.MatchType;
import com.backendsys.modules.system.entity.TokenCatch;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Base64;
import java.util.List;

/**
 * [SecurityUtil 使用方法]
 *
 * 权限工具类
 * SecurityUtil.hasPermission("3.2.1")
 * SecurityUtil.hasPermissions(Arrays.asList("3.2.1", "3.2.2"))
 * SecurityUtil.hasPermissions(Arrays.asList("3.2.1", "3.2.2"), MatchType.OR)
 *
 * 权限注解
 * @PreAuthorize("@sr.hasPermission('3.2.1')")
 * @PreAuthorize("@sr.hasPermissions(T(java.util.Arrays).asList('3.2.1', '3.2.2'))")
 * @PreAuthorize("@sr.hasPermissions(T(java.util.Arrays).asList('3.2.1', '3.2.2'), T(com.backendsys.modules.common.enums.MatchType).OR)")
 */
@Service("sr")
public class SecurityUtil {

    @Value("${REDIS_LOGIN_TOKEN_PREFIX}")
    private String REDIS_LOGIN_TOKEN_PREFIX;

    @Autowired
    private RedisUtil redisUtil;

    private static final String SECRET_KEY = "452948404D635166546A576E5A7134743777217A25432A462D4A614E64526755";

    private static SecretKey getSignInKey() {
        byte[] bytes = Base64.getDecoder().decode(SECRET_KEY.getBytes(StandardCharsets.UTF_8));
        return new SecretKeySpec(bytes, "HmacSHA256");
    }

    /**
     * 获得当前登录用户ID
     */
    public static Long getUserId() {
        SecurityUserInfo userInfo = getUserInfo();
        return userInfo.getUser_id();
    }

    /**
     * 获得当前登录 Token
     */
    public static String getToken() {
        // 如果是在 websocket 周期中，是获取不到 context 的
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        return Convert.toStr(authentication.getDetails());
    }

    /**
     * 获得当前登录用户基础信息
     * 此处的用户信息是根据 Token 解析出来的，因此无法实时更新，仅能用于获取一些基本信息 (以下是完整信息，实际信息见 SecurityUserInfo)
     * {"id":1,"user_id":1,"username":"admin","phone":"13670511519","phone_area_code":"86","nickname":"超人","email":"admin@qq.com","gender":1,"avatar":"/uploads/20240430/20240430143807.png","last_login_ip":"0:0:0:0:0:0:0:1","last_login_uuid":"5b34e58d-c884-4b43-9ce1-1c85b1136dcb","last_login_time":"2024-10-25 17:26:55","is_super":1,"point_balance":9870,"status":1,"audit_status":2,"audit_note":"同意通过备注","create_time":1689734700000,"update_time":1729848415000,"del_flag":-1,"roles":[{"id":1,"role_name":"管理员"}],"token_expiration":1729934998492}
     */
    public static SecurityUserInfo getUserInfo(){
        String token = getToken();
        if (StrUtil.isEmpty(token)) throw new CustException("getUserInfo() token is empty.");
        if (token.contains("SessionId") || token.contains("RemoteIpAddress")) throw new CustException("getUserInfo() need token.");
        Claims tokenInfo = Jwts.parser().verifyWith(getSignInKey()).build().parseSignedClaims(token).getPayload();
        JSONObject userInfo = JSONUtil.parseObj(tokenInfo.get("userInfo"));
        String target = Convert.toStr(tokenInfo.get("target"));
        SecurityUserInfo securityUserInfo = JSONUtil.toBean(userInfo, SecurityUserInfo.class);
        securityUserInfo.setTarget(target);
        return securityUserInfo;
    }

    public static SecurityAppUserInfo getAppUserInfo(){
        String token = getToken();
        if (StrUtil.isEmpty(token)) throw new CustException("getUserInfo() token is empty.");
        if (token.contains("SessionId") || token.contains("RemoteIpAddress")) throw new CustException("getUserInfo() need token.");
        Claims tokenInfo = Jwts.parser().verifyWith(getSignInKey()).build().parseSignedClaims(token).getPayload();
        JSONObject userInfo = JSONUtil.parseObj(tokenInfo.get("userInfo"));
        SecurityAppUserInfo securityAppUserInfo = JSONUtil.toBean(userInfo, SecurityAppUserInfo.class);
        return securityAppUserInfo;
    }


    /**
     * 判断是否超级管理员
     */
    public static Boolean isSuper() {
        SecurityUserInfo userInfo = getUserInfo();
        return userInfo.getIs_super() != null && userInfo.getIs_super() == 1;
    }

//    public static Boolean hasPermission(String permission) {
//        SecurityUserInfo userInfo = getUserInfo();
//        List<String> permission_ids = userInfo.getPermission_ids();
//        return permission_ids.contains(permission);
//    }

    /**
     * 判断是否具备权限 (单个)
     * - SecurityUtil.hasPermission("3.2.1")
     */
    private List<String> getPermissionIds() {
        SecurityUserInfo userInfo = getUserInfo();
        String uuid = userInfo.getLast_login_uuid();
        String redis_value = redisUtil.getCacheObject(REDIS_LOGIN_TOKEN_PREFIX + uuid);
        TokenCatch token_catch = JSONUtil.toBean(redis_value, TokenCatch.class);
        List<String> permission_ids = token_catch.getPermission_ids(); // Arrays.asList(permission_ids_str.split(","));
        return permission_ids;
    }

    /**
     * 判断单个用户权限 (超管直接通过)
     * securityUtil.hasPermission("1.1.6")
     */
    public Boolean hasPermission(String permission) {
        if (isSuper()) return true;
        List<String> permission_ids = getPermissionIds();
        return permission_ids != null && permission_ids.contains(permission);
    }

    /**
     * 判断是否具备用户权限 (多个) (默认 AND) (超管直接通过)
     * - securityUtil.hasPermissions(Arrays.asList("3.2.1", "3.2.2"))
     * - securityUtil.hasPermissions(Arrays.asList("3.2.1", "3.2.2"), MatchType.OR)
     * - 注意：权限储存在Token中，更新权限需要重新登录
     */
    public Boolean hasPermissions(List<String> permis) {
        return hasPermissions(permis, MatchType.AND);
    }
    public Boolean hasPermissions(List<String> permis, MatchType matchType) {
        if (isSuper()) return true;
        List<String> permission_ids = getPermissionIds();
        if (permission_ids != null) {
            if (matchType.equals(MatchType.AND)) {
                return permis.stream().allMatch(permission_ids::contains);
            } else if (matchType.equals(MatchType.OR)) {
                return permis.stream().anyMatch(permission_ids::contains);
            }
        }
        return false;
    }

}
