import java.io.*;
import java.net.*;
import java.nio.charset.StandardCharsets;
import java.util.*;

    public class LibreAuth {
    public String name;
    public String ownerid;
    public String version;
    public String url;
    public String sessionid;
    public String enckey;
    public String hash;

        public LibreAuth(String name, String ownerid, String version, String url) {
        this.name = name;
        this.ownerid = ownerid;
        this.version = version;
        this.url = url.endsWith("/") ? url : url + "/";
    }

    public Map<String, Object> init() throws Exception {
        if (enckey == null || enckey.isEmpty()) {
            enckey = randomHex(18).substring(0, 35);
        }
        Map<String, String> data = base();
        data.put("type", "init");
        data.put("ver", version);
        data.put("enckey", enckey);
        if (hash != null && !hash.isEmpty()) data.put("hash", hash);
        Map<String, Object> res = req(data);
        if (!Boolean.TRUE.equals(res.get("success"))) {
            throw new Exception(String.valueOf(res.get("message")));
        }
        sessionid = String.valueOf(res.get("sessionid"));
        return res;
    }

    public Map<String, Object> login(String user, String pass, String hwid) throws Exception {
        Map<String, String> data = base();
        data.put("type", "login");
        data.put("username", user);
        data.put("pass", pass);
        data.put("hwid", hwid == null ? "" : hwid);
        return auth(data);
    }

    public Map<String, Object> license(String key, String hwid) throws Exception {
        Map<String, String> data = base();
        data.put("type", "license");
        data.put("key", key);
        data.put("hwid", hwid == null ? "" : hwid);
        return auth(data);
    }

    public Map<String, Object> register(String user, String pass, String key, String hwid) throws Exception {
        Map<String, String> data = base();
        data.put("type", "register");
        data.put("username", user);
        data.put("pass", pass);
        data.put("key", key);
        data.put("hwid", hwid == null ? "" : hwid);
        return auth(data);
    }

    private Map<String, Object> auth(Map<String, String> data) throws Exception {
        Map<String, Object> res = req(data);
        if (!Boolean.TRUE.equals(res.get("success"))) {
            throw new Exception(String.valueOf(res.get("message")));
        }
        Object info = res.get("info");
        if (info instanceof Map) return (Map<String, Object>) info;
        return res;
    }

    private Map<String, String> base() {
        Map<String, String> data = new LinkedHashMap<>();
        data.put("name", name);
        data.put("ownerid", ownerid);
        if (sessionid != null && !sessionid.isEmpty()) data.put("sessionid", sessionid);
        return data;
    }

    @SuppressWarnings("unchecked")
    private Map<String, Object> req(Map<String, String> data) throws Exception {
        String body = encode(data);
        HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
        conn.setRequestMethod("POST");
        conn.setDoOutput(true);
        conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
        conn.setConnectTimeout(30000);
        conn.setReadTimeout(30000);
        try (OutputStream os = conn.getOutputStream()) {
            os.write(body.getBytes(StandardCharsets.UTF_8));
        }
        if (conn.getResponseCode() == 429) throw new Exception("Rate limited");
        InputStream stream = conn.getResponseCode() >= 400 ? conn.getErrorStream() : conn.getInputStream();
        String raw = readStream(stream);
        return parseJson(raw);
    }

    private static String encode(Map<String, String> data) throws UnsupportedEncodingException {
        StringBuilder sb = new StringBuilder();
        for (Map.Entry<String, String> e : data.entrySet()) {
            if (sb.length() > 0) sb.append('&');
            sb.append(URLEncoder.encode(e.getKey(), "UTF-8"));
            sb.append('=');
            sb.append(URLEncoder.encode(e.getValue(), "UTF-8"));
        }
        return sb.toString();
    }

    private static String readStream(InputStream in) throws IOException {
        if (in == null) return "{}";
        ByteArrayOutputStream buf = new ByteArrayOutputStream();
        byte[] tmp = new byte[4096];
        int n;
        while ((n = in.read(tmp)) != -1) buf.write(tmp, 0, n);
        return buf.toString(StandardCharsets.UTF_8.name());
    }

    @SuppressWarnings("unchecked")
    private static Map<String, Object> parseJson(String raw) {
        raw = raw.trim();
        Map<String, Object> out = new LinkedHashMap<>();
        if (!raw.startsWith("{")) return out;
        raw = raw.substring(1, raw.length() - 1).trim();
        if (raw.isEmpty()) return out;
        for (String part : splitTop(raw)) {
            int c = part.indexOf(':');
            if (c < 1) continue;
            String k = strip(part.substring(0, c));
            String v = strip(part.substring(c + 1));
            if (v.startsWith("{")) {
                out.put(k, parseJson("{" + v + "}"));
            } else {
                out.put(k, v);
            }
        }
        return out;
    }

    private static List<String> splitTop(String s) {
        List<String> parts = new ArrayList<>();
        int depth = 0, start = 0;
        for (int i = 0; i < s.length(); i++) {
            char ch = s.charAt(i);
            if (ch == '{') depth++;
            else if (ch == '}') depth--;
            else if (ch == ',' && depth == 0) {
                parts.add(s.substring(start, i));
                start = i + 1;
            }
        }
        parts.add(s.substring(start));
        return parts;
    }

    private static String strip(String s) {
        s = s.trim();
        if (s.startsWith("\"") && s.endsWith("\"")) return s.substring(1, s.length() - 1);
        return s;
    }

    private static String randomHex(int len) {
        Random r = new Random();
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < len; i++) sb.append(Integer.toHexString(r.nextInt(16)));
        return sb.toString();
    }
}
