"""
Assessment Tests Resource

CRUD operations for QTI assessment tests including nested test parts,
sections, and 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 (
    AssessmentItemRefInput,
    AssessmentSectionInput,
    CreateAssessmentTestInput,
    ListParams,
    ReorderItemsInput,
    TestPartInput,
    UpdateAssessmentTestInput,
    UpdateTestMetadataInput,
)
from ..types.responses import (
    AssessmentSection,
    AssessmentTest,
    DeleteResponse,
    ListResponse,
    QuestionsResponse,
    TestPart,
)

if TYPE_CHECKING:
    from ..lib.transport import Transport


def _enc(value: str) -> str:
    return quote(value, safe="")


# ═══════════════════════════════════════════════════════════════════════════════
# ITEMS HELPER
# ═══════════════════════════════════════════════════════════════════════════════


class SectionItemsHelper:
    """Helper for managing items within a section."""

    def __init__(
        self,
        transport: Transport,
        test_id: str,
        test_part_id: str,
        section_id: str,
    ) -> None:
        validate_non_empty_string(test_id, "testId")
        validate_non_empty_string(test_part_id, "testPartId")
        validate_non_empty_string(section_id, "sectionId")
        self._transport = transport
        self._test_id = test_id
        self._test_part_id = test_part_id
        self._section_id = section_id

    @property
    def _base_path(self) -> str:
        base = self._transport.paths.base
        return (
            f"{base}/assessment-tests/{_enc(self._test_id)}"
            f"/test-parts/{_enc(self._test_part_id)}"
            f"/sections/{_enc(self._section_id)}/items"
        )

    async def add(self, body: AssessmentItemRefInput | dict[str, Any]) -> AssessmentSection:
        """Add an item to the section."""
        validate_with_schema(AssessmentItemRefInput, body, "assessment item ref")
        result = await self._transport.post(
            self._base_path, serialize_input(body, AssessmentItemRefInput)
        )
        return AssessmentSection.model_validate(result)

    async def remove(self, item_identifier: str) -> DeleteResponse:
        """Remove an item from the section."""
        validate_non_empty_string(item_identifier, "itemIdentifier")
        body = await self._transport.delete(f"{self._base_path}/{_enc(item_identifier)}")
        if body is None:
            return DeleteResponse()
        return DeleteResponse.model_validate(body)

    async def reorder(self, body: ReorderItemsInput | dict[str, Any]) -> AssessmentSection:
        """Reorder items in the section."""
        validate_with_schema(ReorderItemsInput, body, "reorder items")
        result = await self._transport.put(
            f"{self._base_path}/order", serialize_input(body, ReorderItemsInput)
        )
        return AssessmentSection.model_validate(result)


# ═══════════════════════════════════════════════════════════════════════════════
# SECTIONS HELPER
# ═══════════════════════════════════════════════════════════════════════════════


class TestPartSectionsHelper:
    """Helper for managing sections within a test part."""

    def __init__(self, transport: Transport, test_id: str, test_part_id: str) -> None:
        validate_non_empty_string(test_id, "testId")
        validate_non_empty_string(test_part_id, "testPartId")
        self._transport = transport
        self._test_id = test_id
        self._test_part_id = test_part_id

    @property
    def _base_path(self) -> str:
        base = self._transport.paths.base
        return (
            f"{base}/assessment-tests/{_enc(self._test_id)}"
            f"/test-parts/{_enc(self._test_part_id)}/sections"
        )

    async def list(
        self, params: ListParams | dict[str, Any] | None = None
    ) -> ListResponse[AssessmentSection]:
        """List sections in a test part."""
        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[AssessmentSection].model_validate(body)

    async def get(self, identifier: str) -> AssessmentSection:
        """Get a section by identifier."""
        validate_non_empty_string(identifier, "sectionId")
        body = await self._transport.get(f"{self._base_path}/{_enc(identifier)}")
        return AssessmentSection.model_validate(body)

    async def create(self, body: AssessmentSectionInput | dict[str, Any]) -> AssessmentSection:
        """Create a new section."""
        validate_with_schema(AssessmentSectionInput, body, "assessment section")
        result = await self._transport.post(
            self._base_path, serialize_input(body, AssessmentSectionInput)
        )
        return AssessmentSection.model_validate(result)

    async def update(
        self, identifier: str, body: AssessmentSectionInput | dict[str, Any]
    ) -> AssessmentSection:
        """Update a section."""
        validate_non_empty_string(identifier, "sectionId")
        validate_with_schema(AssessmentSectionInput, body, "assessment section update")
        result = await self._transport.put(
            f"{self._base_path}/{_enc(identifier)}",
            serialize_input(body, AssessmentSectionInput),
        )
        return AssessmentSection.model_validate(result)

    async def delete(self, identifier: str) -> DeleteResponse:
        """Delete a section."""
        validate_non_empty_string(identifier, "sectionId")
        body = await self._transport.delete(f"{self._base_path}/{_enc(identifier)}")
        if body is None:
            return DeleteResponse()
        return DeleteResponse.model_validate(body)

    def items(self, section_id: str) -> SectionItemsHelper:
        """Get items helper for a specific section."""
        return SectionItemsHelper(self._transport, self._test_id, self._test_part_id, section_id)


# ═══════════════════════════════════════════════════════════════════════════════
# TEST PARTS HELPER
# ═══════════════════════════════════════════════════════════════════════════════


class AssessmentTestPartsHelper:
    """Helper for managing test parts within an assessment test."""

    def __init__(self, transport: Transport, test_id: str) -> None:
        validate_non_empty_string(test_id, "testId")
        self._transport = transport
        self._test_id = test_id

    @property
    def _base_path(self) -> str:
        base = self._transport.paths.base
        return f"{base}/assessment-tests/{_enc(self._test_id)}/test-parts"

    async def list(
        self, params: ListParams | dict[str, Any] | None = None
    ) -> ListResponse[TestPart]:
        """List test parts."""
        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[TestPart].model_validate(body)

    async def get(self, identifier: str) -> TestPart:
        """Get a test part by identifier."""
        validate_non_empty_string(identifier, "testPartId")
        body = await self._transport.get(f"{self._base_path}/{_enc(identifier)}")
        return TestPart.model_validate(body)

    async def create(self, body: TestPartInput | dict[str, Any]) -> TestPart:
        """Create a new test part."""
        validate_with_schema(TestPartInput, body, "test part")
        result = await self._transport.post(self._base_path, serialize_input(body, TestPartInput))
        return TestPart.model_validate(result)

    async def update(self, identifier: str, body: TestPartInput | dict[str, Any]) -> TestPart:
        """Update a test part."""
        validate_non_empty_string(identifier, "testPartId")
        validate_with_schema(TestPartInput, body, "test part update")
        result = await self._transport.put(
            f"{self._base_path}/{_enc(identifier)}",
            serialize_input(body, TestPartInput),
        )
        return TestPart.model_validate(result)

    async def delete(self, identifier: str) -> DeleteResponse:
        """Delete a test part."""
        validate_non_empty_string(identifier, "testPartId")
        body = await self._transport.delete(f"{self._base_path}/{_enc(identifier)}")
        if body is None:
            return DeleteResponse()
        return DeleteResponse.model_validate(body)

    def sections(self, test_part_id: str) -> TestPartSectionsHelper:
        """Get sections helper for a specific test part."""
        return TestPartSectionsHelper(self._transport, self._test_id, test_part_id)


# ═══════════════════════════════════════════════════════════════════════════════
# MAIN RESOURCE
# ═══════════════════════════════════════════════════════════════════════════════


class AssessmentTestsResource:
    """Assessment tests resource for managing QTI assessment tests."""

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

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

    async def list(
        self, params: ListParams | dict[str, Any] | None = None
    ) -> ListResponse[AssessmentTest]:
        """List assessment tests 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[AssessmentTest].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 tests with automatic 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 test dicts

        Example:
            ```python
            async for test in client.assessment_tests.stream():
                print(test["identifier"])
            ```
        """
        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) -> AssessmentTest:
        """Get a single assessment test by identifier."""
        validate_non_empty_string(identifier, "identifier")
        body = await self._transport.get(f"{self._base_path}/{_enc(identifier)}")
        return AssessmentTest.model_validate(body)

    async def create(self, body: CreateAssessmentTestInput | dict[str, Any]) -> AssessmentTest:
        """Create a new assessment test."""
        validate_with_schema(CreateAssessmentTestInput, body, "assessment test")
        result = await self._transport.post(
            self._base_path, serialize_input(body, CreateAssessmentTestInput)
        )
        return AssessmentTest.model_validate(result)

    async def update(
        self, identifier: str, body: UpdateAssessmentTestInput | dict[str, Any]
    ) -> AssessmentTest:
        """Update an assessment test."""
        validate_non_empty_string(identifier, "identifier")
        validate_with_schema(UpdateAssessmentTestInput, body, "assessment test update")
        payload = serialize_input(body, UpdateAssessmentTestInput)
        payload["identifier"] = identifier
        result = await self._transport.put(
            f"{self._base_path}/{_enc(identifier)}",
            payload,
        )
        return AssessmentTest.model_validate(result)

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

        Attempts an update; if the test 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, UpdateAssessmentTestInput)
                data["identifier"] = identifier
                validate_with_schema(CreateAssessmentTestInput, data, "assessment test")
                result = await self._transport.post(self._base_path, data)
                return AssessmentTest.model_validate(result)
            raise

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

    async def update_metadata(
        self, identifier: str, body: UpdateTestMetadataInput | dict[str, Any]
    ) -> AssessmentTest:
        """Update test metadata."""
        validate_non_empty_string(identifier, "identifier")
        validate_with_schema(UpdateTestMetadataInput, body, "test metadata")
        result = await self._transport.put(
            f"{self._base_path}/{_enc(identifier)}/metadata",
            serialize_input(body, UpdateTestMetadataInput),
        )
        return AssessmentTest.model_validate(result)

    async def get_questions(self, identifier: str) -> QuestionsResponse:
        """
        Get all questions from an assessment test.

        Returns the full item details for each question referenced in the test.
        """
        validate_non_empty_string(identifier, "identifier")
        body = await self._transport.get(f"{self._base_path}/{_enc(identifier)}/questions")
        return QuestionsResponse.model_validate(body)

    def test_parts(self, test_id: str) -> AssessmentTestPartsHelper:
        """Get test parts helper for a specific test."""
        return AssessmentTestPartsHelper(self._transport, test_id)
