# -*- coding: utf-8 -*-
# BSPHP：MD5、AES-128-CBC、RSA PKCS#1 v1.5。

from __future__ import annotations

import base64
import hashlib
import re
from typing import Tuple

from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import padding, serialization
from cryptography.hazmat.primitives.asymmetric import padding as asym_padding
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes


def md5_hex(s: str) -> str:
    return hashlib.md5(s.encode("utf-8")).hexdigest()


def _load_private_key_der_b64(b64: str):
    raw = b64.strip()
    for a, b in (
        ("-----BEGIN RSA PRIVATE KEY-----", "-----END RSA PRIVATE KEY-----"),
        ("-----BEGIN PRIVATE KEY-----", "-----END PRIVATE KEY-----"),
    ):
        raw = raw.replace(a, "").replace(b, "")
    raw = re.sub(r"\s+", "", raw)
    der = base64.b64decode(raw)
    return serialization.load_der_private_key(der, password=None, backend=default_backend())


def _load_public_key_der_b64(b64: str):
    raw = b64.strip()
    for a, b in (
        ("-----BEGIN PUBLIC KEY-----", "-----END PUBLIC KEY-----"),
        ("-----BEGIN RSA PUBLIC KEY-----", "-----END RSA PUBLIC KEY-----"),
    ):
        raw = raw.replace(a, "").replace(b, "")
    raw = re.sub(r"\s+", "", raw)
    der = base64.b64decode(raw)
    return serialization.load_der_public_key(der, backend=default_backend())


def aes128_cbc_encrypt_base64(plaintext: str, key16: str) -> str:
    key = key16[:16].encode("utf-8")
    iv = key
    padder = padding.PKCS7(128).padder()
    data = padder.update(plaintext.encode("utf-8")) + padder.finalize()
    cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())
    encryptor = cipher.encryptor()
    ct = encryptor.update(data) + encryptor.finalize()
    return base64.b64encode(ct).decode("ascii")


def aes128_cbc_decrypt_base64(ciphertext_b64: str, key16: str) -> str:
    key = key16[:16].encode("utf-8")
    iv = key
    ct = base64.b64decode(ciphertext_b64)
    cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())
    decryptor = cipher.decryptor()
    data = decryptor.update(ct) + decryptor.finalize()
    unpadder = padding.PKCS7(128).unpadder()
    pt = unpadder.update(data) + unpadder.finalize()
    return pt.decode("utf-8")


def rsa_encrypt_pkcs1_base64(message: str, public_key_b64_der: str) -> str:
    pub = _load_public_key_der_b64(public_key_b64_der)
    enc = pub.encrypt(message.encode("utf-8"), asym_padding.PKCS1v15())
    return base64.b64encode(enc).decode("ascii")


def rsa_decrypt_pkcs1_base64(ciphertext_b64: str, private_key_b64_der: str) -> str:
    priv = _load_private_key_der_b64(private_key_b64_der)
    ct = base64.b64decode(ciphertext_b64)
    dec = priv.decrypt(ct, asym_padding.PKCS1v15())
    return dec.decode("utf-8")


def build_request_payload(
    plaintext_params: str,
    server_private_key_b64: str,
    client_public_key_b64: str,
    appsafecode: str,
) -> Tuple[str, str]:
    aes_key_full = md5_hex(server_private_key_b64 + appsafecode)
    aes_key = aes_key_full[:16]
    enc_b64 = aes128_cbc_encrypt_base64(plaintext_params, aes_key)
    sig_md5 = md5_hex(enc_b64)
    signature_content = f"0|AES-128-CBC|{aes_key}|{sig_md5}|json"
    rsa_b64 = rsa_encrypt_pkcs1_base64(signature_content, client_public_key_b64)
    payload = f"{enc_b64}|{rsa_b64}"
    from urllib.parse import quote

    encoded = quote(payload, safe="-_.~")
    return payload, encoded


def decrypt_response_body(
    raw: str,
    server_private_key_b64: str,
    appsafecode: str,
) -> dict | None:
    """解析 AppEn 响应体。

    兼容两种常见格式（与 `BSPHP-Python案例Aes加密/bsphp/http.py` 的 `_decrypt_v2` 及新版协议一致）：
    - 三段：`前缀|AES密文Base64|RSA签名Base64`（前缀常为版本号等）
    - 两段：`AES密文Base64|RSA签名Base64`（参考案例仅按两段解析）
    """
    from urllib.parse import unquote

    body = unquote(raw) if "%" in raw else raw
    parts = body.split("|")
    if len(parts) >= 3:
        resp_enc_b64 = parts[1].strip()
        resp_rsa_b64 = parts[2].strip()
    elif len(parts) == 2:
        resp_enc_b64 = parts[0].strip()
        resp_rsa_b64 = parts[1].strip()
    else:
        return None
    sig = rsa_decrypt_pkcs1_base64(resp_rsa_b64, server_private_key_b64)
    sig_parts = sig.split("|")
    if len(sig_parts) < 4:
        return None
    resp_aes_key = sig_parts[2][:16]
    decrypted = aes128_cbc_decrypt_base64(resp_enc_b64, resp_aes_key)
    import json

    try:
        j = json.loads(decrypted)
    except json.JSONDecodeError:
        return None
    resp = j.get("response")
    if not isinstance(resp, dict):
        return None
    if resp.get("appsafecode") != appsafecode:
        resp = dict(resp)
        resp["data"] = "appsafecode 安全参数验证不通过"
    return resp
