"""Client for Nextcloud Collectives app API (OCS)."""

import logging
from typing import Any

from nextcloud_mcp_server.client.base import BaseNextcloudClient

logger = logging.getLogger(__name__)

API_BASE = "/ocs/v2.php/apps/collectives/api/v1.0"

_UNSET = object()
"""Sentinel to distinguish 'not provided' from an explicit None."""


class OCSError(Exception):
    """Error returned in the OCS response envelope."""

    def __init__(self, status_code: int, message: str):
        self.status_code = status_code
        self.message = message
        super().__init__(f"OCS error {status_code}: {message}")


class CollectivesClient(BaseNextcloudClient):
    """Client for Nextcloud Collectives app operations."""

    app_name = "collectives"

    _OCS_HEADERS: dict[str, str] = {
        "OCS-APIRequest": "true",
        "Accept": "application/json",
    }

    _OCS_HEADERS_JSON: dict[str, str] = {
        **_OCS_HEADERS,
        "Content-Type": "application/json",
    }

    def _unwrap_ocs(self, response_json: dict[str, Any]) -> Any:
        """Unwrap OCS envelope, validating the status before returning data."""
        ocs = response_json.get("ocs")
        if ocs is None:
            raise OCSError(500, "Response is not an OCS envelope")
        meta = ocs.get("meta", {})
        status_code = meta.get("statuscode", 200)
        if status_code >= 400:
            message = meta.get("message", "OCS error")
            raise OCSError(status_code, message)
        if "data" not in ocs:
            raise OCSError(500, "OCS response missing 'data' field")
        return ocs["data"]

    # Collectives

    async def get_collectives(self) -> list[dict[str, Any]]:
        """List all collectives the user has access to."""
        response = await self._make_request(
            "GET", f"{API_BASE}/collectives", headers=self._OCS_HEADERS
        )
        data = self._unwrap_ocs(response.json())
        return data["collectives"]

    async def create_collective(
        self, name: str, emoji: str | None = None
    ) -> dict[str, Any]:
        """Create a new collective."""
        json_data: dict[str, Any] = {"name": name}
        if emoji is not None:
            json_data["emoji"] = emoji
        response = await self._make_request(
            "POST",
            f"{API_BASE}/collectives",
            json=json_data,
            headers=self._OCS_HEADERS_JSON,
        )
        data = self._unwrap_ocs(response.json())
        return data["collective"]

    async def update_collective(
        self, collective_id: int, emoji: str | None | object = _UNSET
    ) -> dict[str, Any]:
        """Update a collective (emoji).

        Pass emoji=None to clear the emoji. Omit emoji entirely to leave
        it unchanged.

        Raises:
            ValueError: If no fields are provided to update.
        """
        json_data: dict[str, Any] = {}
        if emoji is not _UNSET:
            json_data["emoji"] = emoji
        if not json_data:
            raise ValueError("At least one field must be provided to update")
        response = await self._make_request(
            "PUT",
            f"{API_BASE}/collectives/{collective_id}",
            json=json_data,
            headers=self._OCS_HEADERS_JSON,
        )
        data = self._unwrap_ocs(response.json())
        return data["collective"]

    async def trash_collective(self, collective_id: int) -> None:
        """Move a collective to trash (soft delete)."""
        response = await self._make_request(
            "DELETE",
            f"{API_BASE}/collectives/{collective_id}",
            headers=self._OCS_HEADERS,
        )
        self._unwrap_ocs(response.json())

    async def delete_collective(self, collective_id: int) -> None:
        """Permanently delete a collective (must be trashed first).

        This is irreversible. The collective must be in the trash before
        calling this method.
        """
        response = await self._make_request(
            "DELETE",
            f"{API_BASE}/collectives/trash/{collective_id}",
            headers=self._OCS_HEADERS,
        )
        self._unwrap_ocs(response.json())

    # Trash (collectives)

    async def get_trashed_collectives(self) -> list[dict[str, Any]]:
        """List trashed collectives."""
        response = await self._make_request(
            "GET", f"{API_BASE}/collectives/trash", headers=self._OCS_HEADERS
        )
        data = self._unwrap_ocs(response.json())
        return data["collectives"]

    async def restore_collective(self, collective_id: int) -> dict[str, Any]:
        """Restore a collective from trash."""
        response = await self._make_request(
            "PATCH",
            f"{API_BASE}/collectives/trash/{collective_id}",
            headers=self._OCS_HEADERS,
        )
        data = self._unwrap_ocs(response.json())
        return data["collective"]

    # Pages

    async def get_pages(self, collective_id: int) -> list[dict[str, Any]]:
        """List all pages in a collective."""
        response = await self._make_request(
            "GET",
            f"{API_BASE}/collectives/{collective_id}/pages",
            headers=self._OCS_HEADERS,
        )
        data = self._unwrap_ocs(response.json())
        return data["pages"]

    async def get_page(self, collective_id: int, page_id: int) -> dict[str, Any]:
        """Get a single page's metadata."""
        response = await self._make_request(
            "GET",
            f"{API_BASE}/collectives/{collective_id}/pages/{page_id}",
            headers=self._OCS_HEADERS,
        )
        data = self._unwrap_ocs(response.json())
        return data["page"]

    async def create_page(
        self, collective_id: int, parent_id: int, title: str
    ) -> dict[str, Any]:
        """Create a new page under a parent page."""
        json_data = {"title": title}
        response = await self._make_request(
            "POST",
            f"{API_BASE}/collectives/{collective_id}/pages/{parent_id}",
            json=json_data,
            headers=self._OCS_HEADERS_JSON,
        )
        data = self._unwrap_ocs(response.json())
        return data["page"]

    async def move_page(
        self,
        collective_id: int,
        page_id: int,
        parent_id: int | None = None,
        title: str | None = None,
        index: int = 0,
        copy: bool = False,
    ) -> dict[str, Any]:
        """Move or copy a page within a collective."""
        json_data: dict[str, Any] = {"index": index, "copy": copy}
        if parent_id is not None:
            json_data["parentId"] = parent_id
        if title is not None:
            json_data["title"] = title
        response = await self._make_request(
            "PUT",
            f"{API_BASE}/collectives/{collective_id}/pages/{page_id}",
            json=json_data,
            headers=self._OCS_HEADERS_JSON,
        )
        data = self._unwrap_ocs(response.json())
        return data["page"]

    async def trash_page(self, collective_id: int, page_id: int) -> None:
        """Move a page to trash (soft delete)."""
        response = await self._make_request(
            "DELETE",
            f"{API_BASE}/collectives/{collective_id}/pages/{page_id}",
            headers=self._OCS_HEADERS,
        )
        self._unwrap_ocs(response.json())

    async def set_page_emoji(
        self, collective_id: int, page_id: int, emoji: str | None
    ) -> dict[str, Any]:
        """Set or clear the emoji on a page."""
        # Sending {"emoji": null} intentionally clears the emoji on the server
        json_data = {"emoji": emoji}
        response = await self._make_request(
            "PUT",
            f"{API_BASE}/collectives/{collective_id}/pages/{page_id}/emoji",
            json=json_data,
            headers=self._OCS_HEADERS_JSON,
        )
        data = self._unwrap_ocs(response.json())
        return data["page"]

    # Search

    async def search_pages(
        self, collective_id: int, query: str
    ) -> list[dict[str, Any]]:
        """Full-text search within a collective."""
        response = await self._make_request(
            "GET",
            f"{API_BASE}/collectives/{collective_id}/search",
            params={"searchString": query},
            headers=self._OCS_HEADERS,
        )
        data = self._unwrap_ocs(response.json())
        return data["pages"]

    # Tags

    async def get_tags(self, collective_id: int) -> list[dict[str, Any]]:
        """List all tags in a collective."""
        response = await self._make_request(
            "GET",
            f"{API_BASE}/collectives/{collective_id}/tags",
            headers=self._OCS_HEADERS,
        )
        data = self._unwrap_ocs(response.json())
        return data["tags"]

    async def create_tag(
        self, collective_id: int, name: str, color: str
    ) -> dict[str, Any]:
        """Create a new tag in a collective."""
        json_data = {"name": name, "color": color}
        response = await self._make_request(
            "POST",
            f"{API_BASE}/collectives/{collective_id}/tags",
            json=json_data,
            headers=self._OCS_HEADERS_JSON,
        )
        data = self._unwrap_ocs(response.json())
        return data["tag"]

    async def assign_tag(self, collective_id: int, page_id: int, tag_id: int) -> None:
        """Assign a tag to a page."""
        response = await self._make_request(
            "PUT",
            f"{API_BASE}/collectives/{collective_id}/pages/{page_id}/tags/{tag_id}",
            headers=self._OCS_HEADERS,
        )
        self._unwrap_ocs(response.json())

    async def remove_tag(self, collective_id: int, page_id: int, tag_id: int) -> None:
        """Remove a tag from a page."""
        response = await self._make_request(
            "DELETE",
            f"{API_BASE}/collectives/{collective_id}/pages/{page_id}/tags/{tag_id}",
            headers=self._OCS_HEADERS,
        )
        self._unwrap_ocs(response.json())

    # Trash

    async def get_trashed_pages(self, collective_id: int) -> list[dict[str, Any]]:
        """List trashed pages in a collective."""
        response = await self._make_request(
            "GET",
            f"{API_BASE}/collectives/{collective_id}/pages/trash",
            headers=self._OCS_HEADERS,
        )
        data = self._unwrap_ocs(response.json())
        return data["pages"]

    async def restore_page(self, collective_id: int, page_id: int) -> dict[str, Any]:
        """Restore a page from trash."""
        response = await self._make_request(
            "PATCH",
            f"{API_BASE}/collectives/{collective_id}/pages/trash/{page_id}",
            headers=self._OCS_HEADERS,
        )
        data = self._unwrap_ocs(response.json())
        return data["page"]
