"""Protected module containing the ECMSecurityAsync namespace class."""

from __future__ import annotations

import logging
from typing import TYPE_CHECKING

from ecmind_blue_client.ecm._ecm_adm_info_parser import ECMAdmInfoParser, ECMGroup, ECMUser, ECMUserAttributes
from ecmind_blue_client.ecm._ecm_security_roles import ECMSystemRole
from ecmind_blue_client.rpc._blue_exception import BlueException
from ecmind_blue_client.rpc._jobs import Jobs

_log = logging.getLogger(__name__)

if TYPE_CHECKING:
    from ecmind_blue_client.ecm.asynchron._ecm_async import ECMAsync


class ECMSecurityAsync:
    """Asynchronous security-related operations namespace.

    Provides access to user and role management functionality.
    Accessed via ``ecm.security`` on an ``ECMAsync`` instance.
    """

    def __init__(self, ecm: "ECMAsync") -> None:
        """Initialize the security namespace.

        Args:
            ecm: The parent ``ECMAsync`` instance used to execute server requests.
        """
        self._ecm = ecm

    async def roles(self, user_guid: str = "") -> list[ECMSystemRole]:
        """Return the system roles assigned to a user.

        Without a ``user_guid``, the roles of the currently logged-in user are returned.
        Querying another user's roles requires the ``ADMIN_CONFIGURE_SECURITY``
        system role.

        Args:
            user_guid: GUID of the user whose roles should be returned.  Pass an empty
                string (the default) to retrieve the roles of the currently logged-in user.

        Returns:
            A list of :class:`ECMSystemRole` members assigned to the user.  Unknown role
            IDs (not present in the enum) are silently omitted.  Returns an empty list
            when no roles are assigned.
        """
        result = await self._ecm.execute(Jobs.MNG_GETUSERROLES, Flags=0, UserGUID=user_guid)
        raw = result.get("Result", str, "")
        roles = []
        for r in raw.split(";"):
            if not r:
                continue
            try:
                roles.append(ECMSystemRole(int(r)))
            except ValueError:
                _log.error("Unknown system role ID %r returned by server — update ECMSystemRole enum.", r)
        return roles

    async def users(self, *, extended_info: bool = False) -> list[ECMUser]:
        """Return all user accounts known to the server.

        Args:
            extended_info: When ``True``, each :class:`ECMUser` is populated
                with the ``groups`` the user belongs to.

        Returns:
            A list of :class:`ECMUser` instances, one per user account.
        """
        result = await self._ecm.execute(Jobs.MNG_GETUSERLIST, Flags=0, OutputUnicode=1, ExtendedInfo=extended_info)
        xml = result.get("utfUserList", bytes, None)
        if xml is None:
            xml = result.get("UserList", str, "<AdmInfo/>").encode("utf-8")
        return ECMAdmInfoParser(xml, extended_info=extended_info).users()

    async def user_groups(self, user_guid: str) -> list[ECMGroup]:
        """Return all groups the given user belongs to.

        Args:
            user_guid: GUID of the user whose group memberships should be returned.

        Returns:
            A list of :class:`ECMGroup` instances.  Returns an empty list when
            the user is not a member of any group.
        """
        result = await self._ecm.execute(Jobs.MNG_GETUSERGROUPS, Flags=0, OutputUnicode=1, UserGUID=user_guid)
        xml = result.get("utfGroupList", bytes, None)
        if xml is None:
            return []
        return ECMAdmInfoParser(xml).groups()

    async def groups(self) -> list[ECMGroup]:
        """Return all groups defined on the server.

        Returns:
            A list of :class:`ECMGroup` instances.  Returns an empty list when
            no groups exist.
        """
        result = await self._ecm.execute(Jobs.MNG_GETGROUPLIST, Flags=0, OutputUnicode=1)
        xml = result.get("utfGroupList", bytes, None)
        if xml is None:
            return []
        return ECMAdmInfoParser(xml).groups()

    async def group(self, name: str) -> ECMGroup | None:
        """Return the attributes of a single group.

        Args:
            name: Name of the group.

        Returns:
            An :class:`ECMGroup` instance, or ``None`` when the group does not exist.
        """
        try:
            result = await self._ecm.execute(Jobs.MNG_GETGROUPATTRIBUTES, Flags=0, OutputUnicode=1, Group=name)
        except BlueException:
            return None
        xml = result.get("utfXmlInfo", bytes, None)
        if xml is None:
            return None
        return ECMAdmInfoParser(xml).group_attributes()

    async def group_members(self, group_name: str) -> list[ECMUser]:
        """Return all members of the given group.

        Args:
            group_name: Name of the group.

        Returns:
            A list of :class:`ECMUser` instances.  Returns an empty list when
            the group has no members or does not exist.
        """
        try:
            result = await self._ecm.execute(
                Jobs.MNG_GETGROUPMEMBERS, Flags=0, OutputUnicode=1, GroupName=group_name, GroupGUID="", GroupID=0
            )
        except BlueException:
            return []
        xml = result.get("utfUserList", bytes, None)
        if xml is None:
            return []
        return ECMAdmInfoParser(xml).users()

    async def user(self, username: str) -> ECMUserAttributes | None:
        """Return detailed attributes for a single user account.

        Args:
            username: Internal user name (e.g. ``"ROOT"``), **not** the GUID.

        Returns:
            An :class:`ECMUserAttributes` instance, or ``None`` when the server
            returns no data for the given user name.
        """
        try:
            result = await self._ecm.execute(Jobs.MNG_GETUSERATTRIBUTES, Flags=0, OutputUnicode=1, User=username)
        except BlueException:
            return None
        xml = result.get("utfXmlInfo", bytes, None)
        if xml is None:
            return None
        return ECMAdmInfoParser(xml).user_attributes()
