package com.github.catvod.spider;

import android.content.Context;
import android.text.TextUtils;
import android.util.Base64;

import com.github.catvod.crawler.Spider;
import com.github.catvod.crawler.SpiderDebug;
import com.github.catvod.net.OkHttp;
import com.github.catvod.net.OkResult;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;

import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import javax.crypto.Cipher;
import javax.crypto.Mac;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class AppNox extends Spider {

    private String baseUrl = "";
    private String newAppUrl = "http://172.247.44.102:9909/newapp.json";
    private String newAppKey = "";  // 用于解密 newapp.json 响应的密钥
    private Gson gson = new Gson();
    private Map<String, String> fixedHeaders = new HashMap<>();

    private byte[] aesKey;      // 握手后获得的 AES 密钥
    private byte[] hmacKey;     // 握手后获得的 HMAC 密钥

    // RSA 公钥（可通过 ext 传入 RSA_PUBLIC_KEY）
    private String rsaPublicKey =
            "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArjMS+mslyst9tqufhGQO09gnaYfAqmS7bVN6bGxrvJUx7NtpJwJVR9AcZY8KhqH4aQIoB7TbNWU2onhz0sM3n5hdyoAojLfehIcyutrSaO8l56ChVD2eeOQtnMbS2UDngRULsFPQowsGXejpgI0YSfRiIxtc+rVAwivIU3ZtKJwJlzVQZWHdjIGM7kOd24RPmCN6SKamGZupgPfg4+cRl1azBdBmJInGV4V9NQroN1G703Sqhzhp6dBvpmGltsNlSQeC50/J9OwE6yaeWuKaSW0DTyWxQkI8Fa07t+j2UNgkK8ZN1DoPeJRpzb0NI3G+ajiljli8vZrtqojuXhehnQIDAQAB";

    // AES 密钥（用于加密握手请求体，也是 RSA 加密的原文，随机生成）
    private String fixedAesKeyBase64;

    // HMAC challenge key（可通过 ext 传入）
    private String hmacChallengeKeyBase64 = "9Bep59wfzsuSMno9j+1LPzZ7firV4LGYNCewy/Ts2Nk=";
    private String deviceId = "782a3fef7fae01b1";
    private String userAgent = "Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) NOX/1.0 Mobile/15E148 Safari/604.1";
    private String appPackage = "com.ddspnox.app";
    private String appVersionName = "3.1.8";
    private String appVersionCode = "11";
    private String appSignature = "C8:42:4E:76:10:F9:E8:96:67:4C:27:B9:2E:A5:E1:F8:D9:05:48:11:A8:D9:D2:18:76:9B:C7:ED:FF:3D:B4:86";

    private SecureRandom secureRandom = new SecureRandom();

    @Override
    public void init(Context context, String extend) {
        try {
            // 随机生成固定 AES 密钥（用于握手请求体加密）
            byte[] randomAesKey = new byte[32];
            secureRandom.nextBytes(randomAesKey);
            fixedAesKeyBase64 = Base64.encodeToString(randomAesKey, Base64.NO_WRAP);
            SpiderDebug.log("随机生成的固定 AES 密钥: " + fixedAesKeyBase64);

            if (!TextUtils.isEmpty(extend)) {
                JsonObject config = JsonParser.parseString(extend.trim()).getAsJsonObject();
                if (config.has("newAppUrl") && !config.get("newAppUrl").getAsString().isEmpty()) {
                    newAppUrl = config.get("newAppUrl").getAsString();
                }
                if (config.has("newApp_key") && !config.get("newApp_key").getAsString().isEmpty()) {
                    newAppKey = config.get("newApp_key").getAsString();
                    SpiderDebug.log("ext newApp_key 已加载");
                }
                if (config.has("RSA_PUBLIC_KEY") && !config.get("RSA_PUBLIC_KEY").getAsString().isEmpty()) {
                    rsaPublicKey = config.get("RSA_PUBLIC_KEY").getAsString();
                    SpiderDebug.log("ext RSA公钥已加载");
                }
                if (config.has("x-app-signature") && !config.get("x-app-signature").getAsString().isEmpty()) {
                    appSignature = config.get("x-app-signature").getAsString();
                }
                if (config.has("x-app-package") && !config.get("x-app-package").getAsString().isEmpty()) {
                    appPackage = config.get("x-app-package").getAsString();
                }
                if (config.has("x-app-version-code") && !config.get("x-app-version-code").getAsString().isEmpty()) {
                    appVersionCode = config.get("x-app-version-code").getAsString();
                }
                if (config.has("x-app-version-name") && !config.get("x-app-version-name").getAsString().isEmpty()) {
                    appVersionName = config.get("x-app-version-name").getAsString();
                }
                SpiderDebug.log("AppNewNox 使用 ext 配置加载");
            } else {
                SpiderDebug.log("AppNewNox 未传入 ext，使用默认配置");
            }

            // 1. 请求 newapp.json 并解密获取 baseUrl（仅触发一次）
            requestNewAppJson();

            // 2. 握手获取动态 aesKey 和 hmacKey
            performHandshake();

            // 3. 固定请求头（后续 API 调用使用）
            fixedHeaders.put("User-Agent", userAgent);
            fixedHeaders.put("x-app-package", appPackage);
            fixedHeaders.put("x-app-version-name", appVersionName);
            fixedHeaders.put("x-app-version-code", appVersionCode);
            fixedHeaders.put("x-app-signature", appSignature);
            fixedHeaders.put("Content-Type", "application/json; charset=utf-8");
            fixedHeaders.put("Connection", "Keep-Alive");
            fixedHeaders.put("x-device-id", deviceId);

            SpiderDebug.log("AppNewNox 初始化成功，baseUrl: " + baseUrl);
        } catch (Exception e) {
            SpiderDebug.log("AppNewNox 初始化失败: " + e.getMessage());
            e.printStackTrace();
        }
    }

    /**
     * 请求 newapp.json 并解密获取 baseUrl
     * 解密方式：OpenSSL Salted 格式 (Salted__ + 8字节盐 + 密文)
     * 算法：AES-256-CBC，密钥派生使用 EVP_BytesToKey (MD5)
     */
    private void requestNewAppJson() {
        try {
            if (TextUtils.isEmpty(newAppUrl) || TextUtils.isEmpty(newAppKey)) {
                SpiderDebug.log("newapp.json 配置不完整，跳过");
                return;
            }

            Map<String, String> headers = new HashMap<>();
            headers.put("User-Agent", "okhttp/5.3.2");
            String resp = OkHttp.string(newAppUrl, headers);
            if (TextUtils.isEmpty(resp)) {
                SpiderDebug.log("newapp.json 响应为空，跳过");
                return;
            }
            SpiderDebug.log("newapp.json 原始响应: " + resp);

            // Base64 解码
            byte[] encryptedRaw = Base64.decode(resp, Base64.DEFAULT);

            // 校验 Salted__ 头部
            if (encryptedRaw.length < 16 || encryptedRaw[0] != 'S' || encryptedRaw[1] != 'a') {
                SpiderDebug.log("newapp.json 不是 OpenSSL Salted 格式，尝试直接解析");
                // 尝试直接作为 JSON 解析
                try {
                    JsonObject json = JsonParser.parseString(resp).getAsJsonObject();
                    extractBaseUrl(json);
                } catch (Exception e2) {
                    SpiderDebug.log("newapp.json 直接解析也失败: " + e2.getMessage());
                }
                return;
            }

            // 提取 salt（8 字节）和密文
            byte[] salt = Arrays.copyOfRange(encryptedRaw, 8, 16);
            byte[] ciphertext = Arrays.copyOfRange(encryptedRaw, 16, encryptedRaw.length);
            SpiderDebug.log("newapp.json salt 长度: " + salt.length + ", 密文长度: " + ciphertext.length);

            // EVP_BytesToKey 派生 key 和 iv（MD5 迭代）
            byte[][] derived = evpBytesToKey(newAppKey.getBytes(StandardCharsets.UTF_8), salt, 32, 16);
            byte[] key = derived[0];
            byte[] iv = derived[1];
            SpiderDebug.log("EVP_BytesToKey 派生完成");

            // AES-256-CBC 解密
            SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
            IvParameterSpec ivSpec = new IvParameterSpec(iv);
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivSpec);
            byte[] decrypted = cipher.doFinal(ciphertext);
            String decryptedStr = new String(decrypted, StandardCharsets.UTF_8);
            SpiderDebug.log("newapp.json 解密结果: " + decryptedStr);

            // 提取 baseUrl
            JsonObject configJson = JsonParser.parseString(decryptedStr).getAsJsonObject();
            extractBaseUrl(configJson);
        } catch (Exception e) {
            SpiderDebug.log("newapp.json 处理失败: " + e.getMessage());
            e.printStackTrace();
        }
    }

    /**
     * 从解密的 JSON 中提取第一个 base_url 作为 baseUrl
     */
    private void extractBaseUrl(JsonObject configJson) {
        try {
            if (configJson.has("api_config")) {
                JsonObject apiConfig = configJson.getAsJsonObject("api_config");
                if (apiConfig.has("base_urls")) {
                    JsonArray baseUrls = apiConfig.getAsJsonArray("base_urls");
                    if (baseUrls.size() > 0 && baseUrls.get(0).isJsonObject()) {
                        String url = baseUrls.get(0).getAsJsonObject().get("url").getAsString();
                        if (url.endsWith("/")) url = url.substring(0, url.length() - 1);
                        baseUrl = url;
                        SpiderDebug.log("从 newapp.json 提取到 baseUrl: " + baseUrl);
                        return;
                    }
                }
            }
        } catch (Exception e) {
            SpiderDebug.log("提取 baseUrl 失败: " + e.getMessage());
        }
    }

    /**
     * EVP_BytesToKey 实现（MD5 迭代，与 openssl enc -md md5 兼容）
     * 用于从密码和盐派生 AES-256-CBC 的 key 和 iv
     */
    private byte[][] evpBytesToKey(byte[] password, byte[] salt, int keyLen, int ivLen) throws Exception {
        int totalLen = keyLen + ivLen;
        byte[] derived = new byte[totalLen];
        byte[] hash = new byte[0];
        int offset = 0;

        while (offset < totalLen) {
            MessageDigest md5 = MessageDigest.getInstance("MD5");
            md5.update(hash);
            md5.update(password);
            md5.update(salt);
            hash = md5.digest();

            int copyLen = Math.min(hash.length, totalLen - offset);
            System.arraycopy(hash, 0, derived, offset, copyLen);
            offset += copyLen;
        }

        return new byte[][]{
                Arrays.copyOfRange(derived, 0, keyLen),
                Arrays.copyOfRange(derived, keyLen, keyLen + ivLen)
        };
    }

    /**
     * 握手流程：使用固定 AES 密钥加密请求体，RSA/OAEP 加密该固定密钥，
     * 用随机生成的临时密钥解密响应，获得真正的 aesKey 和 hmacKey。
     */
    private void performHandshake() throws Exception {
        SpiderDebug.log("========== 开始握手（RSA/ECB/OAEPPadding） ==========");

        // 固定 AES 密钥（用于加密请求体）
        byte[] fixedAesKey = Base64.decode(fixedAesKeyBase64, Base64.DEFAULT);
        SpiderDebug.log("固定 AES 密钥解码成功，长度: " + fixedAesKey.length);

        // 1. 生成临时密钥（用于解密响应）
        byte[] tempKeyBytes = new byte[32];
        secureRandom.nextBytes(tempKeyBytes);
        String tempKeyBase64 = Base64.encodeToString(tempKeyBytes, Base64.NO_WRAP);
        SpiderDebug.log("临时密钥 temp_key (Base64): " + tempKeyBase64);

        // 2. 计算 challenge_response
        byte[] hmacChallengeKey = Base64.decode(hmacChallengeKeyBase64, Base64.DEFAULT);
        String message = "|" + deviceId;
        String challengeResponse = hmacSha256Hex(message, hmacChallengeKey);
        SpiderDebug.log("challenge_response: " + challengeResponse);

        // 3. 构建明文 JSON
        JsonObject plain = new JsonObject();
        plain.addProperty("temp_key", tempKeyBase64);
        plain.addProperty("device_id", deviceId);
        plain.addProperty("challenge", "");
        plain.addProperty("challenge_response", challengeResponse);
        String plainText = gson.toJson(plain);
        SpiderDebug.log("请求明文: " + plainText);

        // 4. 用固定 AES 密钥加密明文 (AES/GCM/NoPadding)
        // 生成16个随机Base64字符作为IV，解码后用于GCM加密
        String ivBase64 = generateRandomIvBase64(16);
        SpiderDebug.log("AES-GCM 随机 IV (Base64): " + ivBase64);
        byte[] ivBytes = Base64.decode(ivBase64, Base64.NO_WRAP);
        byte[] cipherData = aesGcmEncrypt(plainText.getBytes(StandardCharsets.UTF_8), fixedAesKey, ivBytes);
        // payload = IV(Base64字符串) + 密文(Base64)，直接拼接字符串
        String encryptedPayload = ivBase64 + Base64.encodeToString(cipherData, Base64.NO_WRAP);
        SpiderDebug.log("加密后的 payload 长度: " + encryptedPayload.length());

        // 5. 用 RSA/ECB/OAEPPadding 加密固定的 AES 密钥字符串
        String encryptedKey = rsaEncryptOAEP(fixedAesKeyBase64, rsaPublicKey);
        SpiderDebug.log("RSA 加密后的 key: " + encryptedKey);

        // 6. 拼接 data 字段
        String dataField = encryptedKey + "|" + encryptedPayload;
        SpiderDebug.log("最终 data 字段长度: " + dataField.length());

        // 7. 发送 POST 请求到 /api/sync/preferences
        Map<String, String> headers = new HashMap<>();
        headers.put("User-Agent", userAgent);
        headers.put("x-app-package", appPackage);
        headers.put("x-app-version-name", appVersionName);
        headers.put("x-app-version-code", appVersionCode);
        headers.put("x-app-signature", appSignature);
        headers.put("Content-Type", "application/json; charset=utf-8");
        headers.put("Connection", "Keep-Alive");

        JsonObject requestBody = new JsonObject();
        requestBody.addProperty("data", dataField);

        String requestUrl = baseUrl + "/api/sync/preferences";
        SpiderDebug.log("请求 URL: " + requestUrl);
        SpiderDebug.log("请求体: " + requestBody.toString());

        OkResult result = OkHttp.post(requestUrl, requestBody.toString(), headers);
        SpiderDebug.log("响应状态码: " + result.getCode());
        String response = result.getBody();
        SpiderDebug.log("响应内容: " + response);

        if (result.getCode() != 200) {
            throw new Exception("HTTP 错误: " + result.getCode());
        }

        JsonObject respObj = JsonParser.parseString(response).getAsJsonObject();
        if (respObj.get("code").getAsInt() != 0) {
            throw new Exception("业务错误: " + respObj.get("msg").getAsString());
        }

        // 8. 解密响应数据（使用临时密钥）
        // data格式: 前16个字符是Base64编码的IV，剩余部分是Base64编码的密文
        String encryptedResponseData = respObj.get("data").getAsString();
        SpiderDebug.log("响应data原始内容: " + encryptedResponseData);
        SpiderDebug.log("响应data长度: " + encryptedResponseData.length());

        String respIvBase64 = encryptedResponseData.substring(0, 16);
        String respCipherBase64 = encryptedResponseData.substring(16);
        SpiderDebug.log("响应 IV (Base64, 16字符): " + respIvBase64);
        SpiderDebug.log("响应密文(Base64)前50字符: " + respCipherBase64.substring(0, Math.min(50, respCipherBase64.length())));
        SpiderDebug.log("解密使用的临时密钥(temp_key) Base64: " + tempKeyBase64);

        byte[] respIv = Base64.decode(respIvBase64, Base64.NO_WRAP); // Base64 decode 16字符 → 约12字节IV
        byte[] respCipher = Base64.decode(respCipherBase64, Base64.DEFAULT); // 剩余部分Base64解码为密文
        SpiderDebug.log("响应IV字节长度: " + respIv.length);
        SpiderDebug.log("响应密文字节长度: " + respCipher.length);

        String decrypted = aesGcmDecrypt(respCipher, tempKeyBytes, respIv);
        SpiderDebug.log("解密后的响应: " + decrypted);

        JsonObject keys = JsonParser.parseString(decrypted).getAsJsonObject();
        String aesKeyBase64 = keys.get("aes_key").getAsString();
        String hmacKeyBase64 = keys.get("hmac_key").getAsString();
        aesKey = Base64.decode(aesKeyBase64, Base64.DEFAULT);
        hmacKey = Base64.decode(hmacKeyBase64, Base64.DEFAULT);

        SpiderDebug.log("握手成功，aes_key: " + aesKeyBase64);
        SpiderDebug.log("握手成功，hmac_key: " + hmacKeyBase64);
    }

    // ==================== 加密工具方法 ====================

    private byte[] aesGcmEncrypt(byte[] plain, byte[] key, byte[] iv) throws Exception {
        Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
        SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
        GCMParameterSpec gcmSpec = new GCMParameterSpec(128, iv);
        cipher.init(Cipher.ENCRYPT_MODE, keySpec, gcmSpec);
        return cipher.doFinal(plain);
    }

    private String aesGcmDecrypt(byte[] cipherData, byte[] key, byte[] iv) throws Exception {
        Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
        SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
        GCMParameterSpec gcmSpec = new GCMParameterSpec(128, iv);
        cipher.init(Cipher.DECRYPT_MODE, keySpec, gcmSpec);
        byte[] plain = cipher.doFinal(cipherData);
        return new String(plain, StandardCharsets.UTF_8);
    }

    private String hmacSha256Hex(String data, byte[] key) throws Exception {
        Mac mac = Mac.getInstance("HmacSHA256");
        SecretKeySpec keySpec = new SecretKeySpec(key, "HmacSHA256");
        mac.init(keySpec);
        byte[] result = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
        StringBuilder hex = new StringBuilder();
        for (byte b : result) {
            hex.append(String.format("%02x", b));
        }
        return hex.toString();
    }

    // ==================== 加密工具方法 ====================

    /**
     * MGF1 掩码生成函数 (SHA-256)
     */
    private byte[] mgf1Sha256(byte[] seed, int maskLen) throws Exception {
        java.security.MessageDigest sha256 = java.security.MessageDigest.getInstance("SHA-256");
        int hLen = 32;
        int loops = (maskLen + hLen - 1) / hLen;
        byte[] t = new byte[loops * hLen];
        int offset = 0;
        for (int counter = 0; counter < loops; counter++) {
            sha256.reset();
            sha256.update(seed);
            sha256.update(new byte[]{
                    (byte) (counter >>> 24), (byte) (counter >>> 16),
                    (byte) (counter >>> 8), (byte) counter
            });
            byte[] hash = sha256.digest();
            System.arraycopy(hash, 0, t, offset, hLen);
            offset += hLen;
        }
        byte[] result = new byte[maskLen];
        System.arraycopy(t, 0, result, 0, maskLen);
        return result;
    }

    /**
     * OAEP-SHA256 手动填充（参考 PHP 实现）
     */
    private byte[] oaepSha256Pad(byte[] message, int keyLen) throws Exception {
        int hLen = 32;
        int maxMsgLen = keyLen - 2 * hLen - 2;
        if (message.length > maxMsgLen) {
            throw new Exception("OAEP: 消息过长，最大允许 " + maxMsgLen + " 字节");
        }
        java.security.MessageDigest sha256 = java.security.MessageDigest.getInstance("SHA-256");
        byte[] lHash = sha256.digest(new byte[0]);
        int psLen = keyLen - message.length - 2 * hLen - 2;
        byte[] db = new byte[hLen + psLen + 1 + message.length];
        System.arraycopy(lHash, 0, db, 0, hLen);
        db[hLen + psLen] = 0x01;
        System.arraycopy(message, 0, db, hLen + psLen + 1, message.length);
        byte[] seed = new byte[hLen];
        secureRandom.nextBytes(seed);
        byte[] dbMask = mgf1Sha256(seed, keyLen - hLen - 1);
        byte[] maskedDB = new byte[dbMask.length];
        for (int i = 0; i < maskedDB.length; i++) {
            maskedDB[i] = (byte) (db[i] ^ dbMask[i]);
        }
        byte[] seedMask = mgf1Sha256(maskedDB, hLen);
        byte[] maskedSeed = new byte[hLen];
        for (int i = 0; i < hLen; i++) {
            maskedSeed[i] = (byte) (seed[i] ^ seedMask[i]);
        }
        byte[] result = new byte[keyLen];
        result[0] = 0x00;
        System.arraycopy(maskedSeed, 0, result, 1, hLen);
        System.arraycopy(maskedDB, 0, result, 1 + hLen, maskedDB.length);
        return result;
    }

    /**
     * RSA-OAEP-SHA256 加密：手动 OAEP 填充 + RSA 原始加密（NOPADDING）
     * 与 PHP 的 rsaOaepSha256Encrypt 完全等价
     */
    private String rsaEncryptOAEP(String plainText, String publicKeyPem) throws Exception {
        String publicKeyContent = publicKeyPem.replaceAll("\\s", "");
        byte[] keyBytes = Base64.decode(publicKeyContent, Base64.DEFAULT);
        X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        java.security.PublicKey publicKey = keyFactory.generatePublic(spec);

        SpiderDebug.log("RSA 公钥类型: " + publicKey.getClass().getName());
        SpiderDebug.log("RSA 加密明文: " + plainText);

        java.security.interfaces.RSAPublicKey rsaPubKey = (java.security.interfaces.RSAPublicKey) publicKey;
        int keyLen = (rsaPubKey.getModulus().bitLength() + 7) / 8;
        SpiderDebug.log("RSA 密钥长度: " + keyLen + " 字节 (" + rsaPubKey.getModulus().bitLength() + " 位)");

        // 关键：与PHP一致，先将Base64字符串解码为原始字节，再做OAEP填充
        byte[] plaintext = Base64.decode(plainText, Base64.NO_WRAP);
        SpiderDebug.log("RSA 明文(Base64解码后)长度: " + plaintext.length);
        byte[] padded = oaepSha256Pad(plaintext, keyLen);
        SpiderDebug.log("OAEP 填充后长度: " + padded.length);

        Cipher cipher = Cipher.getInstance("RSA/ECB/NoPadding");
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        byte[] encrypted = cipher.doFinal(padded);
        String result = Base64.encodeToString(encrypted, Base64.NO_WRAP);
        SpiderDebug.log("RSA 加密结果长度: " + result.length());
        return result;
    }

    // 生成随机设备ID（16个十六进制字符，如 782a3fef7fae01b1）
    private String generateDeviceId() {
        byte[] bytes = new byte[8];
        secureRandom.nextBytes(bytes);
        StringBuilder sb = new StringBuilder(16);
        for (byte b : bytes) {
            sb.append(String.format("%02x", b & 0xff));
        }
        return sb.toString();
    }

    private String generateRandomIvBase64(int length) {
        String base64Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
        StringBuilder sb = new StringBuilder(length);
        for (int i = 0; i < length; i++) {
            sb.append(base64Chars.charAt(secureRandom.nextInt(base64Chars.length())));
        }
        return sb.toString();
    }

    private String generateRandomTrace() {
        String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
        StringBuilder sb = new StringBuilder(24);
        for (int i = 0; i < 24; i++) {
            sb.append(chars.charAt(secureRandom.nextInt(chars.length())));
        }
        return sb.toString();
    }

    private String generateReqId() {
        return UUID.randomUUID().toString().replace("-", "").substring(0, 16);
    }

    private String fixImageUrl(String url) {
        if (TextUtils.isEmpty(url)) return "";
        if (url.startsWith("http")) return url;
        if (url.startsWith("/")) return baseUrl + url;
        return baseUrl + "/" + url;
    }

    // ==================== 核心 API 请求（使用动态密钥） ====================

    private JsonObject sendApiRequest(String method, String path, Map<String, String> query, Object bodyObj) throws Exception {
        // 构建内部请求体
        JsonObject innerReq = new JsonObject();
        innerReq.addProperty("method", method);

        // 查询参数拼接到 path 中（不编码，保持原始字符），query 字段始终为空
        StringBuilder fullPath = new StringBuilder(path);
        if (query != null && !query.isEmpty()) {
            fullPath.append("?");
            boolean first = true;
            for (Map.Entry<String, String> entry : query.entrySet()) {
                if (!first) fullPath.append("&");
                first = false;
                fullPath.append(entry.getKey()).append("=").append(entry.getValue());
            }
        }
        innerReq.addProperty("path", fullPath.toString());
        innerReq.addProperty("query", ""); // 始终为空

        JsonObject innerHeaders = new JsonObject();
        innerHeaders.addProperty("X-App-Package", appPackage);
        innerHeaders.addProperty("X-App-Signature", appSignature);
        innerHeaders.addProperty("X-App-Version-Code", appVersionCode);
        innerHeaders.addProperty("X-App-Version-Name", appVersionName);
        innerHeaders.addProperty("Content-Type", "application/json");
        innerReq.add("headers", innerHeaders);

        String bodyStr = "";
        if (bodyObj != null) {
            bodyStr = (bodyObj instanceof String) ? (String) bodyObj : gson.toJson(bodyObj);
        }
        innerReq.addProperty("body", bodyStr);

        String plainText = gson.toJson(innerReq)
                .replace("/", "\\/")  // 转义 / 为 \/
                .replace("\\u003d", "=")   // 还原 = (Gson htmlSafe 转义)
                .replace("\\u0026", "&")    // 还原 &
                .replace("\\u0025", "%")    // 还原 %
                .replace("\\u003e", ">")    // 还原 >
                .replace("\\u003c", "<");   // 还原 <

        // AES-GCM 加密（使用动态 aesKey）
        // 与握手一致: 生成16个随机Base64字符作为IV，解码后用于GCM加密
        String ivBase64 = generateRandomIvBase64(16);
        byte[] ivBytes = Base64.decode(ivBase64, Base64.NO_WRAP);
        byte[] cipherText = aesGcmEncrypt(plainText.getBytes(StandardCharsets.UTF_8), aesKey, ivBytes);
        // bundle = IV(Base64字符串) + 密文(Base64)，直接拼接
        String bundle = ivBase64 + Base64.encodeToString(cipherText, Base64.NO_WRAP);

        // 生成动态请求头
        String ts = String.valueOf(System.currentTimeMillis());
        String trace = generateRandomTrace();
        String reqId = deviceId; // x-req-id 使用 deviceId
        String signData = bundle + ts + trace;
        String token = hmacSha256Hex(signData, hmacKey);

        Map<String, String> outerHeaders = new HashMap<>(fixedHeaders);
        outerHeaders.put("x-req-ts", ts);
        outerHeaders.put("x-req-trace", trace);
        outerHeaders.put("x-req-id", reqId);
        outerHeaders.put("x-req-token", token);

        JsonObject outerBody = new JsonObject();
        outerBody.addProperty("bundle", bundle);
        OkResult result = OkHttp.post(baseUrl + "/api/sync/push", outerBody.toString(), outerHeaders);
        if (result.getCode() != 200) {
            throw new Exception("API HTTP 错误: " + result.getCode());
        }
        String response = result.getBody();

        // 解密响应
        // 与握手一致: 前16个字符是Base64编码的IV，剩余部分是Base64编码的密文
        JsonObject respObj = JsonParser.parseString(response).getAsJsonObject();
        String respBundle = respObj.get("bundle").getAsString();
        String respIvBase64 = respBundle.substring(0, 16);
        String respCipherBase64 = respBundle.substring(16);
        byte[] respIv = Base64.decode(respIvBase64, Base64.NO_WRAP); // Base64 decode → 约12字节IV
        byte[] respCipher = Base64.decode(respCipherBase64, Base64.DEFAULT); // 剩余部分Base64解码为密文
        String decrypted = aesGcmDecrypt(respCipher, aesKey, respIv);
        return JsonParser.parseString(decrypted).getAsJsonObject();
    }

    // ==================== Spider 接口实现 ====================

    @Override
    public String homeContent(boolean filter) {
        try {
            JsonObject resp = sendApiRequest("GET", "/api/categories", null, null);
            if (resp.get("code").getAsInt() != 0) {
                return "{\"class\": [], \"filters\": {}}";
            }
            JsonArray data = resp.getAsJsonArray("data");
            JsonArray classes = new JsonArray();
            JsonObject filters = new JsonObject();

            for (JsonElement elem : data) {
                JsonObject item = elem.getAsJsonObject();
                String typeId = item.get("type_id").getAsString();
                String typeName = item.get("type_name").getAsString();

                JsonObject classObj = new JsonObject();
                classObj.addProperty("type_id", typeId);
                classObj.addProperty("type_name", typeName);
                classes.add(classObj);

                if (filter && item.has("filters")) {
                    JsonObject filterObj = item.getAsJsonObject("filters");
                    JsonArray typeFilters = new JsonArray();

                    if (filterObj.has("areas")) {
                        JsonArray areas = filterObj.getAsJsonArray("areas");
                        if (areas.size() > 0) {
                            JsonArray values = new JsonArray();
                            for (JsonElement areaElem : areas) {
                                JsonObject v = new JsonObject();
                                v.addProperty("n", areaElem.getAsString());
                                v.addProperty("v", areaElem.getAsString());
                                values.add(v);
                            }
                            JsonObject filterItem = new JsonObject();
                            filterItem.addProperty("key", "area");
                            filterItem.addProperty("name", "地区");
                            filterItem.add("value", values);
                            typeFilters.add(filterItem);
                        }
                    }

                    if (filterObj.has("classes")) {
                        JsonArray classesArr = filterObj.getAsJsonArray("classes");
                        if (classesArr.size() > 0) {
                            JsonArray values = new JsonArray();
                            for (JsonElement classElem : classesArr) {
                                JsonObject v = new JsonObject();
                                v.addProperty("n", classElem.getAsString());
                                v.addProperty("v", classElem.getAsString());
                                values.add(v);
                            }
                            JsonObject filterItem = new JsonObject();
                            filterItem.addProperty("key", "class");
                            filterItem.addProperty("name", "类型");
                            filterItem.add("value", values);
                            typeFilters.add(filterItem);
                        }
                    }

                    if (filterObj.has("years")) {
                        JsonArray years = filterObj.getAsJsonArray("years");
                        if (years.size() > 0) {
                            JsonArray values = new JsonArray();
                            for (JsonElement yearElem : years) {
                                JsonObject v = new JsonObject();
                                v.addProperty("n", yearElem.getAsString());
                                v.addProperty("v", yearElem.getAsString());
                                values.add(v);
                            }
                            JsonObject filterItem = new JsonObject();
                            filterItem.addProperty("key", "year");
                            filterItem.addProperty("name", "年份");
                            filterItem.add("value", values);
                            typeFilters.add(filterItem);
                        }
                    }

                    if (typeFilters.size() > 0) {
                        filters.add(typeId, typeFilters);
                    }
                }
            }

            JsonObject result = new JsonObject();
            result.add("class", classes);
            result.add("filters", filters);
            return result.toString();
        } catch (Exception e) {
            SpiderDebug.log("homeContent 错误: " + e.getMessage());
            return "{\"class\": [], \"filters\": {}}";
        }
    }

    @Override
    public String homeVideoContent() {
        try {
            JsonObject resp = sendApiRequest("GET", "/api/navigations/1", null, null);
            if (resp.get("code").getAsInt() != 0) {
                return "{\"list\": []}";
            }
            JsonObject data = resp.getAsJsonObject("data");
            JsonArray videos = new JsonArray();

            // 从 banners 中提取视频
            if (data.has("banners") && !data.get("banners").isJsonNull()) {
                JsonArray banners = data.getAsJsonArray("banners");
                for (JsonElement elem : banners) {
                    JsonObject item = elem.getAsJsonObject();
                    JsonObject video = new JsonObject();
                    video.addProperty("vod_id", item.get("id").getAsString());
                    video.addProperty("vod_name", item.get("name").getAsString());
                    video.addProperty("vod_pic", fixImageUrl(item.get("banner_url").getAsString()));
                    video.addProperty("vod_remarks", item.has("vod_remarks") ? item.get("vod_remarks").getAsString() : "");
                    video.addProperty("vod_year", item.has("year") ? item.get("year").getAsString() : "");
                    videos.add(video);
                }
            }

            // 从 modules[*].content 中提取视频
            if (data.has("modules") && !data.get("modules").isJsonNull()) {
                JsonArray modules = data.getAsJsonArray("modules");
                for (JsonElement modElem : modules) {
                    JsonObject module = modElem.getAsJsonObject();
                    if (module.has("content") && !module.get("content").isJsonNull()) {
                        JsonArray content = module.getAsJsonArray("content");
                        for (JsonElement elem : content) {
                            JsonObject item = elem.getAsJsonObject();
                            JsonObject video = new JsonObject();
                            video.addProperty("vod_id", item.get("id").getAsString());
                            video.addProperty("vod_name", item.get("name").getAsString());
                            video.addProperty("vod_pic", fixImageUrl(item.get("banner_url").getAsString()));
                            video.addProperty("vod_remarks", item.has("vod_remarks") ? item.get("vod_remarks").getAsString() : "");
                            video.addProperty("vod_year", item.has("year") ? item.get("year").getAsString() : "");
                            videos.add(video);
                        }
                    }
                }
            }

            JsonObject result = new JsonObject();
            result.add("list", videos);
            return result.toString();
        } catch (Exception e) {
            SpiderDebug.log("homeVideoContent 错误: " + e.getMessage());
            return "{\"list\": []}";
        }
    }

    @Override
    public String categoryContent(String tid, String pg, boolean filter, HashMap<String, String> extend) {
        try {
            Map<String, String> query = new HashMap<>();
            query.put("type", tid);
            query.put("page", pg);
            query.put("limit", "20");

            if (extend.containsKey("class") && !TextUtils.isEmpty(extend.get("class"))) {
                query.put("class", extend.get("class"));
            }
            if (extend.containsKey("area") && !TextUtils.isEmpty(extend.get("area"))) {
                query.put("area", extend.get("area"));
            }
            if (extend.containsKey("year") && !TextUtils.isEmpty(extend.get("year"))) {
                query.put("year", extend.get("year"));
            }

            JsonObject resp = sendApiRequest("GET", "/api/category", query, null);
            if (resp.get("code").getAsInt() != 0) {
                return "{\"list\": []}";
            }
            JsonObject data = resp.getAsJsonObject("data");
            JsonArray list = data.getAsJsonArray("list");
            JsonArray videos = new JsonArray();
            for (JsonElement elem : list) {
                JsonObject item = elem.getAsJsonObject();
                JsonObject video = new JsonObject();
                video.addProperty("vod_id", item.get("vod_id").getAsString());
                video.addProperty("vod_name", item.get("vod_name").getAsString());
                video.addProperty("vod_pic", fixImageUrl(item.get("image_url").getAsString()));
                video.addProperty("vod_remarks", item.has("vod_remarks") ? item.get("vod_remarks").getAsString() : "");
                video.addProperty("vod_year", item.has("vod_year") ? item.get("vod_year").getAsString() : "");
                videos.add(video);
            }
            JsonObject result = new JsonObject();
            result.add("list", videos);
            result.addProperty("page", pg);
            return result.toString();
        } catch (Exception e) {
            SpiderDebug.log("categoryContent 错误: " + e.getMessage());
            return "{\"list\": []}";
        }
    }

    @Override
    public String detailContent(List<String> ids) {
        try {
            if (ids.isEmpty()) return "{\"list\": []}";
            String vodId = ids.get(0);
            JsonObject resp = sendApiRequest("GET", "/api/videos/" + vodId, null, null);
            if (resp.get("code").getAsInt() != 0) {
                return "{\"list\": []}";
            }
            JsonObject data = resp.getAsJsonObject("data");

            StringBuilder vodPlayFrom = new StringBuilder();
            StringBuilder vodPlayUrl = new StringBuilder();

            JsonArray playSources = data.getAsJsonArray("play_sources");
            for (JsonElement sourceElem : playSources) {
                JsonObject source = sourceElem.getAsJsonObject();
                String sourceName = source.get("source_name").getAsString();
                String sourceCode = source.get("source_code").getAsString();

                JsonArray episodes = source.getAsJsonArray("episodes");
                if (episodes.size() == 0) continue;

                if (vodPlayFrom.length() > 0) vodPlayFrom.append("$$$");
                vodPlayFrom.append(sourceName);

                StringBuilder episodesStr = new StringBuilder();
                for (JsonElement epElem : episodes) {
                    JsonObject ep = epElem.getAsJsonObject();
                    String name = ep.get("name").getAsString();
                    String url = ep.get("url").getAsString();
                    if (episodesStr.length() > 0) episodesStr.append("#");
                    episodesStr.append(name).append("$").append(sourceCode).append("@").append(url);
                }
                if (vodPlayUrl.length() > 0) vodPlayUrl.append("$$$");
                vodPlayUrl.append(episodesStr);
            }

            JsonObject vod = new JsonObject();
            vod.addProperty("vod_id", data.get("vod_id").getAsString());
            vod.addProperty("vod_name", data.get("vod_name").getAsString());
            vod.addProperty("vod_pic", fixImageUrl(data.get("vod_pic").getAsString()));
            vod.addProperty("vod_year", data.has("vod_year") ? data.get("vod_year").getAsString() : "");
            vod.addProperty("vod_area", data.has("vod_area") ? data.get("vod_area").getAsString() : "");
            vod.addProperty("vod_class", data.has("vod_class") ? data.get("vod_class").getAsString() : "");
            vod.addProperty("vod_blurb", data.has("vod_blurb") ? data.get("vod_blurb").getAsString() : "");
            vod.addProperty("vod_actor", data.has("vod_actor") ? data.get("vod_actor").getAsString() : "");
            vod.addProperty("vod_director", data.has("vod_director") ? data.get("vod_director").getAsString() : "");
            vod.addProperty("vod_remarks", data.has("vod_remarks") ? data.get("vod_remarks").getAsString() : "");
            vod.addProperty("score", data.has("score") ? data.get("score").getAsString() : "");
            vod.addProperty("vod_play_from", vodPlayFrom.toString());
            vod.addProperty("vod_play_url", vodPlayUrl.toString());
            vod.addProperty("vod_play_note", "$$$");

            JsonArray list = new JsonArray();
            list.add(vod);
            JsonObject result = new JsonObject();
            result.add("list", list);
            return result.toString();
        } catch (Exception e) {
            SpiderDebug.log("detailContent 错误: " + e.getMessage());
            return "{\"list\": []}";
        }
    }

    @Override
    public String searchContent(String key, boolean quick) {
        return searchContent(key, quick, "1");
    }

    @Override
    public String searchContent(String key, boolean quick, String pg) {
        try {
            Map<String, String> query = new HashMap<>();
            query.put("keyword", URLEncoder.encode(key, "UTF-8"));
            query.put("page", pg);
            query.put("limit", "20");
            JsonObject resp = sendApiRequest("GET", "/api/search", query, null);
            if (resp.get("code").getAsInt() != 0) {
                return "{\"list\": []}";
            }
            JsonObject data = resp.getAsJsonObject("data");
            JsonArray list = data.getAsJsonArray("list");
            JsonArray videos = new JsonArray();
            for (JsonElement elem : list) {
                JsonObject item = elem.getAsJsonObject();
                JsonObject video = new JsonObject();
                video.addProperty("vod_id", item.get("vod_id").getAsString());
                video.addProperty("vod_name", item.get("vod_name").getAsString());
                video.addProperty("vod_pic", fixImageUrl(item.get("image_url").getAsString()));
                video.addProperty("vod_remarks", item.has("vod_remarks") ? item.get("vod_remarks").getAsString() : "");
                video.addProperty("vod_year", item.has("vod_year") ? item.get("vod_year").getAsString() : "");
                videos.add(video);
            }
            JsonObject result = new JsonObject();
            result.add("list", videos);
            result.addProperty("page", pg);
            return result.toString();
        } catch (Exception e) {
            SpiderDebug.log("searchContent 错误: " + e.getMessage());
            return "{\"list\": []}";
        }
    }

    @Override
    public String playerContent(String flag, String id, List<String> vipFlags) {
        try {
            String[] parts = id.split("@", 2);
            if (parts.length < 2) {
                return buildPlayerResult(1, "", "参数错误", null);
            }
            String sourceCode = parts[0];
            String episodeUrl = parts[1];

            Map<String, String> body = new HashMap<>();
            body.put("url", episodeUrl);
            body.put("source_code", sourceCode);
            JsonObject resp = sendApiRequest("POST", "/api/videos/parse-url", null, body);
            if (resp.get("code").getAsInt() != 0) {
                return buildPlayerResult(1, episodeUrl, "解析失败", null);
            }
            JsonObject data = resp.getAsJsonObject("data");
            String parsedUrl = data.get("parsed_url").getAsString();

            // 提取响应中的 headers（可能为 null）
            JsonObject respHeaders = new JsonObject();
            if (data.has("headers") && !data.get("headers").isJsonNull() && data.get("headers").isJsonObject()) {
                respHeaders = data.getAsJsonObject("headers");
                SpiderDebug.log("播放响应包含自定义 headers: " + respHeaders.toString());
            }

            if (!TextUtils.isEmpty(parsedUrl)) {
                return buildPlayerResult(0, parsedUrl, null, respHeaders);
            } else {
                return buildPlayerResult(1, episodeUrl, "未获取到播放地址", null);
            }
        } catch (Exception e) {
            SpiderDebug.log("playerContent 错误: " + e.getMessage());
            return buildPlayerResult(1, "", e.getMessage(), null);
        }
    }

    private String buildPlayerResult(int jx, String url, String errorMsg, JsonObject respHeaders) {
        JsonObject header = new JsonObject();
        // 基础 User-Agent
        header.addProperty("User-Agent", userAgent);
        // 合并响应中的自定义 headers（如果有）
        if (respHeaders != null && respHeaders.size() > 0) {
            for (Map.Entry<String, JsonElement> entry : respHeaders.entrySet()) {
                if (!entry.getValue().isJsonNull()) {
                    header.add(entry.getKey(), entry.getValue());
                }
            }
        }
        JsonObject result = new JsonObject();
        result.addProperty("jx", jx);
        result.addProperty("parse", 0);
        result.addProperty("url", url);
        result.add("header", header);
        if (!TextUtils.isEmpty(errorMsg)) {
            result.addProperty("error", errorMsg);
        }
        return result.toString();
    }
}