import globalConfig from '@/config';
import { CompressMethod } from '@/config/types';
import {
    bytesToNumber,
    numberToBytesAt,
    Endian,
    bytesToString,
    stringToBytes,
} from '@/utils/bytesUtils';
import {
    aesCbcDecryptBytes,
    aesCbcEncryptBytes,
    bytesBase64decode,
    bytesBase64encode,
} from '@/utils/crypto';

import { compressBytes, decompressBytes, CompressFormat } from './compress';
import { currentUnixTimestamp } from './timeUtils';

export type JsonPrimitive = string | number | boolean | null;
export type JsonValue = JsonPrimitive | JsonValue[] | { [key: string]: JsonValue | undefined };

const TIMESTAMP_BYTES = 8;

function buildTimestampBytes(timestamp: number): Uint8Array {
    const arr = new Uint8Array(TIMESTAMP_BYTES);
    numberToBytesAt(0, arr, 0, Endian.BE);
    numberToBytesAt(timestamp, arr, 4, Endian.BE);
    return arr;
}

function parseTimestampBytes(bytes: Uint8Array): number {
    const high = bytesToNumber(bytes, 0, Endian.BE);
    const low = bytesToNumber(bytes, 4, Endian.BE);
    return high * 0x100000000 + low;
}

function compressFormatFromMethod(method: CompressMethod): CompressFormat {
    return method === CompressMethod.BR ? CompressFormat.BROTLI : CompressFormat.GZIP;
}

/**
 * 请求体加密（与后端 GoDataEncrypt 对应）
 * 仅接收字符串：8字节时间戳(BE)+数据 → 压缩() → AES-CBC 加密 → IV + 密文
 * @param plaintext 明文字符串
 * @param key 密钥（由 crypto 内部补齐/截断为 32 字节）
 * @param compressMethod 压缩算法，空表示不压缩
 * @param timestamp 时间戳（秒级数字），内部转为 8 字节大端后拼在明文前
 * @returns 二进制密文（IV 16 字节 + 密文）
 */
export async function encryptRequestPayload(
    dataBytes: Uint8Array,
    timestamp: number,
    key: Uint8Array,
    compressMethod: CompressMethod = CompressMethod.NO_ZIP
): Promise<Uint8Array> {
    const compressed =
        compressMethod && compressMethod !== CompressMethod.NO_ZIP
            ? await compressBytes(dataBytes, compressFormatFromMethod(compressMethod))
            : dataBytes;

    const timestampBytes = buildTimestampBytes(timestamp);
    const timeAndData = new Uint8Array(TIMESTAMP_BYTES + compressed.length);
    timeAndData.set(timestampBytes, 0);
    timeAndData.set(compressed, TIMESTAMP_BYTES);

    return aesCbcEncryptBytes(timeAndData, key);
}

/**
 * 响应体解密（与后端 GoDataDecrypt 对应）
 * 仅接收二进制：IV + 密文 → AES-CBC 解密 → 去掉 8 字节时间戳 → 可选解压
 * @param encryptedBytes 二进制密文（IV 16 字节 + 密文）
 * @param key 密钥（由 crypto 内部补齐/截断为 32 字节）
 * @param compressMethod 解压算法，空表示未压缩
 * @returns 去掉时间戳并解压后的数据
 */
export async function decryptResponsePayload(
    encryptedBytes: Uint8Array,
    key: Uint8Array,
    compressMethod: CompressMethod = CompressMethod.NO_ZIP
): Promise<{ timestamp: number; data: Uint8Array }> {
    const decrypted = aesCbcDecryptBytes(encryptedBytes, key);
    if (decrypted.length < TIMESTAMP_BYTES) return { timestamp: 0, data: new Uint8Array(0) };

    const timestampBytes = decrypted.subarray(0, TIMESTAMP_BYTES);
    const timestamp = parseTimestampBytes(timestampBytes);

    const dataBytes = decrypted.subarray(TIMESTAMP_BYTES);

    const rawBytes =
        compressMethod && compressMethod !== CompressMethod.NO_ZIP
            ? await decompressBytes(dataBytes, compressFormatFromMethod(compressMethod))
            : dataBytes;

    return { timestamp, data: rawBytes };
}

/**
 * 加密 URL 参数
 * @param params 参数对象
 * @returns 加密后的 URL 参数
 */
export async function encryptUrlParams(params: JsonValue): Promise<string> {
    const jsonString = typeof params === 'string' ? params : JSON.stringify(params);
    const key = stringToBytes(globalConfig.security.requestEncryptionKey);
    const dataBytes = stringToBytes(jsonString);
    const encrypted = await encryptRequestPayload(
        dataBytes,
        currentUnixTimestamp(),
        key,
        globalConfig.security.compressMethod
    );
    return bytesBase64encode(encrypted, true);
}

/**
 * 解密 URL 参数
 * @param params 加密后的 URL 参数
 * @returns 解密后的参数对象
 */
export async function decryptUrlParams<T = any>(params: string): Promise<T | null> {
    // const key = bytesBase64decode(globalConfig.security.requestEncryptionKey);
    const key = stringToBytes(globalConfig.security.requestEncryptionKey);
    const dataBytes = bytesBase64decode(params, true);
    const compressMethod = globalConfig.security.compressMethod;
    try {
        const { data } = await decryptResponsePayload(dataBytes, key, compressMethod);
        const result = bytesToString(data);
        return JSON.parse(result) as T;
    } catch {
        return null;
    }
}
