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
from polarion_rest_client.openapi.api.document_comments.get_document_comments import (
    sync_detailed as _get_document_comments,
    asyncio_detailed as _get_document_comments_async,
)
from polarion_rest_client.openapi.api.document_comments.get_document_comment import (
    sync_detailed as _get_document_comment,
    asyncio_detailed as _get_document_comment_async,
)
from polarion_rest_client.openapi.api.document_comments.post_document_comments import (
    sync_detailed as _post_document_comments,
    asyncio_detailed as _post_document_comments_async,
)
from polarion_rest_client.openapi.api.document_comments.patch_document_comment import (
    sync_detailed as _patch_document_comment,
    asyncio_detailed as _patch_document_comment_async,
)

# Request models
from polarion_rest_client.openapi.models.document_comments_list_post_request import (
    DocumentCommentsListPostRequest,
)
from polarion_rest_client.openapi.models.document_comments_single_patch_request import (
    DocumentCommentsSinglePatchRequest,
)


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


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

    Available operations:
      • list   (GET  /projects/{projectId}/spaces/{spaceId}/documents/{documentName}/comments)
      • get    (GET  /projects/{projectId}/spaces/{spaceId}/documents/{documentName}/comments/{commentId})
      • create (POST /projects/{projectId}/spaces/{spaceId}/documents/{documentName}/comments)
      • update (PATCH /projects/{projectId}/spaces/{spaceId}/documents/{documentName}/comments/{commentId})
    """

    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_comments)

    # ---------------------------- LIST ----------------------------
    def list(
        self,
        project_id: str,
        space_id: str,
        document_name: str,
        *,
        page_number: int = 1,
        page_size: int = 100,
        fields_comments: 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 comments as a list of JSON:API resource objects.

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

        return self._list_items(
            _get_document_comments,
            _get_document_comments_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,
        comment_id: str,
        *,
        fields_comments: 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 Comment by ID.
        """
        fields = _build_fields_param_for_comments(fields_comments)

        return self._request(
            _get_document_comment,
            _get_document_comment_async,
            project_id=project_id,
            space_id=space_id,
            document_name=document_name,
            comment_id=comment_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,
        *,
        text: str,
        text_type: str = "text/plain",
        resolved: Optional[bool] = None,
        parent_comment_id: Optional[str] = None,
    ) -> Union[Dict[str, Any], Awaitable[Dict[str, Any]]]:
        """
        Create a Document Comment.

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

        Args:
            project_id: The project ID
            space_id: The space ID
            document_name: The document name
            text: The comment text content
            text_type: The text content type (default: 'text/plain', can be 'text/html')
            resolved: Optional flag indicating if the comment is resolved
            parent_comment_id: Optional parent comment ID for threaded comments

        Returns:
            The created comment resource object.
        """
        attrs: Dict[str, Any] = {
            "text": {"type": text_type, "value": text}
        }
        if resolved is not None:
            attrs["resolved"] = resolved

        item: Dict[str, Any] = {
            "type": "document_comments",
            "attributes": attrs,
        }

        # Add parent comment relationship if specified
        if parent_comment_id is not None:
            if "/" not in parent_comment_id:
                parent_comment_id = f"{project_id}/{space_id}/{document_name}/{parent_comment_id}"
            item["relationships"] = {
                "parentComment": {
                    "data": {
                        "type": "document_comments",
                        "id": parent_comment_id
                    }
                }
            }

        body = DocumentCommentsListPostRequest.from_dict({"data": [item]})

        def _extract_created_comment(resp) -> Dict[str, Any]:
            if resp.status_code == 201 and resp.parsed:
                doc = self._to_dict(resp)
                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 _post_document_comments_async(
                    client=self._c,
                    project_id=project_id,
                    space_id=space_id,
                    document_name=document_name,
                    body=body,
                )
                return _extract_created_comment(resp)
            return _do_async()
        else:
            resp = _post_document_comments(
                client=self._c,
                project_id=project_id,
                space_id=space_id,
                document_name=document_name,
                body=body,
            )
            return _extract_created_comment(resp)

    # ----------------------------- UPDATE -----------------------------
    def update(
        self,
        project_id: str,
        space_id: str,
        document_name: str,
        comment_id: str,
        *,
        resolved: Optional[bool] = None,
    ) -> Union[None, Awaitable[None]]:
        """
        Update a Document Comment.

        PATCH /projects/{projectId}/spaces/{spaceId}/documents/{documentName}/comments/{commentId}

        Note: The Polarion API only supports updating the 'resolved' attribute for comments.
              Text and title cannot be modified after creation.

        Args:
            project_id: The project ID
            space_id: The space ID
            document_name: The document name
            comment_id: The comment ID to update
            resolved: Optional resolved flag (True to mark as resolved, False for unresolved)
        """
        attrs: Dict[str, Any] = {}
        if resolved is not None:
            attrs["resolved"] = resolved

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

        body = DocumentCommentsSinglePatchRequest.from_dict({"data": data})

        return self._request_no_content(
            _patch_document_comment,
            _patch_document_comment_async,
            project_id=project_id,
            space_id=space_id,
            document_name=document_name,
            comment_id=comment_id,
            body=body,
        )

    # ------------------------------ ITER ------------------------------
    def iter(
        self,
        project_id: str,
        space_id: str,
        document_name: str,
        *,
        page_size: int = 100,
        start_page: int = 1,
        fields_comments: 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 comments for a given document.
        Returns an Iterator (Sync) or AsyncIterator (Async).
        """
        fields = _build_fields_param_for_comments(fields_comments)

        return self._iter_items(
            _get_document_comments,
            _get_document_comments_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,
        )

    # ------------------------ REPLY TO COMMENT ------------------------
    def reply(
        self,
        project_id: str,
        space_id: str,
        document_name: str,
        parent_comment_id: str,
        *,
        text: str,
        text_type: str = "text/plain",
    ) -> Union[Dict[str, Any], Awaitable[Dict[str, Any]]]:
        """
        Create a reply to an existing comment.

        This is a convenience method that wraps create() with the parent_comment_id set.

        Args:
            project_id: The project ID
            space_id: The space ID
            document_name: The document name
            parent_comment_id: The ID of the comment to reply to
            text: The reply text content
            text_type: The text content type (default: 'text/plain')

        Returns:
            The created reply comment resource object.
        """
        return self.create(
            project_id,
            space_id,
            document_name,
            text=text,
            text_type=text_type,
            parent_comment_id=parent_comment_id,
        )

    # ----------------------- RESOLVE/UNRESOLVE -----------------------
    def resolve(
        self,
        project_id: str,
        space_id: str,
        document_name: str,
        comment_id: str,
    ) -> Union[None, Awaitable[None]]:
        """
        Mark a comment as resolved.
        """
        return self.update(
            project_id,
            space_id,
            document_name,
            comment_id,
            resolved=True,
        )

    def unresolve(
        self,
        project_id: str,
        space_id: str,
        document_name: str,
        comment_id: str,
    ) -> Union[None, Awaitable[None]]:
        """
        Mark a comment as unresolved.
        """
        return self.update(
            project_id,
            space_id,
            document_name,
            comment_id,
            resolved=False,
        )
