# --------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# Code generated by Microsoft (R) Python Code Generator.
# Changes may cause incorrect behavior and will be lost if the code is regenerated.
# --------------------------------------------------------------------------

import json
import os
from typing import Any, IO, Mapping, Optional, Union

from .._utils.model_base import Model, SdkJSONEncoder

# file-like tuple could be `(filename, IO (or bytes))` or `(filename, IO (or bytes), content_type)`
FileContent = Union[str, bytes, IO[str], IO[bytes]]

FileType = Union[
    # file (or bytes)
    FileContent,
    # (filename, file (or bytes))
    tuple[Optional[str], FileContent],
    # (filename, file (or bytes), content_type)
    tuple[Optional[str], FileContent, Optional[str]],
]


def serialize_multipart_data_entry(data_entry: Any) -> Any:
    if isinstance(data_entry, (list, tuple, dict, Model)):
        return json.dumps(data_entry, cls=SdkJSONEncoder, exclude_readonly=True)
    return data_entry


def _normalize_multipart_file_entry(field_name: str, entry: Any, index: int) -> Any:
    """Ensure each multipart file entry carries a filename so that it is encoded
    as a file part (with ``filename=``) rather than a plain form field.

    Servers commonly distinguish multipart file parts from form-data parts by
    the presence of ``filename=`` in the part's ``Content-Disposition`` header.
    When callers pass bare bytes / str / IO objects (e.g.
    ``Path("x.zip").read_bytes()``), the underlying HTTP client emits the part
    without a filename, which several Foundry endpoints reject with errors like
    "At least one file must be uploaded". This helper synthesizes a filename
    from the IO object's ``name`` attribute when available, otherwise falls
    back to a stable default.

    :param field_name: The multipart form field name, used as a fallback filename.
    :type field_name: str
    :param entry: The file entry to normalize. May be a tuple, bytes, str, or IO object.
    :type entry: any
    :param index: Position of the entry within its field's list, used to disambiguate fallback filenames.
    :type index: int
    :return: A ``(filename, entry)`` tuple if ``entry`` was not already a tuple, otherwise ``entry`` unchanged.
    :rtype: any
    """
    if isinstance(entry, tuple):
        return entry
    filename: Optional[str] = None
    name_attr = getattr(entry, "name", None)
    if isinstance(name_attr, str) and name_attr:
        filename = os.path.basename(name_attr)
    if not filename:
        filename = f"{field_name}_{index}" if index else field_name
    return (filename, entry)


def prepare_multipart_form_data(
    body: Mapping[str, Any], multipart_fields: list[str], data_fields: list[str]
) -> list[FileType]:
    files: list[FileType] = []

    # Append data fields first so they appear before file parts in the encoded
    # multipart body. Some streaming server-side parsers (e.g. the Foundry
    # hosted-agents `create_agent_version_from_code` endpoint) require small
    # JSON metadata parts to precede large binary file parts; otherwise they
    # report the metadata part as missing.
    for data_field in data_fields:
        data_entry = body.get(data_field)
        if data_entry:
            files.append((data_field, str(serialize_multipart_data_entry(data_entry))))

    for multipart_field in multipart_fields:
        multipart_entry = body.get(multipart_field)
        if isinstance(multipart_entry, list):
            for idx, e in enumerate(multipart_entry):
                files.append((multipart_field, _normalize_multipart_file_entry(multipart_field, e, idx)))
        elif multipart_entry:
            files.append((multipart_field, _normalize_multipart_file_entry(multipart_field, multipart_entry, 0)))

    return files
