package org.dromara.member.utils;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.Base64;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.UUID;

/**
 * JWT工具类
 *
 * @author pengles
 */
public class JwtUtil {
    private final String secret;

    public JwtUtil(String secret) {
        this.secret = secret;
    }

    public String generateToken(String subject, long ttlSeconds, String jti) {
        long now = Instant.now().getEpochSecond();
        long exp = now + ttlSeconds;
        Map<String, Object> header = new LinkedHashMap<>();
        header.put("alg", "HS256");
        header.put("typ", "JWT");
        Map<String, Object> payload = new LinkedHashMap<>();
        payload.put("iss", "ruoyi-app");
        payload.put("sub", subject);
        payload.put("iat", now);
        payload.put("exp", exp);
        payload.put("jti", jti == null ? UUID.randomUUID().toString() : jti);
        String h = base64Url(Json.write(header));
        String p = base64Url(Json.write(payload));
        String signing = h + "." + p;
        String sig = base64Url(hmacSha256(signing, secret));
        return signing + "." + sig;
    }

    public boolean verify(String token) {
        try {
            String[] parts = token.split("\\.");
            if (parts.length != 3) return false;
            String signing = parts[0] + "." + parts[1];
            String calc = base64Url(hmacSha256(signing, secret));
            return slowEquals(calc, parts[2]);
        } catch (Exception e) {
            return false;
        }
    }

    public Map<String, Object> parsePayload(String token) {
        String[] parts = token.split("\\.");
        if (parts.length != 3) return Map.of();
        String json = new String(Base64.getUrlDecoder().decode(parts[1]), StandardCharsets.UTF_8);
        return Json.read(json);
    }

    private static boolean slowEquals(String a, String b) {
        if (a == null || b == null) return false;
        if (a.length() != b.length()) return false;
        int r = 0;
        for (int i = 0; i < a.length(); i++) r |= a.charAt(i) ^ b.charAt(i);
        return r == 0;
    }

    private static byte[] hmacSha256(String data, String key) {
        try {
            Mac mac = Mac.getInstance("HmacSHA256");
            mac.init(new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
            return mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private static String base64Url(String s) {
        return Base64.getUrlEncoder().withoutPadding().encodeToString(s.getBytes(StandardCharsets.UTF_8));
    }

    private static String base64Url(byte[] b) {
        return Base64.getUrlEncoder().withoutPadding().encodeToString(b);
    }

    static class Json {
        public static String write(Map<String, Object> map) {
            StringBuilder sb = new StringBuilder("{");
            boolean first = true;
            for (Map.Entry<String, Object> e : map.entrySet()) {
                if (!first) sb.append(",");
                sb.append("\"").append(e.getKey()).append("\":");
                Object v = e.getValue();
                if (v instanceof Number || v instanceof Boolean) {
                    sb.append(v.toString());
                } else {
                    sb.append("\"").append(String.valueOf(v).replace("\"", "\\\"")).append("\"");
                }
                first = false;
            }
            sb.append("}");
            return sb.toString();
        }

        @SuppressWarnings("unchecked")
        public static Map<String, Object> read(String json) {
            try {
                return org.dromara.common.json.utils.JsonUtils.parseObject(json, Map.class);
            } catch (Exception e) {
                return Map.of();
            }
        }
    }
}

