from __future__ import annotations

import base64
import hashlib
import json
import secrets
import string
from typing import Any

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

# From feemoo web app module 36157 — RSA public key for `pto` header.
FEEMOO_DEVICE_PUBLIC_KEY_B64 = (
    "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDIBI+1eO2zaS1kpyy4f3tRxHOb"
    "5gUFWV5ecA7bhKHtkszrEg1+eNBhBxGXfVSj4D9N/Pq1+cHbqxWfJPeg3Rc/j6hEr"
    "11kHimC6wqB9WtT8eCYtuo/UGFEi37YREKRdf5g3tdysFdnEBOWxgdQOJ76qrRaxV"
    "WOaw/3Ih31eDO8VQIDAQAB"
)

# From feemoo web app module 81211 — RSA public key for request `sn`.
FEEMOO_RSA_PUBLIC_KEY_B64 = (
    "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApkEFC8tcLCKGX/BhQ6bp"
    "YUN6bJCB7uYG8qGvjudOuKw8B53BXGYbBm5gQgcv0IVmfL8qkbRahC2JkvzLKRMw"
    "+xM3ZkoWwinPEYt8ni34+bv0HwsEXh662TTYgJSJDPB/XP2X8p2OEY84ZnUc3XGo"
    "9gxI6NdlgpLFG08oLqA1zlf5A2/QNAZqzJoSSaNGTPGXw4BHuqdvmOmHuCh1wDEi"
    "FKcLgww0ns4cEB28x4lUAA4z18Fk1e30CVAzz3BABi01Ay5EJbB45StGYyma9R7R"
    "WsJrptt/RLsc1RmkX9jq/LjCC4dxy3hWsLwxb7eMzpTvErhpUmQZ0bDS8Vpo9jkz"
    "tQIDAQAB"
)

# From feemoo web app module 26106 — RSA private key for response `ak`.
FEEMOO_RSA_PRIVATE_KEY_PEM = (
    "-----BEGIN PRIVATE KEY-----\n"
    "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCgze9N5ivwCMfu\n"
    "cM2PRg//C8On3XtGYoSWYEWC3kG/Tj7tXctddthK0sE2buW0dLNkUJ6prcFqB5xU\n"
    "FEDoQqGaDXaQKUHzqf0r3idcZM1QiARZp6A6MEkToz+2zL+0Gu4ZXhXG2vBInMaN\n"
    "SdOvZo9S0S+6y4qPAIo0p6/K6ucs4g9SGrxkamc2h7KdlKl5U+a3OTgdt78oLfkx\n"
    "vHQJonL66O6QGVsd1UH11Vnj3BqAm7iwDJ/69BjEeV/jzqxzGw6gIEkbHeg9PB69\n"
    "ms1NntMAeYGj+vXzRHl0fl5kgB7k5u0LXtTGzrpGt7B15xx/5JaGsWoyZ33/F4dw\n"
    "A6o5iJrxAgMBAAECggEAIpWH5ToeswZAcbDDFRBdel3+W4kP7Uhc+baE3KuOemWK\n"
    "tSRaC6T2ycjgEyPwtUnwFyAbr6SWF4bBgXfFqe5CsU459LFa5NvnflrOHlnzqmFG\n"
    "hj2+ASXHYbjXiCorBMJR+RS6HEWULhnBvjtlkrC9HHOvDz6dczoLf12dOkghJg61\n"
    "LeNKsa0nCb/7goiPd1WlsrB2JgHdyY9W0WJtsdh4W57tV+zFnewg9t9zHBd2f8Mo\n"
    "MIdJdqv5CCp5Nuixm/q/FoPoR+M9DgDxIceUBXsY/fIXTDmZtzkf+gmInyie+HtK\n"
    "6DREGeF37chjNyHc1sOCF003XHTP76fb0yqLcfBXEQKBgQDP+beKWOg7Ne8a3n4u\n"
    "uSuu357dvFh1KbJWPvDOO/pGW3vYaPfGto/hbD+NH4QkygHog3EkYMJ+8cg67KcP\n"
    "PkbOGRV3w5233rENZ+KpGFZpp5EROXJzjRSKnQxdUVecoCGwLRaFjqpKNV30AcHT\n"
    "k5LUTsyA03YfCMMvAxqnODSgfQKBgQDF7760ojkzROL2nihxMvXDH8JwyMn/TWDg\n"
    "V7OirKcZWck5sxImYXawtOCAnrykThUClLAURts+ZAAuOKsefiUATN4yPgcL27y4\n"
    "sFr0E5bdYvNHFz30GXQlx7UVmqCwBJ2nmaAL+quR6wWgM5zr637px5cOIz1bB9eB\n"
    "i3iOieVChQKBgQDM0wMQHb+ZkOSLcdST74o2D8ZGWthDi203w4Ull6xE3pR57ecY\n"
    "rXPGbvWn5wIduVVnyQwE58alfznTzn45ZPwWvl2GNgF+tLNqoIV6Q53XMFCOxiUR\n"
    "6D/yw/kJbrL6bjq/Z6hmoLrVmvmM9q4N0akprvF5bslGhzqzcjsttQ0fgQKBgCaD\n"
    "j8f2CMtBDCiQfa4JDGj7NQpdB3D1zXPZnMp6bckw/0aK4W02abHYgBaGzI3r44qt\n"
    "A9XuM5rgOmjD/gfg47I7QsDj8NsN1prZNdloNmK9bjjW/OcSTNdNFJ8vhzS4sTR7\n"
    "X/j0vY2W/XE+mboOCJPUkj/d+4JK9FPwn3ScwE9BAoGBALr1Wm8nkUzk3GFwDL9L\n"
    "hwCVpjNRk8MYiXxOgt7TNZKBlLZkchDXxyJfDlghqi5ypfgPj5P9hEcpoTLGRyU8\n"
    "m+iw0d/c9fVZFu8CfKpcpnTXU9FU7tdgLZxk+g90yfqre2eb2SFzJ4fDrWGnGXJX\n"
    "Wmm+hMfwD/hG3BhuGeWZXqfF\n"
    "-----END PRIVATE KEY-----\n"
)

_ALPHABET = string.ascii_letters + string.digits
_DEVICE_PUBLIC_KEY = load_der_public_key(
    base64.b64decode(FEEMOO_DEVICE_PUBLIC_KEY_B64),
    backend=default_backend(),
)
_REQUEST_PUBLIC_KEY = load_der_public_key(
    base64.b64decode(FEEMOO_RSA_PUBLIC_KEY_B64),
    backend=default_backend(),
)
_RESPONSE_PRIVATE_KEY = load_pem_private_key(
    FEEMOO_RSA_PRIVATE_KEY_PEM.encode("utf-8"),
    password=None,
    backend=default_backend(),
)


def genak(length: int = 12) -> str:
    return "".join(secrets.choice(_ALPHABET) for _ in range(length))


def _md5_key_material(key: str) -> tuple[bytes, bytes]:
    digest = hashlib.md5(key.encode("utf-8")).hexdigest()
    return digest[:16].encode("utf-8"), digest[16:].encode("utf-8")


def aes_encrypt(payload: dict[str, Any] | str, key: str) -> str:
    raw = json.dumps(payload, ensure_ascii=False, separators=(",", ":")) if isinstance(payload, dict) else str(payload)
    iv, aes_key = _md5_key_material(key)
    padder = sym_padding.PKCS7(128).padder()
    padded = padder.update(raw.encode("utf-8")) + padder.finalize()
    cipher = Cipher(algorithms.AES(aes_key), modes.CBC(iv), backend=default_backend())
    encryptor = cipher.encryptor()
    encrypted = encryptor.update(padded) + encryptor.finalize()
    return base64.b64encode(encrypted).decode("ascii")


def aes_decrypt(ciphertext_b64: str, key: str) -> str:
    iv, aes_key = _md5_key_material(key)
    encrypted = base64.b64decode(ciphertext_b64)
    cipher = Cipher(algorithms.AES(aes_key), modes.CBC(iv), backend=default_backend())
    decryptor = cipher.decryptor()
    padded = decryptor.update(encrypted) + decryptor.finalize()
    unpadder = sym_padding.PKCS7(128).unpadder()
    raw = unpadder.update(padded) + unpadder.finalize()
    return raw.decode("utf-8")


def build_device_headers(device_key: str) -> dict[str, str]:
    """Generate par/pto headers required by fmpapi (device registration)."""
    session_key = genak()
    pto = base64.b64encode(
        _DEVICE_PUBLIC_KEY.encrypt(session_key.encode("utf-8"), asym_padding.PKCS1v15())
    ).decode("ascii")
    par = aes_encrypt({"device_key": device_key}, session_key)
    return {"par": par, "pto": pto}


def rsa_encrypt_session_key(session_key: str) -> str:
    encrypted = _REQUEST_PUBLIC_KEY.encrypt(
        session_key.encode("utf-8"),
        asym_padding.PKCS1v15(),
    )
    return base64.b64encode(encrypted).decode("ascii")


def rsa_decrypt_session_key(ciphertext_b64: str) -> str:
    encrypted = base64.b64decode(ciphertext_b64)
    raw = _RESPONSE_PRIVATE_KEY.decrypt(encrypted, asym_padding.PKCS1v15())
    return raw.decode("utf-8")


def wrap_request(payload: dict[str, Any], session_key: str, sn: str) -> dict[str, str]:
    return {"sn": sn, "jt": aes_encrypt(payload, session_key)}


def unwrap_response(data: Any) -> Any:
    if not isinstance(data, dict):
        return data
    if "ak" not in data:
        return data
    session_key = rsa_decrypt_session_key(str(data["ak"]))
    decrypted = aes_decrypt(str(data["ed"]), session_key)
    try:
        return json.loads(decrypted)
    except json.JSONDecodeError:
        return decrypted
