"""
Assessment Items Resource

CRUD operations for QTI assessment items.
"""

from __future__ import annotations

from typing import TYPE_CHECKING, Any
from urllib.parse import quote

from timeback_common import (
    APIError,
    Paginator,
    serialize_input,
    validate_non_empty_string,
    validate_page_list_params,
    validate_with_schema,
)

from ..types.inputs import (
    CreateAssessmentItemInput,
    CreateAssessmentItemMetadataInput,
    CreateAssessmentItemXmlInput,
    ListParams,
    ProcessResponseInput,
    UpdateAssessmentItemInput,
)
from ..types.responses import (
    AssessmentItem,
    DeleteResponse,
    ListResponse,
    ProcessResponseResult,
)

if TYPE_CHECKING:
    from ..lib.transport import Transport


class AssessmentItemsResource:
    """Assessment items resource for managing QTI assessment items."""

    def __init__(self, transport: Transport) -> None:
        self._transport = transport

    @property
    def _base_path(self) -> str:
        return f"{self._transport.paths.base}/assessment-items"

    async def list(
        self, params: ListParams | dict[str, Any] | None = None
    ) -> ListResponse[AssessmentItem]:
        """List assessment items with pagination."""
        query_params: dict[str, Any] = {}
        if params is not None:
            p = serialize_input(params, ListParams) if not isinstance(params, dict) else params
            validate_page_list_params(page=p.get("page"), limit=p.get("limit"))
            for key in ("page", "limit", "sort", "order"):
                if p.get(key) is not None:
                    query_params[key] = p[key]

        body = await self._transport.get(self._base_path, params=query_params or None)
        return ListResponse[AssessmentItem].model_validate(body)

    def stream(
        self,
        *,
        limit: int = 100,
        sort: str | None = None,
        order: str | None = None,
        max_items: int | None = None,
    ) -> Paginator[dict[str, Any]]:
        """
        Stream assessment items with automatic pagination.

        Lazily fetches pages as you iterate, making it easy to process
        large datasets without manual pagination.

        Args:
            limit: Items per page (default: 100)
            sort: Sort field
            order: Sort order ("asc" or "desc")
            max_items: Maximum total items to fetch

        Returns:
            Async iterator over raw item dicts

        Example:
            ```python
            async for item in client.assessment_items.stream():
                print(item["identifier"])

            all_items = await client.assessment_items.stream(max_items=500).to_list()
            ```
        """
        validate_page_list_params(limit=limit, max_items=max_items)
        params: dict[str, Any] = {}
        if sort:
            params["sort"] = sort
        if order:
            params["order"] = order

        return Paginator(
            self._transport,
            self._base_path,
            unwrap_key="items",
            params=params,
            limit=limit,
            max_items=max_items,
            pagination_style="page",
        )

    async def get(self, identifier: str) -> AssessmentItem:
        """Get a single assessment item by identifier."""
        validate_non_empty_string(identifier, "identifier")
        body = await self._transport.get(f"{self._base_path}/{quote(identifier, safe='')}")
        return AssessmentItem.model_validate(body)

    async def create(
        self,
        body: CreateAssessmentItemInput | CreateAssessmentItemXmlInput | dict[str, Any],
    ) -> AssessmentItem:
        """Create a new assessment item.

        Accepts either the structured JSON input or the XML input
        (with ``format: "xml"``).
        """
        if isinstance(body, CreateAssessmentItemXmlInput) or (
            isinstance(body, dict) and body.get("format") == "xml"
        ):
            validate_with_schema(CreateAssessmentItemXmlInput, body, "assessment item XML")
            result = await self._transport.post(
                self._base_path, serialize_input(body, CreateAssessmentItemXmlInput)
            )
        else:
            validate_with_schema(CreateAssessmentItemInput, body, "assessment item")
            result = await self._transport.post(
                self._base_path, serialize_input(body, CreateAssessmentItemInput)
            )
        return AssessmentItem.model_validate(result)

    async def create_from_xml(
        self, body: CreateAssessmentItemXmlInput | dict[str, Any]
    ) -> AssessmentItem:
        """
        Create an assessment item from raw QTI 3.0 XML.

        This is the preferred creation method - the server validates the XML
        against IMS QTI XSDs. Optionally include metadata (subject, grade, etc.).
        """
        validate_with_schema(CreateAssessmentItemXmlInput, body, "assessment item XML")
        result = await self._transport.post(
            self._base_path, serialize_input(body, CreateAssessmentItemXmlInput)
        )
        return AssessmentItem.model_validate(result)

    async def create_from_metadata(
        self, body: CreateAssessmentItemMetadataInput | dict[str, Any]
    ) -> AssessmentItem:
        """Create an assessment item from metadata."""
        validate_with_schema(CreateAssessmentItemMetadataInput, body, "assessment item metadata")
        result = await self._transport.post(
            f"{self._base_path}/metadata",
            serialize_input(body, CreateAssessmentItemMetadataInput),
        )
        return AssessmentItem.model_validate(result)

    async def update(
        self, identifier: str, body: UpdateAssessmentItemInput | dict[str, Any]
    ) -> AssessmentItem:
        """Update an assessment item."""
        validate_non_empty_string(identifier, "identifier")
        validate_with_schema(UpdateAssessmentItemInput, body, "assessment item update")
        payload = serialize_input(body, UpdateAssessmentItemInput)
        payload["identifier"] = identifier
        result = await self._transport.put(
            f"{self._base_path}/{quote(identifier, safe='')}",
            payload,
        )
        return AssessmentItem.model_validate(result)

    async def upsert(
        self, identifier: str, body: UpdateAssessmentItemInput | dict[str, Any]
    ) -> AssessmentItem:
        """
        Create or update an assessment item.

        Attempts an update; if the item doesn't exist (404), creates it.
        """
        validate_non_empty_string(identifier, "identifier")
        try:
            return await self.update(identifier, body)
        except APIError as error:
            if error.status_code == 404:
                data = serialize_input(body, UpdateAssessmentItemInput)
                data.pop("rawXml", None)
                data.pop("content", None)
                data["identifier"] = identifier
                validate_with_schema(CreateAssessmentItemInput, data, "assessment item")
                result = await self._transport.post(self._base_path, data)
                return AssessmentItem.model_validate(result)
            raise

    async def delete(self, identifier: str) -> DeleteResponse:
        """Delete an assessment item."""
        validate_non_empty_string(identifier, "identifier")
        body = await self._transport.delete(f"{self._base_path}/{quote(identifier, safe='')}")
        if body is None:
            return DeleteResponse()
        return DeleteResponse.model_validate(body)

    async def process_response(
        self, identifier: str, body: ProcessResponseInput | dict[str, Any]
    ) -> ProcessResponseResult:
        """
        Process a response for an assessment item.

        Validates and scores the response against the item's correct answer.
        """
        validate_non_empty_string(identifier, "identifier")
        validate_with_schema(ProcessResponseInput, body, "item response")
        payload = {"identifier": identifier, **serialize_input(body, ProcessResponseInput)}
        result = await self._transport.post(
            f"{self._base_path}/{quote(identifier, safe='')}/process-response",
            payload,
        )
        return ProcessResponseResult.model_validate(result)
