"""Shared multipart/form-data parser used by hook and page SDKs.

Multipart bodies reach the worker one of two ways:

- inline, base64-encoded in ``Request.body`` (the default transport), or
- offloaded to a shared-EFS temp file whose project-relative path is carried in
  ``Request.body_ref`` (cloud-api does this for large uploads to keep the
  RabbitMQ message small — see cloud-api FilesRepository.persistUploadBody).

``load_upload_bytes`` hides that split so callers always get the raw bytes with
no base64 round-trip. Parts are yielded with raw bytes preserved — callers that
need base64 (e.g. the public hooks contract) can re-encode after the fact.
"""

import base64
import os
from io import BytesIO
from typing import Dict, List, Union

from abstra_internals.utils.insensitive_dict import CaseInsensitiveDict

FilePart = Dict[str, Union[str, bytes]]
TextPart = Dict[str, str]
Part = Union[FilePart, TextPart]


def _parse_parts(raw_bytes: bytes, headers: CaseInsensitiveDict) -> List[Part]:
    """Parse raw multipart/form-data bytes into a list of parts.

    File parts are returned as::

        {"name": str, "filename": str, "content_type": str, "content": bytes}

    Text parts are returned as::

        {"name": str, "value": str}
    """
    from multipart import MultipartParser, MultipartPart, parse_options_header

    _, options = parse_options_header(headers["Content-Type"])
    boundary = options["boundary"].encode("utf-8")
    parser = MultipartParser(BytesIO(raw_bytes), boundary)
    parts: List[Part] = []
    for i in parser:
        if not isinstance(i, MultipartPart):
            continue
        filename = getattr(i, "filename", None)
        if filename:
            raw = getattr(i, "raw", b"")
            parts.append(
                {
                    "name": i.name,
                    "filename": filename,
                    "content_type": getattr(
                        i, "content_type", "application/octet-stream"
                    ),
                    "content": raw,
                }
            )
        else:
            parts.append({"name": i.name, "value": i.value})
    return parts


def load_upload_bytes(request) -> bytes:
    """Return the raw multipart bytes for a ``Request``.

    Reads the shared-EFS temp file when cloud-api offloaded a large body
    (``body_ref``), otherwise decodes the inline base64 ``body``.
    """
    body_ref = getattr(request, "body_ref", None)
    if body_ref:
        from abstra_internals.constants import get_persistent_dir

        base = get_persistent_dir().resolve()
        path = (base / body_ref).resolve()
        if os.path.commonpath([str(base), str(path)]) != str(base):
            raise ValueError(f"Upload body_ref escapes project files dir: {body_ref}")
        return path.read_bytes()
    return base64.b64decode(request.body, validate=True)


def parse_multipart_request(request, headers: CaseInsensitiveDict) -> List[Part]:
    """Parse the multipart body of a ``Request`` (inline or EFS-offloaded)."""
    return _parse_parts(load_upload_bytes(request), headers)


def parse_multipart(body: str, headers: CaseInsensitiveDict) -> List[Part]:
    """Parse a base64-encoded multipart body (inline transport)."""
    return _parse_parts(base64.b64decode(body, validate=True), headers)
