package com.bsphp.login;

import com.google.gson.JsonObject;
import com.google.gson.JsonParser;

import java.net.URI;
import java.net.URLDecoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;

/**
 * 卡模式 AppEn（.ic）客户端，行为对齐 Mac {@code bsphp.mac.demo.card} 的 {@code BSPHPClient}。
 */
public final class CardBSPHPClient {

    private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss", Locale.US);
    private static final DateTimeFormatter DATE_HASH_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd'#'HH:mm:ss", Locale.US);

    private final String url;
    private final String mutualKey;
    private final String serverPrivateKey;
    private final String clientPublicKey;

    private volatile String bsPhpSeSsL = "";
    private final HttpClient http = HttpClient.newBuilder()
            .connectTimeout(Duration.ofSeconds(20))
            .build();
    public CardBSPHPClient(String url, String mutualKey, String serverPrivateKey, String clientPublicKey) {
        this.url = url;
        this.mutualKey = mutualKey;
        this.serverPrivateKey = serverPrivateKey;
        this.clientPublicKey = clientPublicKey;
    }

    public String getBsPhpSeSsL() {
        return bsPhpSeSsL;
    }

    public static String machineCode() {
        return MachineCodeUtil.getMachineCode();
    }

    public void bootstrap() throws Exception {
        if (!connect()) {
            throw new IllegalStateException("连接失败");
        }
        if (!getSeSsL()) {
            throw new IllegalStateException("获取 BSphpSeSsL 失败");
        }
    }

    public boolean connect() throws Exception {
        JsonObject r = send("internet.in", Map.of());
        if (r == null) return false;
        return "1".equals(stringData(r.get("data")));
    }

    public boolean getSeSsL() throws Exception {
        JsonObject r = send("BSphpSeSsL.in", Map.of());
        if (r == null) return false;
        String data = stringData(r.get("data"));
        if (data == null || data.isEmpty()) return false;
        bsPhpSeSsL = data;
        return true;
    }

    public BSPHPAPIResult logout() throws Exception {
        JsonObject r = send("cancellation.ic", Map.of());
        BSPHPAPIResult res = toResult(r);
        bsPhpSeSsL = "";
        getSeSsL();
        return res;
    }

    public BSPHPAPIResult addCardFeatures(String carid, String key, String maxoror) throws Exception {
        return apiResult("AddCardFeatures.key.ic", Map.of(
                "carid", carid, "key", key, "maxoror", maxoror));
    }

    public BSPHPAPIResult rechargeCard(String icid, String ka, String pwd) throws Exception {
        return apiResult("chong.ic", Map.of("icid", icid, "ka", ka, "pwd", pwd));
    }

    public BSPHPAPIResult getNotice() throws Exception {
        return apiResult("gg.in", Map.of());
    }

    public BSPHPAPIResult getVersion() throws Exception {
        return apiResult("v.in", Map.of());
    }

    public BSPHPAPIResult getDateIC() throws Exception {
        return apiResult("getdate.ic", Map.of());
    }

    public BSPHPAPIResult getLoginInfo() throws Exception {
        return apiResult("getlkinfo.ic", Map.of());
    }

    public BSPHPAPIResult heartbeat() throws Exception {
        return apiResult("timeout.ic", Map.of());
    }

    public BSPHPAPIResult getServerDate() throws Exception {
        return apiResult("date.in", Map.of());
    }

    public BSPHPAPIResult getSoftInfo() throws Exception {
        return apiResult("miao.in", Map.of());
    }

    public BSPHPAPIResult getPresetURL() throws Exception {
        return apiResult("url.in", Map.of());
    }

    public BSPHPAPIResult getWebURL() throws Exception {
        return apiResult("weburl.in", Map.of());
    }

    public BSPHPAPIResult getAppCustom(String info) throws Exception {
        return apiResult("appcustom.in", Map.of("info", info));
    }

    public BSPHPAPIResult getGlobalInfo() throws Exception {
        return apiResult("globalinfo.in", Map.of());
    }

    public BSPHPAPIResult getLogicA() throws Exception {
        return apiResult("logica.in", Map.of());
    }

    public BSPHPAPIResult getLogicB() throws Exception {
        return apiResult("logicb.in", Map.of());
    }

    public BSPHPAPIResult queryCard(String cardid) throws Exception {
        return apiResult("socard.in", Map.of("cardid", cardid));
    }

    public BSPHPAPIResult getCardInfo(String icCarid, String icPwd, String info) throws Exception {
        return apiResult("getinfo.ic", Map.of(
                "ic_carid", icCarid, "ic_pwd", icPwd, "info", info));
    }

    public BSPHPAPIResult bindCard(String key, String icid, String icpwd) throws Exception {
        return apiResult("setcaron.ic", Map.of("key", key, "icid", icid, "icpwd", icpwd));
    }

    public BSPHPAPIResult unbindCard(String icid, String icpwd) throws Exception {
        return apiResult("setcarnot.ic", Map.of("icid", icid, "icpwd", icpwd));
    }

    public BSPHPAPIResult loginIC(String icid, String icpwd, String key, String maxoror) throws Exception {
        String k = key != null && !key.isEmpty() ? key : machineCode();
        String m = maxoror != null && !maxoror.isEmpty() ? maxoror : k;
        Map<String, String> p = new LinkedHashMap<>();
        p.put("icid", icid);
        p.put("icpwd", icpwd != null ? icpwd : "");
        p.put("key", k);
        p.put("maxoror", m);
        JsonObject r = send("login.ic", p);
        if (r == null) {
            return new BSPHPAPIResult("系统错误，登录失败！", null);
        }
        Integer code = intOrNull(r.get("code"));
        if (code != null && (code == 1011 || code == 9908 || code == 1081)) {
            String ssl = stringData(r.get("SeSsL"));
            if (ssl != null && !ssl.isEmpty()) {
                bsPhpSeSsL = ssl;
            }
        }
        return new BSPHPAPIResult(extractData(r.get("data")), code);
    }

    private BSPHPAPIResult apiResult(String api, Map<String, String> params) throws Exception {
        JsonObject r = send(api, params);
        if (r == null) return new BSPHPAPIResult(null, null);
        return toResult(r);
    }

    private static BSPHPAPIResult toResult(JsonObject r) {
        if (r == null) return new BSPHPAPIResult(null, null);
        return new BSPHPAPIResult(extractData(r.get("data")), intOrNull(r.get("code")));
    }

    private static Object extractData(com.google.gson.JsonElement el) {
        if (el == null || el.isJsonNull()) return null;
        return gsonData(el);
    }

    private static Object gsonData(com.google.gson.JsonElement el) {
        if (el.isJsonPrimitive()) {
            var p = el.getAsJsonPrimitive();
            if (p.isString()) return p.getAsString();
            if (p.isNumber()) return p.getAsNumber();
            if (p.isBoolean()) return p.getAsBoolean();
        }
        return el;
    }

    private static Integer intOrNull(com.google.gson.JsonElement el) {
        if (el == null || el.isJsonNull() || !el.isJsonPrimitive()) return null;
        try {
            return el.getAsInt();
        } catch (Exception e) {
            return null;
        }
    }

    private static String stringData(com.google.gson.JsonElement el) {
        if (el == null || el.isJsonNull()) return null;
        if (el.isJsonPrimitive() && el.getAsJsonPrimitive().isString()) {
            return el.getAsString();
        }
        return el.toString();
    }

    private JsonObject send(String api, Map<String, String> params) throws Exception {
        String appsafecode = BSPHPCrypto.md5Hex(DATE_FMT.format(LocalDateTime.now()));
        Map<String, String> param = new LinkedHashMap<>();
        param.put("api", api);
        param.put("BSphpSeSsL", bsPhpSeSsL);
        param.put("date", DATE_HASH_FMT.format(LocalDateTime.now()));
        param.put("md5", "");
        param.put("mutualkey", mutualKey);
        param.put("appsafecode", appsafecode);
        param.putAll(params);

        StringBuilder dataStr = new StringBuilder();
        for (Map.Entry<String, String> e : param.entrySet()) {
            if (dataStr.length() > 0) dataStr.append('&');
            String encVal = java.net.URLEncoder.encode(e.getValue(), StandardCharsets.UTF_8);
            dataStr.append(e.getKey()).append('=').append(encVal);
        }

        String aesKeyFull = BSPHPCrypto.md5Hex(serverPrivateKey + appsafecode);
        String aesKey = aesKeyFull.length() >= 16 ? aesKeyFull.substring(0, 16) : aesKeyFull;

        String encryptedB64 = BSPHPCrypto.aes128CbcEncryptBase64(dataStr.toString(), aesKey);
        String sigMd5 = BSPHPCrypto.md5Hex(encryptedB64);
        String signatureContent = "0|AES-128-CBC|" + aesKey + "|" + sigMd5 + "|json";
        String rsaB64 = BSPHPCrypto.rsaEncryptPkcs1Base64(signatureContent, clientPublicKey);

        String payload = encryptedB64 + "|" + rsaB64;
        String encoded = BSPHPCrypto.percentEncodePayload(payload);
        String body = "parameter=" + encoded;

        HttpRequest req = HttpRequest.newBuilder()
                .uri(URI.create(url))
                .timeout(Duration.ofSeconds(60))
                .header("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8")
                .POST(HttpRequest.BodyPublishers.ofString(body, StandardCharsets.UTF_8))
                .build();

        HttpResponse<String> resp = http.send(req, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
        if (resp.statusCode() != 200) return null;
        String text = resp.body();
        if (text == null || text.isEmpty()) return null;

        String raw;
        try {
            raw = URLDecoder.decode(text, StandardCharsets.UTF_8);
        } catch (Exception e) {
            raw = text;
        }

        String[] parts = raw.split("\\|", -1);
        if (parts.length < 3) return null;
        String respEncB64 = parts[1].trim();
        String respRsaB64 = parts[2].trim();

        String sigDecrypted = BSPHPCrypto.rsaDecryptPkcs1Base64(respRsaB64, serverPrivateKey);
        String[] sigParts = sigDecrypted.split("\\|");
        if (sigParts.length < 4) return null;
        String respAesKeyFull = sigParts[2];
        String respAesKey = respAesKeyFull.length() >= 16 ? respAesKeyFull.substring(0, 16) : respAesKeyFull;

        String decrypted = BSPHPCrypto.aes128CbcDecryptBase64ToString(respEncB64, respAesKey);

        JsonObject root = JsonParser.parseString(decrypted).getAsJsonObject();
        if (!root.has("response")) return null;
        JsonObject response = root.getAsJsonObject("response");

        String respSafe = stringData(response.get("appsafecode"));
        if (respSafe != null && !respSafe.equals(appsafecode)) {
            JsonObject copy = response.deepCopy();
            copy.addProperty("data", "appsafecode 安全参数验证不通过");
            return copy;
        }
        return response;
    }
}
