from collections.abc import Iterator
from dataclasses import dataclass
from dataclasses import field
from functools import cached_property
from plone import api
from plone.memoize.view import memoize_contextless
from ploneintranet.search.base import SearchResponse
from ploneintranet.search.base import SiteSearch
from ploneintranet.search.interfaces import ISiteSearch
from ploneintranet.userprofile.interfaces import IFunctionalPrincipal
from zope.component import getUtility
from zope.globalrequest import getRequest
from zope.interface import implementer
from zope.schema.interfaces import ISource
from zope.schema.vocabulary import SimpleTerm
from zope.schema.vocabulary import SimpleVocabulary


def primaryLocationVocabulary(context):
    locations = api.portal.get_registry_record("ploneintranet.userprofile.locations")
    return SimpleVocabulary.fromValues(locations)


@implementer(ISource)
@dataclass
class PrincipalSource:
    """Source for the users and groups in the collection edit form

    Use this source to query the users and the groups in the system.

    Use it like:

    directives.widget("user_or_group", AjaxSelectFieldWidget)
    user_or_group = schema.Choice(
        title=_("label_user_or_group", default="User or Group"),
        description=_("Select a user or a group"),
        source=PrincipalSource(),
    )

    directives.widget("user_and_groups", AjaxSelectFieldWidget)
    user_and_groups = schema.List(
        title=_("label_user_and_groups", default="Users and Groups"),
        description=_("Select the users and groups that can view this collection"),
        value_type=schema.Choice(
            source=PrincipalSource()
        ),
    )

    You can pass arguments to the constructor to adjust the solr filters
    for the users and groups.
    For example, if you want to only show enabled users and groups, e.g.:

    PrincipalSource(extra_groups_filters={"-Title": INTRANET_USERS_GROUP_ID,})

    This is heavily based on the ploneintranet.microblog.browser.panel_users.Users code.
    TODO: refactor to avoid code repetition.
    """

    extra_groups_filters: dict = field(default_factory=dict)
    extra_users_filters: dict = field(default_factory=dict)

    @property
    def request(self):
        return getRequest()

    @property
    @memoize_contextless
    def pas_view(self):
        """This will return the PAS view,
        which we can use to quickly resolve the principals.
        """
        return api.content.get_view("pas_view", api.portal.get())

    @property
    @memoize_contextless
    def query(self) -> str:
        """Get the query from the request.
        `q` is the default query parameter used by pat-autosuggest.
        We strip it to avoid unnecessary whitespace in the search.
        """
        form = self.request.form
        query = (form.get("q", "") or "").strip()
        return query

    @property
    @memoize_contextless
    def search_util(self) -> SiteSearch:
        return getUtility(ISiteSearch)

    @cached_property
    def groups_filters(self) -> dict:
        """Get the filters for the groups search.

        They can be tweaked by passing `extra_groups_filters` to the constructor.
        """
        filter = {
            "portal_type": "ploneintranet.userprofile.workgroup",
            "-object_provides": IFunctionalPrincipal.__identifier__,
        }
        filter.update(self.extra_groups_filters)
        return filter

    @property
    def groups(self) -> SearchResponse:
        """Get the groups matching the current query."""
        search_util = self.search_util
        response = search_util.query(
            phrase=self.query,
            filters=self.groups_filters,
            start=0,
            step=10,
            sort="sortable_title",
        )
        return response

    @cached_property
    def users_filters(self) -> dict:
        """Get the filters for the users search.

        They can be tweaked by passing `extra_users_filters` to the constructor.
        """
        filters = {
            "portal_type": "ploneintranet.userprofile.userprofile",
            "review_state": "enabled",
        }
        filters.update(self.extra_users_filters)
        return filters

    @property
    def users(self) -> SearchResponse:
        """Get the users matching the current query."""
        search_util = self.search_util
        response = search_util.query(
            phrase=self.query,
            filters=self.users_filters,
            start=0,
            step=10,
            sort="sortable_title",
        )
        return response

    @property
    @memoize_contextless
    def items(self) -> list[SimpleTerm]:
        """Get the items for the vocabulary."""
        items = []
        for group in self.groups:
            items.append(
                SimpleTerm(
                    value=group.getObject().getGroupId(),
                    title=group.Title,
                )
            )
        for user in self.users:
            items.append(
                SimpleTerm(
                    value=user.getId,
                    title=user.Title,
                )
            )
        return items

    def getTermByToken(self, value) -> SimpleTerm | None:
        """Get the term for the given value.

        Raises a LookupError if the value is not resolvable.
        """
        pas_view = self.pas_view
        pas_uid = pas_view.principalname2uid(value)
        if not pas_uid:
            raise LookupError(f"Principal {value} not found")

        try:
            principal = pas_view.resolvepasuid(pas_uid)
        except KeyError:
            raise LookupError(f"Principal uid {pas_uid}  for {value} not resolvable")

        if pas_uid.partition(":")[0] == "userprofile":
            return SimpleTerm(
                value=value,
                title=principal.fullname,
            )

        try:
            # We expect to have a group here
            title = principal.getGroupName()
        except AttributeError:
            # If the principal doesn't have a getGroupName method,
            # we fallback to the title property
            title = principal.Title()
        return SimpleTerm(
            value=value,
            title=title,
        )

    def __contains__(self, value) -> bool:
        """Check if the value is a user or a group"""
        return bool(self.pas_view.principalname2uid(value))

    def __len__(self) -> int:
        """Get the number of currently queried items."""
        return len(self.items)

    def __iter__(self) -> Iterator[SimpleTerm]:
        """Iterate over the items in the vocabulary."""
        return iter(self.items)
