from __future__ import annotations

from typing import Any, Dict, Iterator, List, Optional, Union, Awaitable, AsyncIterator

from .client import PolarionClient, PolarionAsyncClient
from .resource import PolarionResource, DeepFields
from .error import raise_from_response
from .paging import resolve_page_params

# Generated client bits
from polarion_rest_client.openapi.types import UNSET, Unset

# Low-level endpoints (read operations use generated functions)
from polarion_rest_client.openapi.api.document_attachments.get_document_attachments import (
    sync_detailed as _get_document_attachments,
    asyncio_detailed as _get_document_attachments_async,
)
from polarion_rest_client.openapi.api.document_attachments.get_document_attachment import (
    sync_detailed as _get_document_attachment,
    asyncio_detailed as _get_document_attachment_async,
)
from polarion_rest_client.openapi.api.document_attachments.get_document_attachment_content import (
    sync_detailed as _get_document_attachment_content,
    asyncio_detailed as _get_document_attachment_content_async,
)


def _build_fields_param_for_attachments(
    fields_attachments: Optional[Union[str, List[str]]],
) -> Union[Unset, DeepFields]:
    """
    Turn `fields_attachments` into a deep-object fields parameter
    for the 'document_attachments' resource.
    """
    if not fields_attachments:
        return UNSET
    return DeepFields(
        "document_attachments",
        fields_attachments
        if isinstance(fields_attachments, str)
        else list(fields_attachments),
    )


class DocumentAttachment(PolarionResource):
    """
    High-level Document Attachment operations built on the generated client API.
    Supports both synchronous and asynchronous clients.

    Available operations:
      • list   (GET  /projects/{projectId}/spaces/{spaceId}/documents/{documentName}/attachments)
      • get    (GET  /projects/{projectId}/spaces/{spaceId}/documents/{documentName}/attachments/{attachmentId})
      • create (POST /projects/{projectId}/spaces/{spaceId}/documents/{documentName}/attachments)
      • update (PATCH /projects/{projectId}/spaces/{spaceId}/documents/{documentName}/attachments/{attachmentId})
      • get_content (GET .../attachments/{attachmentId}/content)
    """

    def __init__(self, pc: Union[PolarionClient, PolarionAsyncClient]):
        super().__init__(pc)
        self._c = pc.gen
        # Resolve the exact paging param names from the generated function
        self._page_param, self._size_param = resolve_page_params(_get_document_attachments)

    # ---------------------------- LIST ----------------------------
    def list(
        self,
        project_id: str,
        space_id: str,
        document_name: str,
        *,
        page_number: int = 1,
        page_size: int = 100,
        fields_attachments: Optional[Union[str, List[str]]] = None,
        include: Optional[str] = None,
        revision: Optional[str] = None,
        chunk_size: int = 200,
        max_pages: int = 5000,
    ) -> Union[List[Dict[str, Any]], Awaitable[List[Dict[str, Any]]]]:
        """
        Return document attachments as a list of JSON:API resource objects.

        Notes:
          * If page_size == -1: fetch ALL pages client-side.
          * fields_attachments: if provided, encoded as deep-object fields[document_attachments]=...
        """
        fields = _build_fields_param_for_attachments(fields_attachments)

        return self._list_items(
            _get_document_attachments,
            _get_document_attachments_async,
            page_param=self._page_param,
            size_param=self._size_param,
            page_number=page_number,
            page_size=page_size,
            chunk_size=chunk_size,
            max_pages=max_pages,
            project_id=project_id,
            space_id=space_id,
            document_name=document_name,
            fields=fields,
            include=include if include is not None else UNSET,
            revision=revision if revision is not None else UNSET,
        )

    # ------------------------------ GET ------------------------------
    def get(
        self,
        project_id: str,
        space_id: str,
        document_name: str,
        attachment_id: str,
        *,
        fields_attachments: Optional[Union[str, List[str]]] = None,
        include: Optional[str] = None,
        revision: Optional[str] = None,
    ) -> Union[Dict[str, Any], Awaitable[Dict[str, Any]]]:
        """
        Return a single Document Attachment by ID.
        """
        fields = _build_fields_param_for_attachments(fields_attachments)

        return self._request(
            _get_document_attachment,
            _get_document_attachment_async,
            project_id=project_id,
            space_id=space_id,
            document_name=document_name,
            attachment_id=attachment_id,
            fields=fields,
            include=include if include is not None else UNSET,
            revision=revision if revision is not None else UNSET,
        )

    # ---------------------------- CREATE ----------------------------
    def create(
        self,
        project_id: str,
        space_id: str,
        document_name: str,
        *,
        file_data: bytes,
        file_name: str,
        title: Optional[str] = None,
        mime_type: Optional[str] = None,
    ) -> Union[Dict[str, Any], Awaitable[Dict[str, Any]]]:
        """
        Create a Document Attachment by uploading a file.

        POST /projects/{projectId}/spaces/{spaceId}/documents/{documentName}/attachments

        The multipart body is constructed manually with the file part before
        the resource part, as required by the Polarion REST API.

        Args:
            project_id: The project ID
            space_id: The space ID
            document_name: The document name
            file_data: The binary content of the file to upload
            file_name: The name of the file (used as attachment ID)
            title: Optional title for the attachment
            mime_type: Optional MIME type (e.g., 'image/png', 'application/pdf')

        Returns:
            The created attachment resource object.
        """
        import json
        from io import BytesIO

        attrs: Dict[str, Any] = {"fileName": file_name}
        if title is not None:
            attrs["title"] = title

        resource_json = json.dumps({
            "data": [{"type": "document_attachments", "attributes": attrs}]
        })

        url = (
            f"/projects/{project_id}/spaces/{space_id}"
            f"/documents/{document_name}/attachments"
        )

        # Polarion requires file part BEFORE resource part in multipart body
        multipart = [
            ("files", (file_name, BytesIO(file_data),
                       mime_type or "application/octet-stream")),
            ("resource", (None, resource_json.encode())),
        ]

        def _extract(resp) -> Dict[str, Any]:
            if resp.status_code == 201:
                doc = resp.json()
                data = doc.get("data") or []
                return data[0] if isinstance(data, list) and data else doc
            raise_from_response(resp)

        if self.client.is_async:
            async def _do_async():
                resp = await self._c.get_async_httpx_client().request(
                    "POST", url, files=multipart,
                )
                return _extract(resp)
            return _do_async()
        else:
            resp = self._c.get_httpx_client().request(
                "POST", url, files=multipart,
            )
            return _extract(resp)

    # ----------------------------- UPDATE -----------------------------
    def update(
        self,
        project_id: str,
        space_id: str,
        document_name: str,
        attachment_id: str,
        *,
        title: Optional[str] = None,
        file_data: Optional[bytes] = None,
        mime_type: Optional[str] = None,
    ) -> Union[None, Awaitable[None]]:
        """
        Update a Document Attachment.

        PATCH /projects/{projectId}/spaces/{spaceId}/documents/{documentName}/attachments/{attachmentId}

        The multipart body is constructed manually with the content part
        before the resource part, as required by the Polarion REST API.

        Note: The Polarion API only supports updating the 'title' attribute.
              The fileName cannot be modified after creation. You can update
              the file content using file_data.

        Args:
            project_id: The project ID
            space_id: The space ID
            document_name: The document name
            attachment_id: The attachment ID to update
            title: Optional new title for the attachment
            file_data: Optional new file content (replaces existing content)
            mime_type: Optional MIME type for the new file
        """
        import json
        from io import BytesIO

        attrs: Dict[str, Any] = {}
        if title is not None:
            attrs["title"] = title

        data: Dict[str, Any] = {
            "type": "document_attachments",
            "id": f"{project_id}/{space_id}/{document_name}/{attachment_id}",
        }
        if attrs:
            data["attributes"] = attrs

        resource_json = json.dumps({"data": data})

        url = (
            f"/projects/{project_id}/spaces/{space_id}"
            f"/documents/{document_name}/attachments/{attachment_id}"
        )

        # Polarion requires content (file) part BEFORE resource part
        multipart: list = []
        if file_data is not None:
            multipart.append(
                ("content", (attachment_id, BytesIO(file_data),
                             mime_type or "application/octet-stream"))
            )
        multipart.append(("resource", (None, resource_json.encode())))

        def _check(resp) -> None:
            if 200 <= resp.status_code < 300:
                return None
            raise_from_response(resp)

        if self.client.is_async:
            async def _do_async():
                resp = await self._c.get_async_httpx_client().request(
                    "PATCH", url, files=multipart,
                )
                return _check(resp)
            return _do_async()
        else:
            resp = self._c.get_httpx_client().request(
                "PATCH", url, files=multipart,
            )
            return _check(resp)

    # ------------------------- GET CONTENT -------------------------
    def get_content(
        self,
        project_id: str,
        space_id: str,
        document_name: str,
        attachment_id: str,
        *,
        revision: Optional[str] = None,
    ) -> Union[bytes, Awaitable[bytes]]:
        """
        Download the file content for a specified Document Attachment.

        GET /projects/{projectId}/spaces/{spaceId}/documents/{documentName}/attachments/{attachmentId}/content

        Returns:
            The binary content of the attachment file.
        """
        if self.client.is_async:
            async def _do_async():
                resp = await _get_document_attachment_content_async(
                    client=self._c,
                    project_id=project_id,
                    space_id=space_id,
                    document_name=document_name,
                    attachment_id=attachment_id,
                    revision=revision if revision is not None else UNSET,
                )
                if 200 <= resp.status_code < 300:
                    return resp.content
                raise_from_response(resp)
            return _do_async()
        else:
            resp = _get_document_attachment_content(
                client=self._c,
                project_id=project_id,
                space_id=space_id,
                document_name=document_name,
                attachment_id=attachment_id,
                revision=revision if revision is not None else UNSET,
            )
            if 200 <= resp.status_code < 300:
                return resp.content
            raise_from_response(resp)

    # ------------------------------ ITER ------------------------------
    def iter(
        self,
        project_id: str,
        space_id: str,
        document_name: str,
        *,
        page_size: int = 100,
        start_page: int = 1,
        fields_attachments: Optional[Union[str, List[str]]] = None,
        include: Optional[str] = None,
        revision: Optional[str] = None,
    ) -> Union[Iterator[Dict[str, Any]], AsyncIterator[Dict[str, Any]]]:
        """
        Iterate all attachments for a given document.
        Returns an Iterator (Sync) or AsyncIterator (Async).
        """
        fields = _build_fields_param_for_attachments(fields_attachments)

        return self._iter_items(
            _get_document_attachments,
            _get_document_attachments_async,
            page_param=self._page_param,
            size_param=self._size_param,
            page_size=page_size,
            start_page=start_page,
            project_id=project_id,
            space_id=space_id,
            document_name=document_name,
            fields=fields,
            include=include if include is not None else UNSET,
            revision=revision if revision is not None else UNSET,
        )
