"""
    When doing requests to NeoFS, we get JSON output as an automatically decoded
    structure from protobuf. Some fields are decoded with boilerplates and binary
    values are Base64-encoded.

    This module contains functions which rearrange the structure and reencode binary
    data from Base64 to Base58.
"""

import base64

import base58


def decode_simple_header(data: dict) -> dict:
    """
    This function reencodes Simple Object header and its attributes.
    """
    try:
        data = decode_common_fields(data)

        # Normalize object attributes
        data["header"]["attributes"] = {
            attr["key"]: attr["value"] for attr in data["header"]["attributes"]
        }
    except Exception as exc:
        raise ValueError(f"failed to decode JSON output: {exc}") from exc

    return data


def decode_split_header(data: dict) -> dict:
    """
    This function rearranges Complex Object header.
    The header holds SplitID, a random unique
    number, which is common among all splitted objects, and IDs of the Linking
    Object and the last splitted Object.
    """
    try:
        data["splitId"] = json_reencode(data["splitId"])
        data["lastPart"] = json_reencode(data["lastPart"]["value"]) if data["lastPart"] else None
        data["link"] = json_reencode(data["link"]["value"]) if data["link"] else None
    except Exception as exc:
        raise ValueError(f"failed to decode JSON output: {exc}") from exc

    return data


def decode_linking_object(data: dict) -> dict:
    """
    This function reencodes Linking Object header.
    It contains IDs of child Objects and Split Chain data.
    """
    try:
        data = decode_simple_header(data)
        split = data["header"]["split"]
        split["children"] = [json_reencode(item["value"]) for item in split["children"]]
        split["splitID"] = json_reencode(split["splitID"])
        split["previous"] = json_reencode(split["previous"]["value"]) if split["previous"] else None
        split["parent"] = json_reencode(split["parent"]["value"]) if split["parent"] else None
    except Exception as exc:
        raise ValueError(f"failed to decode JSON output: {exc}") from exc

    return data


def decode_storage_group(data: dict) -> dict:
    """
    This function reencodes Storage Group header.
    """
    try:
        data = decode_common_fields(data)
    except Exception as exc:
        raise ValueError(f"failed to decode JSON output: {exc}") from exc

    return data


def decode_tombstone(data: dict) -> dict:
    """
    This function reencodes Tombstone header.
    """
    try:
        data = decode_simple_header(data)
        data["header"]["sessionToken"] = decode_session_token(data["header"]["sessionToken"])
    except Exception as exc:
        raise ValueError(f"failed to decode JSON output: {exc}") from exc
    return data


def decode_session_token(data: dict) -> dict:
    """
    This function reencodes a fragment of header which contains
    information about session token.
    """
    target = data["body"]["object"]["target"]
    target["container"] = json_reencode(target["container"]["value"])
    target["objects"] = [json_reencode(obj["value"]) for obj in target["objects"]]
    return data


def json_reencode(data: str) -> str:
    """
    According to JSON protocol, binary data (Object/Container/Storage Group IDs, etc)
    is converted to string via Base58 encoder. But we usually operate with Base64-encoded format.
    This function reencodes given Base58 string into the Base64 one.
    """
    return base58.b58encode(base64.b64decode(data)).decode("utf-8")


def encode_for_json(data: str) -> str:
    """
    This function encodes binary data for sending them as protobuf
    structures.
    """
    return base64.b64encode(base58.b58decode(data)).decode("utf-8")


def decode_common_fields(data: dict) -> dict:
    """
    Despite of type (simple/complex Object, Storage Group, etc) every Object
    header contains several common fields.
    This function rearranges these fields.
    """
    data["objectID"] = json_reencode(data["objectID"]["value"])

    header = data["header"]
    header["containerID"] = json_reencode(header["containerID"]["value"])
    header["ownerID"] = json_reencode(header["ownerID"]["value"])
    header["payloadHash"] = json_reencode(header["payloadHash"]["sum"])
    header["version"] = f"{header['version']['major']}{header['version']['minor']}"
    # Homomorphic hash is optional and its calculation might be disabled in trusted network
    if header.get("homomorphicHash"):
        header["homomorphicHash"] = json_reencode(header["homomorphicHash"]["sum"])

    return data