from __future__ import annotations

import os
import pathlib
from typing import IO, Any, Mapping, Optional, Sequence, Tuple, Union, cast

from ._types import (
    FileContent,
    FileTypes,
    HttpxFileTypes,
    HttpxRequestFiles,
    is_given,
)


def _is_file_content(obj: object) -> bool:
    return isinstance(obj, (bytes, IO)) or isinstance(obj, os.PathLike)


def _read_file_content(content: FileContent) -> bytes:
    if isinstance(content, bytes):
        return content
    if isinstance(content, os.PathLike):
        return pathlib.Path(content).read_bytes()
    return content.read()


def _transform_file(file: FileTypes) -> HttpxFileTypes:
    if isinstance(file, (bytes, IO)):
        return file
    if isinstance(file, os.PathLike):
        path = pathlib.Path(file)
        return (path.name, path.read_bytes())
    if isinstance(file, tuple):
        name = file[0]
        content = _read_file_content(file[1])
        rest = file[2:]
        return (name, content, *rest)  # type: ignore[return-value]
    return file  # type: ignore[return-value]


def to_httpx_files(files: Sequence[Tuple[str, FileTypes]]) -> list[tuple[str, HttpxFileTypes]]:
    return [(name, _transform_file(f)) for name, f in files]


def extract_files(
    query: dict[str, object],
    *,
    paths: Sequence[Sequence[str]],
) -> list[tuple[str, FileTypes]]:
    """Extract file fields from *query* (mutating it) and return as a flat list."""
    files: list[tuple[str, FileTypes]] = []
    for path in paths:
        files.extend(_extract_items(query, path, index=0, flattened_key=None))
    return files


def _extract_items(
    obj: object,
    path: Sequence[str],
    index: int,
    flattened_key: str | None,
) -> list[tuple[str, FileTypes]]:
    if index >= len(path):
        if _is_file_content(obj) or (isinstance(obj, tuple) and len(obj) >= 2):
            key = flattened_key or "file"
            return [(key, cast(FileTypes, obj))]
        return []

    key = path[index]

    if isinstance(obj, dict):
        if key == "<array>":
            return []
        val = obj.get(key)
        if val is None or not is_given(val):
            return []
        items = _extract_items(val, path, index + 1, flattened_key=key if flattened_key is None else flattened_key)
        if items:
            obj.pop(key, None)
        return items

    if isinstance(obj, list):
        if key != "<array>":
            return []
        result: list[tuple[str, FileTypes]] = []
        arr_key = (flattened_key or "file") + "[]"
        i = 0
        while i < len(obj):
            sub = _extract_items(obj[i], path, index + 1, flattened_key=arr_key)
            if sub:
                result.extend(sub)
                obj.pop(i)
            else:
                i += 1
        return result

    return []
