package com.ayue.sdk;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Base64;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * 阿越云验证 - Java SDK
 *
 * 支持：卡密登录、心跳保活、公告获取、版本检查（含强制更新）、
 *       卡密解绑、获取应用配置、获取外链文件、应用账号登录
 *
 * 环境：JDK 8+
 *
 * @version 1.0.0
 */
public class AyueVerify {

    private final String baseUrl;
    private final int appId;
    private final String appKey;
    private final String rc4Key;
    private final int miType;
    private final int miTime;
    private final boolean signEnabled;
    private final int timeout;

    private String lastError = "";
    private int lastCode = 0;

    /**
     * @param baseUrl 验证服务器地址，如 https://yz.example.com
     * @param appId   应用 ID
     * @param appKey  应用密钥
     * @param rc4Key  通信密钥
     * @param miType  加密类型 0=明文 1=RC4 2=Base64
     * @param sign    是否签名
     * @param timeout 超时毫秒
     */
    public AyueVerify(String baseUrl, int appId, String appKey, String rc4Key,
                      int miType, boolean sign, int timeout, int miTime) {
        String b = baseUrl == null ? "" : baseUrl.trim();
        while (b.endsWith("/")) {
            b = b.substring(0, b.length() - 1);
        }
        this.baseUrl = b + "/";
        this.appId = appId;
        this.appKey = appKey == null ? "" : appKey;
        this.rc4Key = rc4Key == null ? "" : rc4Key;
        this.miType = miType;
        this.miTime = miTime;
        this.signEnabled = sign;
        this.timeout = timeout > 0 ? timeout : 10000;
    }

    public AyueVerify(String baseUrl, int appId, String appKey, String rc4Key) {
        this(baseUrl, appId, appKey, rc4Key, 0, false, 10000, 10);
    }

    // ==================== 业务接口 ====================

    /** 卡密登录 */
    public Result login(String kami, String markcode) {
        Map<String, String> p = new LinkedHashMap<>();
        p.put("kami", kami);
        p.put("markcode", markcode);
        return normalize(request("kmlogon", p), true);
    }

    /** 心跳保活 */
    public Result heartbeat(String kami, String markcode) {
        Map<String, String> p = new LinkedHashMap<>();
        p.put("kami", kami);
        p.put("markcode", markcode);
        return normalize(request("heartbeat", p), true);
    }

    /** 退出心跳 */
    public Result heartbeatQuit(String kami) {
        return heartbeat(kami, "quit");
    }

    /** 获取公告 */
    public String notice() {
        Map<String, String> p = new LinkedHashMap<>();
        Map<?, ?> r = request("notice", p);
        if (codeOf(r) == 200 && r.get("msg") instanceof Map) {
            Object gg = ((Map<?, ?>) r.get("msg")).get("app_gg");
            return gg == null ? "" : String.valueOf(gg);
        }
        return "";
    }

    /** 获取应用配置 */
    public Config config(boolean withPay) {
        Map<String, String> p = new LinkedHashMap<>();
        if (withPay) p.put("pay", "1");
        Map<?, ?> r = request("ini", p);
        Config c = new Config();
        c.ok = codeOf(r) == 200;
        c.code = lastCode;
        if (c.ok && r.get("msg") instanceof Map) {
            Map<?, ?> d = (Map<?, ?>) r.get("msg");
            c.version = str(d.get("version"));
            c.versionInfo = str(d.get("version_info"));
            c.updateUrl = str(d.get("app_update_url"));
            c.updateShow = str(d.get("app_update_show"));
            c.mustUpdate = "y".equals(str(d.get("app_update_must")));
        } else {
            c.msg = lastError;
        }
        return c;
    }

    public Config config() {
        return config(false);
    }

    /** 检查更新 */
    public UpdateInfo checkUpdate(String currentVersion) {
        Config c = config();
        UpdateInfo u = new UpdateInfo();
        if (!c.ok) {
            u.msg = c.msg;
            return u;
        }
        u.ok = true;
        u.hasUpdate = versionCompare(c.version, currentVersion) > 0;
        u.mustUpdate = u.hasUpdate && c.mustUpdate;
        u.latest = c.version;
        u.url = c.updateUrl;
        u.desc = c.updateShow.isEmpty() ? c.versionInfo : c.updateShow;
        return u;
    }

    /** 卡密解绑 */
    public Result unbind(String kami, String markcode) {
        Map<String, String> p = new LinkedHashMap<>();
        p.put("kami", kami);
        p.put("markcode", markcode);
        return normalize(request("kmunmachine", p), false);
    }

    /** 获取外链文件（原始 JSON 字符串） */
    public String getFilesRaw() {
        Map<String, String> p = new LinkedHashMap<>();
        return requestRaw("getfile", p);
    }

    /** 应用账号登录 */
    public Result userLogin(String user, String password) {
        Map<String, String> p = new LinkedHashMap<>();
        p.put("user", user);
        p.put("password", password);
        return normalize(request("userlogon", p), false);
    }

    /** 应用账号注册 */
    public Result userRegister(String user, String password) {
        Map<String, String> p = new LinkedHashMap<>();
        p.put("user", user);
        p.put("password", password);
        return normalize(request("userreg", p), false);
    }

    /** 应用账号心跳 */
    public Result userHeartbeat(String user, String markcode) {
        Map<String, String> p = new LinkedHashMap<>();
        p.put("user", user);
        p.put("markcode", markcode);
        return normalize(request("userheartbeat", p), false);
    }

    // ==================== 底层通信 ====================

    private String requestRaw(String api, Map<String, String> fields) {
        lastError = "";
        lastCode = 0;
        String value = randomStr(16);
        fields.put("value", value);

        // 关键：服务端 mi_time > 0 时强制要求 t 参数，缺失返回 108
        if (miTime > 0 && !fields.containsKey("t")) {
            fields.put("t", String.valueOf(System.currentTimeMillis() / 1000));
        }

        String url = baseUrl + "api.php?api=" + enc(api) + "&app=" + appId;

        Map<String, String> post = new LinkedHashMap<>();
        if (miType == 0) {
            post.putAll(fields);
        } else {
            String plain = buildQuery(fields);
            post.put("data", encrypt(plain));
            post.put("value", value);
        }

        if (signEnabled) {
            Map<String, String> src = new LinkedHashMap<>(fields);
            if (miType != 0) src.put("data", post.get("data"));
            post.put("sign", makeSign(src));
        }

        StringBuilder body = new StringBuilder();
        for (Map.Entry<String, String> e : post.entrySet()) {
            if (body.length() > 0) body.append('&');
            body.append(enc(e.getKey())).append('=').append(enc(e.getValue()));
        }

        HttpURLConnection conn = null;
        try {
            conn = (HttpURLConnection) new URL(url).openConnection();
            conn.setRequestMethod("POST");
            conn.setDoOutput(true);
            conn.setConnectTimeout(timeout);
            conn.setReadTimeout(timeout);
            conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
            conn.setRequestProperty("User-Agent", "AyueVerify-Java-SDK/1.0.0");

            OutputStream os = conn.getOutputStream();
            os.write(body.toString().getBytes(StandardCharsets.UTF_8));
            os.flush();
            os.close();

            int code = conn.getResponseCode();
            InputStream is = (code >= 200 && code < 300) ? conn.getInputStream() : conn.getErrorStream();
            BufferedReader br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8));
            StringBuilder sb = new StringBuilder();
            String line;
            while ((line = br.readLine()) != null) sb.append(line);
            br.close();
            return sb.toString();
        } catch (Exception e) {
            lastError = "网络请求失败: " + e.getMessage();
            lastCode = -1;
            return "{\"code\":-1,\"msg\":\"网络请求失败\"}";
        } finally {
            if (conn != null) conn.disconnect();
        }
    }

    private Map<?, ?> request(String api, Map<String, String> fields) {
        String raw = requestRaw(api, fields);
        try {
            // 去除 UTF-8 BOM：部分服务器输出带 BOM，会导致解析失败
            if (raw != null && raw.length() > 0 && raw.charAt(0) == '\uFEFF') {
                raw = raw.substring(1);
            }
            raw = raw == null ? "" : raw.trim();
            Object o = new JsonParser(raw).parse();
            if (o instanceof Map) {
                Map<?, ?> m = (Map<?, ?>) o;
                lastCode = codeOf(m);
                if (lastCode != 200) {
                    Object msg = m.get("msg");
                    lastError = msg == null ? "" : String.valueOf(msg);
                }
                return m;
            }
        } catch (Exception e) {
            lastError = "返回格式错误: " + raw;
            lastCode = -1;
        }
        Map<String, Object> fail = new LinkedHashMap<>();
        fail.put("code", -1);
        fail.put("msg", lastError);
        return fail;
    }

    private Result normalize(Map<?, ?> r, boolean wantExpires) {
        Result res = new Result();
        res.code = codeOf(r);
        res.ok = res.code == 200;
        res.msg = r.get("msg") == null ? "" : String.valueOf(r.get("msg"));
        res.data = (r.get("msg") instanceof Map) ? (Map<?, ?>) r.get("msg") : null;
        if (wantExpires && res.ok && res.data != null && res.data.get("expires_ts") != null) {
            try {
                res.expiresTs = Long.parseLong(String.valueOf(res.data.get("expires_ts")));
            } catch (Exception ignored) { }
        }
        return res;
    }

    private int codeOf(Map<?, ?> r) {
        Object c = r.get("code");
        if (c == null) return -1;
        try {
            return Integer.parseInt(String.valueOf(c).trim());
        } catch (Exception e) {
            return -1;
        }
    }

    private static String str(Object o) {
        return o == null ? "" : String.valueOf(o);
    }

    private static String enc(String s) {
        try {
            return URLEncoder.encode(s == null ? "" : s, "UTF-8");
        } catch (Exception e) {
            return "";
        }
    }

    private String buildQuery(Map<String, String> f) {
        StringBuilder sb = new StringBuilder();
        for (Map.Entry<String, String> e : f.entrySet()) {
            if (sb.length() > 0) sb.append('&');
            sb.append(e.getKey()).append('=').append(e.getValue());
        }
        return sb.toString();
    }

    private String makeSign(Map<String, String> fields) {
        String[] ignore = {"sign", "app", "api", "value", "PHPSESSID", "sec_defend", "sidenav-state"};
        StringBuilder sb = new StringBuilder();
        outer:
        for (Map.Entry<String, String> e : fields.entrySet()) {
            for (String ig : ignore) {
                if (ig.equals(e.getKey())) continue outer;
            }
            sb.append(e.getKey()).append('=').append(e.getValue()).append('&');
        }
        sb.append(appKey);
        return md5(sb.toString());
    }

    private String encrypt(String plain) {
        if (miType == 1) {
            // 关键：服务端 mi_rc4 会先做 UTF-8 -> GBK 转换，客户端必须保持一致。
            String d = plain;
            String k = rc4Key;
            try {
                d = new String(plain.getBytes("GBK"), "ISO-8859-1");
            } catch (Exception ignored) { }
            try {
                k = new String(rc4Key.getBytes("GBK"), "ISO-8859-1");
            } catch (Exception ignored) { }
            return rc4Hex(d, k);
        }
        return Base64.getEncoder().encodeToString(plain.getBytes(StandardCharsets.UTF_8));
    }

    private static String rc4Hex(String data, String key) {
        if (key == null || key.isEmpty()) return "";
        int[] s = new int[256];
        for (int i = 0; i < 256; i++) s[i] = i;
        int j = 0;
        int kl = key.length();
        for (int i = 0; i < 256; i++) {
            j = (j + s[i] + key.charAt(i % kl)) % 256;
            int t = s[i]; s[i] = s[j]; s[j] = t;
        }
        int i = 0; j = 0;
        StringBuilder out = new StringBuilder();
        for (int n = 0; n < data.length(); n++) {
            i = (i + 1) % 256;
            j = (j + s[i]) % 256;
            int t = s[i]; s[i] = s[j]; s[j] = t;
            int c = (data.charAt(n) ^ s[(s[i] + s[j]) % 256]) & 0xFF;
            out.append(String.format("%02x", c));
        }
        return out.toString();
    }

    private static String md5(String s) {
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] d = md.digest(s.getBytes(StandardCharsets.UTF_8));
            StringBuilder sb = new StringBuilder();
            for (byte b : d) sb.append(String.format("%02x", b & 0xFF));
            return sb.toString();
        } catch (Exception e) {
            return "";
        }
    }

    private static String randomStr(int n) {
        String chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        StringBuilder sb = new StringBuilder();
        java.util.Random rnd = new java.util.Random();
        for (int i = 0; i < n; i++) sb.append(chars.charAt(rnd.nextInt(chars.length())));
        return sb.toString();
    }

    /** 版本比较：v1 > v2 返回 1 */
    public static int versionCompare(String v1, String v2) {
        String[] a = String.valueOf(v1).trim().split("[.\\-+]");
        String[] b = String.valueOf(v2).trim().split("[.\\-+]");
        int n = Math.max(a.length, b.length);
        for (int i = 0; i < n; i++) {
            String x = i < a.length ? a[i] : "0";
            String y = i < b.length ? b[i] : "0";
            try {
                int xi = Integer.parseInt(x.trim());
                int yi = Integer.parseInt(y.trim());
                if (xi > yi) return 1;
                if (xi < yi) return -1;
            } catch (Exception e) {
                int c = x.compareTo(y);
                if (c > 0) return 1;
                if (c < 0) return -1;
            }
        }
        return 0;
    }

    /** 生成设备码（首次运行应持久化） */
    public static String generateMarkcode() {
        String seed = System.getProperty("os.name") + "|" + System.getProperty("os.arch")
                + "|" + System.getProperty("user.name") + "|" + Runtime.getRuntime().availableProcessors();
        return md5(seed).substring(0, 24).toUpperCase();
    }

    public String getLastError() { return lastError; }
    public int getLastCode() { return lastCode; }

    // ==================== 数据结构 ====================

    public static class Result {
        public boolean ok;
        public int code;
        public String msg = "";
        public Map<?, ?> data;
        public long expiresTs;
    }

    public static class Config {
        public boolean ok;
        public int code;
        public String msg = "";
        public String version = "";
        public String versionInfo = "";
        public String updateUrl = "";
        public String updateShow = "";
        public boolean mustUpdate;
    }

    public static class UpdateInfo {
        public boolean ok;
        public boolean hasUpdate;
        public boolean mustUpdate;
        public String latest = "";
        public String url = "";
        public String desc = "";
        public String msg = "";
    }

    // ==================== 极简 JSON 解析（避免外部依赖） ====================

    static class JsonParser {
        private final String s;
        private int i = 0;

        JsonParser(String s) { this.s = s; }

        Object parse() {
            ws();
            if (i < s.length() && s.charAt(i) == '{') return obj();
            if (i < s.length() && s.charAt(i) == '[') return arr();
            return val();
        }

        private void ws() {
            while (i < s.length() && Character.isWhitespace(s.charAt(i))) i++;
        }

        private Map<String, Object> obj() {
            Map<String, Object> m = new LinkedHashMap<>();
            i++; ws();
            if (i < s.length() && s.charAt(i) == '}') { i++; return m; }
            while (i < s.length()) {
                ws();
                String k = string();
                ws();
                if (i < s.length() && s.charAt(i) == ':') i++;
                ws();
                m.put(k, parse());
                ws();
                if (i < s.length() && s.charAt(i) == ',') { i++; continue; }
                if (i < s.length() && s.charAt(i) == '}') { i++; break; }
                break;
            }
            return m;
        }

        private List<Object> arr() {
            List<Object> l = new ArrayList<>();
            i++; ws();
            if (i < s.length() && s.charAt(i) == ']') { i++; return l; }
            while (i < s.length()) {
                ws();
                l.add(parse());
                ws();
                if (i < s.length() && s.charAt(i) == ',') { i++; continue; }
                if (i < s.length() && s.charAt(i) == ']') { i++; break; }
                break;
            }
            return l;
        }

        private String string() {
            if (i >= s.length() || s.charAt(i) != '"') return "";
            i++;
            StringBuilder sb = new StringBuilder();
            while (i < s.length()) {
                char c = s.charAt(i++);
                if (c == '"') break;
                if (c == '\\' && i < s.length()) {
                    char n = s.charAt(i++);
                    switch (n) {
                        case 'n': sb.append('\n'); break;
                        case 'r': sb.append('\r'); break;
                        case 't': sb.append('\t'); break;
                        case 'b': sb.append('\b'); break;
                        case 'f': sb.append('\f'); break;
                        case 'u':
                            if (i + 4 <= s.length()) {
                                sb.append((char) Integer.parseInt(s.substring(i, i + 4), 16));
                                i += 4;
                            }
                            break;
                        default: sb.append(n);
                    }
                } else {
                    sb.append(c);
                }
            }
            return sb.toString();
        }

        private Object val() {
            ws();
            if (i >= s.length()) return null;
            char c = s.charAt(i);
            if (c == '"') return string();
            if (c == 't' && s.startsWith("true", i)) { i += 4; return Boolean.TRUE; }
            if (c == 'f' && s.startsWith("false", i)) { i += 5; return Boolean.FALSE; }
            if (c == 'n' && s.startsWith("null", i)) { i += 4; return null; }
            int st = i;
            while (i < s.length() && "-+.eE0123456789".indexOf(s.charAt(i)) >= 0) i++;
            String num = s.substring(st, i);
            if (num.isEmpty()) { i++; return null; }
            try {
                if (num.indexOf('.') < 0 && num.indexOf('e') < 0 && num.indexOf('E') < 0) {
                    return Long.parseLong(num);
                }
                return Double.parseDouble(num);
            } catch (Exception e) {
                return num;
            }
        }
    }
}
