# ruff: noqa: F841
"""Stages and serializers for supported serialization formats.

.. automodsumm:: requests_cache.serializers.preconf
   :nosignatures:
"""

import base64
import json
import pickle
from functools import partial
from importlib import import_module

from .._utils import get_placeholder_class
from .cattrs import CattrStage, _convert_floats, make_decimal_timedelta_converter
from .pipeline import SerializerPipeline, Stage


def make_stage(preconf_module: str, b64: bool = False, **kwargs):
    """Create a preconf serializer stage from a module name, if dependencies are installed.

    For JSON converters, force base64 encoding; cattrs defaults to base85, which
    (in the cpython implementation) has poor memory performance with large objects.
    """
    try:
        factory = import_module(preconf_module).make_converter
        stage = CattrStage(factory, **kwargs)
        if b64:
            # Override the default bytes hooks to use base64 encoding instead of base85
            stage.converter.register_unstructure_hook(
                bytes, lambda v: base64.b64encode(v).decode() if v else None
            )
            stage.converter.register_structure_hook(
                bytes, lambda v, _: base64.b64decode(v.encode()) if v else b''
            )
        return stage
    except ImportError as e:
        return get_placeholder_class(e)


# Pre-serialization stages
base_stage = CattrStage()  #: Base stage for all serializer pipelines
utf8_encoder = Stage(dumps=str.encode, loads=lambda x: x.decode())  #: Encode to bytes
utf8_serializer = SerializerPipeline([utf8_encoder], 'utf8', is_binary=True)  #: Encode to bytes
bson_preconf_stage = make_stage(
    'cattr.preconf.bson', convert_datetime=False
)  #: Pre-serialization for BSON
json_preconf_stage = make_stage('cattr.preconf.json', b64=True)  #: Pre-serialization for JSON
msgpack_preconf_stage = make_stage('cattr.preconf.msgpack')  #: Pre-serialization for msgpack
orjson_preconf_stage = make_stage('cattr.preconf.orjson', b64=True)  #: Pre-serialization for orjson
toml_preconf_stage = make_stage('cattr.preconf.tomlkit')  #: Pre-serialization for TOML
ujson_preconf_stage = make_stage('cattr.preconf.ujson', b64=True)  #: Pre-serialization for ujson
yaml_preconf_stage = make_stage('cattr.preconf.pyyaml')  #: Pre-serialization for YAML

# Basic serializers with no additional dependencies
dict_serializer = SerializerPipeline(
    [base_stage], name='dict', is_binary=False
)  #: Partial serializer that unstructures responses into dicts
pickle_serializer = SerializerPipeline(
    [base_stage, Stage(pickle)], name='pickle', is_binary=True
)  #: Pickle serializer


# Safe pickle serializer
def signer_stage(secret_key=None, salt='requests-cache') -> Stage:
    """Create a stage that uses ``itsdangerous`` to add a signature to responses on write, and
    validate that signature with a secret key on read. Can be used in a
    :py:class:`.SerializerPipeline` in combination with any other serialization steps.
    """
    from itsdangerous import Signer

    return Stage(
        Signer(secret_key=secret_key, salt=salt),
        dumps='sign',
        loads='unsign',
    )


def safe_pickle_serializer(secret_key=None, salt='requests-cache', **kwargs) -> SerializerPipeline:
    """Create a serializer that uses ``pickle`` + ``itsdangerous`` to add a signature to
    responses on write, and validate that signature with a secret key on read.
    """
    return SerializerPipeline(
        [base_stage, Stage(pickle), signer_stage(secret_key, salt)],
        name='safe_pickle',
        is_binary=True,
    )


try:
    import itsdangerous  # noqa: F401
except ImportError as e:
    signer_stage = get_placeholder_class(e)  # noqa: F811
    safe_pickle_serializer = get_placeholder_class(e)  # noqa: F811


# BSON/MongoDB document serializer
try:
    import bson

    bson_serializer = SerializerPipeline(
        [bson_preconf_stage, Stage(bson, dumps='encode', loads='decode')],
        name='bson',
        is_binary=True,
    )  #: Complete BSON serializer
    bson_document_serializer = SerializerPipeline(
        [bson_preconf_stage],
        name='bson_document',
        is_binary=False,
    )  #: BSON partial serializer that produces a MongoDB-compatible document
except ImportError as e:
    bson_serializer = get_placeholder_class(e)
    bson_document_serializer = get_placeholder_class(e)


# JSON serializer: stdlib
json_serializer = SerializerPipeline(
    [json_preconf_stage, Stage(dumps=partial(json.dumps, indent=2), loads=json.loads)],
    name='json',
    is_binary=False,
)  #: Complete JSON serializer using stdlib json module


# JSON serializer: ultrajson
try:
    import ujson

    ujson_serializer = SerializerPipeline(
        [ujson_preconf_stage, Stage(dumps=partial(ujson.dumps, indent=2), loads=ujson.loads)],
        name='ujson',
        is_binary=False,
    )  #: Complete JSON serializer using ultrajson module
except ImportError as e:
    ujson_serializer = get_placeholder_class(e)


# JSON serializer: orjson
try:
    import orjson

    orjson_serializer = SerializerPipeline(
        [
            orjson_preconf_stage,
            Stage(dumps=partial(orjson.dumps, option=orjson.OPT_INDENT_2), loads=orjson.loads),
        ],
        name='orjson',
        is_binary=True,
    )  #: Complete JSON serializer using orjson module
except ImportError as e:
    orjson_serializer = get_placeholder_class(e)


# YAML serializer
try:
    import yaml

    _yaml_stage = Stage(yaml, loads='safe_load', dumps='safe_dump')
    yaml_serializer = SerializerPipeline(
        [yaml_preconf_stage, _yaml_stage],
        name='yaml',
        is_binary=False,
    )  #: Complete YAML serializer
except ImportError as e:
    yaml_serializer = get_placeholder_class(e)


# DynamoDB document serializer
dynamodb_preconf_stage = CattrStage(
    factory=make_decimal_timedelta_converter, convert_timedelta=False
)  #: Pre-serialization steps for DynamoDB
convert_float_stage = Stage(dumps=_convert_floats, loads=lambda x: x)
dynamodb_document_serializer = SerializerPipeline(
    [dynamodb_preconf_stage, convert_float_stage],
    name='dynamodb_document',
    is_binary=False,
)  #: DynamoDB-compatible document serializer
