"""Asynchronous typed model query builder using LOL (Linear Object List) queries."""

from __future__ import annotations

from typing import TYPE_CHECKING, Any, AsyncGenerator, Generic, Optional, TypeVar

from ecmind_blue_client.ecm.builder import DmsQueryBuilderLol
from ecmind_blue_client.ecm.builder._common import DmsQueryFieldSchemaT
from ecmind_blue_client.ecm.model._ecm_model_field import (
    ECMCondition,
    ECMConditionGroup,
    ECMField,
    ECMSortOrder,
)
from ecmind_blue_client.ecm.parser import DmsContentParserLol

if TYPE_CHECKING:
    from ecmind_blue_client.ecm.model._ecm_model import _ECMModelBase

_AnyModelT = TypeVar("_AnyModelT", bound="_ECMModelBase")


class ECMModelQueryLolAsync(Generic[_AnyModelT]):
    """Asynchronous query builder for declarative ECM model classes using LOL format.

    Do not instantiate directly — use ``ECMAsync.dms.select_lol()``.

    LOL (Linear Object List) queries are typically **faster** than HOL for large, flat
    result sets because the server returns a compact column-oriented rowset rather than
    a per-object element tree.  Paging via ``offset()`` and ``pagesize()`` is
    fully supported.

    **Limitations compared to** ``select()`` **(HOL):**

    - ``file_properties()`` / ``obj.system.file_properties`` is **not available**.
      Only partial file metadata is accessible via system field columns when declared
      on the model: ``OBJECT_FILESIZE`` (size), ``OBJECT_COUNT`` (count),
      ``OBJECT_DOCPAGECOUNT`` (document page count).  Extension, MIME type string,
      MIME type group, and icon ID are **never** returned by the server in LOL mode.
    - ``base_params()`` is **not available** as a structured ``ECMModelBaseParams``
      object (``obj.system.base_params`` is always ``None``).  Audit metadata is only
      present when the corresponding system fields are declared via
      ``_ecm_system_fields_`` on the model and will then appear in ``_raw_fields_``:
      ``OBJECT_CRID`` (creator ID), ``OBJECT_CRDATE`` (creation date),
      ``OBJECT_USERGUID`` (owner GUID), ``OBJECT_MODIFYUSER`` (modifier),
      ``OBJECT_MODIFYTIME`` (modification date), ``OBJECT_LINKS`` (link count),
      ``OBJECT_TXTNOTICECOUNT`` (text notice count).
    - ``variants()`` is **not supported** — document variant data is never returned in
      LOL mode (``obj.system.variants`` is always ``None``).
    - ``icons()`` has **no effect** — the server ignores the ``icons`` flag in LOL mode.
    - ``with_children()`` / ``with_parents()`` (HOL hierarchy traversal) are **not
      available** in LOL mode.  Use ``select()`` for hierarchical queries.
    - **Table field values are untyped raw strings.**  Values are not parsed to their
      declared Python types and ``row_id`` is always ``None`` for table rows returned
      via LOL.

    Args:
        ecm: ``ECMAsync`` instance used to execute the query.
        model_class: Model class to query and instantiate results for.
    """

    def __init__(self, ecm: Any, model_class: type[_AnyModelT]) -> None:
        self._ecm = ecm
        self._model_class = model_class
        self._conditions: list[ECMCondition | ECMConditionGroup] = []
        self._sort_orders: list[ECMSortOrder] = []
        self._pagesize: int = 1000
        self._offset: int = 0
        self._maxhits: Optional[int] = None
        self._field_schema: DmsQueryFieldSchemaT = "ALL"
        self._selected_fields: list[str] | None = None
        self._request_rights: bool = False
        self._garbage_mode: bool = False

    def where(self, *conditions: ECMCondition | ECMConditionGroup) -> ECMModelQueryLolAsync[_AnyModelT]:
        """Add filter conditions. Multiple arguments are combined with AND.

        Args:
            *conditions: ``ECMCondition`` or ``ECMConditionGroup`` instances.
        """
        self._conditions.extend(conditions)
        return self

    def pagesize(self, value: int) -> ECMModelQueryLolAsync[_AnyModelT]:
        """Set results per server request (default: 1000).

        Args:
            value: Page size.
        """
        self._pagesize = value
        return self

    def offset(self, value: int) -> ECMModelQueryLolAsync[_AnyModelT]:
        """Set the zero-based start offset.

        Args:
            value: Number of results to skip.
        """
        self._offset = value
        return self

    def limit(self, value: int) -> ECMModelQueryLolAsync[_AnyModelT]:
        """Limit the total number of results across all pages.

        Args:
            value: Maximum result count.
        """
        self._maxhits = value
        return self

    def rights(self, value: bool = True) -> ECMModelQueryLolAsync[_AnyModelT]:
        """Request access rights data from the server (populates ``obj.system.rights``).

        Args:
            value: ``True`` to enable (default), ``False`` to disable.
        """
        self._request_rights = value
        return self

    def order_by(self, *sort_orders: ECMSortOrder) -> ECMModelQueryLolAsync[_AnyModelT]:
        """Set the sort order for query results.

        Each argument is an ``ECMSortOrder`` created via ``ECMField.ASC`` or ``ECMField.DESC``
        at class level.

        Args:
            *sort_orders: Sort directives in priority order.
        """
        self._sort_orders = list(sort_orders)
        return self

    def garbage_mode(self, value: bool = True) -> ECMModelQueryLolAsync[_AnyModelT]:
        """Restrict the query to objects in the recycle bin (``GarbageMode=1``).

        Args:
            value: ``True`` to search the recycle bin (default), ``False`` to search normally.
        """
        self._garbage_mode = value
        return self

    def fields(self, *fields: "str | ECMField[Any]") -> ECMModelQueryLolAsync[_AnyModelT]:
        """Restrict the returned fields to an explicit list (sets ``field_schema="MIN"``).

        Calling this method without arguments resets the field selection (all fields returned).

        Args:
            *fields: Field names as strings or ``ECMField`` descriptors at class level.
                Passing no arguments clears any previous selection.
        """
        if not fields:
            self._selected_fields = None
            return self
        self._selected_fields = [
            name
            for f in fields
            if (name := f._internal_name if isinstance(f, ECMField) else f) is not None  # pylint: disable=protected-access
        ]
        return self

    def _build_query(self) -> DmsQueryBuilderLol:
        """Build and return the configured ``DmsQueryBuilderLol``."""
        from ecmind_blue_client.ecm.model.synchron._ecm_model_query_sync import (  # pylint: disable=import-outside-toplevel
            _apply_condition,
            _condition_object_type,
        )

        identifier = self._model_class._ecm_identifier()  # pylint: disable=protected-access
        sys_fields = self._model_class._ecm_system_fields_  # pylint: disable=protected-access
        builder = DmsQueryBuilderLol()
        if self._request_rights:
            builder.rights = True
        schema: DmsQueryFieldSchemaT = "MIN" if self._selected_fields is not None else self._field_schema
        ot = builder.object_type(identifier)
        with ot.fields(schema) as f:
            for sys_field in sys_fields:
                f.field(sys_field)
            if self._selected_fields is not None:
                for field_name in self._selected_fields:
                    f.field(field_name)
            for pos, sort_order in enumerate(self._sort_orders, start=1):
                f.field(sort_order.field_name, sortpos=pos, sortorder=sort_order.direction)
        if self._conditions:
            buckets: dict[str | int, list[ECMCondition | ECMConditionGroup]] = {}
            for condition in self._conditions:
                key = _condition_object_type(condition, identifier)
                buckets.setdefault(key, []).append(condition)
            for ot_id, grouped in buckets.items():
                co = ot.condition_object(ot_id)
                for condition in grouped:
                    _apply_condition(co, condition)
        return builder

    async def stream(self) -> AsyncGenerator[_AnyModelT, None]:
        """Execute the query and yield typed model instances asynchronously. Handles pagination.

        Yields:
            Model instances populated with field values from the server response.
        """
        builder = self._build_query()
        builder.pagesize = self._pagesize
        builder.offset = self._offset
        if self._maxhits is not None:
            builder.maxhits = self._maxhits

        total_hits = -1

        while total_hits == -1 or builder.offset < total_hits:
            result = await self._ecm.execute(
                "dms.GetResultList",
                Flags=16,
                XML=builder.to_xml(),
                **{"GarbageMode": 1} if self._garbage_mode else {},
            )

            total_hits = result.get("TotalHits", int, 0)
            builder.offset += result.get("Count", int)
            dms_content = DmsContentParserLol(result.files[0])

            for ot in dms_content.object_types():
                for row in ot.rows():
                    yield self._model_class._from_lol_row(ot, row, request_rights=self._request_rights)  # type: ignore[return-value]  # pylint: disable=protected-access

    async def execute(self) -> list[_AnyModelT]:
        """Execute the query and return all results as a list (all pages in memory).

        Returns:
            List of model instances.
        """
        return [item async for item in self.stream()]
