"""
OneLake Data Access Security Rules.

Provides classes for defining security rules with row-level and column-level
security constraints for OneLake items.
"""

from dataclasses import dataclass, field
from typing import Any, Dict, List, Optional, Union, overload

from ..._shared.enums import ItemAccess, MemberType


@dataclass
class Member:
    """
    Defines a security member for OneLake data access.

    Members specify who can access data and with what permissions.

    Attributes:
        memberType: Type of member (MemberType.ENTRA or MemberType.FABRIC)
        path: Optional path for Fabric item members
        tenant: Optional tenant ID for Entra members
        object: Optional object ID for Entra members
        access: List of access permissions (use ItemAccess enum values)

    Examples:
        >>> # Entra member with read access
        >>> member = Member(
        ...     memberType=MemberType.ENTRA,
        ...     tenant="tenant-guid",
        ...     object="user-guid",
        ...     access=[ItemAccess.READ]
        ... )
        >>>
        >>> # Entra member with multiple permissions
        >>> member = Member(
        ...     memberType=MemberType.ENTRA,
        ...     tenant="tenant-guid",
        ...     object="group-guid",
        ...     access=[ItemAccess.READ, ItemAccess.WRITE, ItemAccess.RESHARE]
        ... )
        >>>
        >>> # Fabric item member
        >>> member = Member(
        ...     memberType=MemberType.FABRIC,
        ...     path="/items/workspace-id/item-id",
        ...     access=[ItemAccess.READALL]
        ... )
    """

    memberType: Union[MemberType, str]
    path: Optional[str] = field(default=None)
    tenant: Optional[str] = field(default=None)
    object: Optional[str] = field(default=None)
    access: Optional[List[Union[ItemAccess, str]]] = field(default=None)

    def toJsonSingle(self) -> Dict[str, Any]:
        """Convert single member to API format."""
        if self.memberType == MemberType.FABRIC:
            return {
                "sourcePath": self.path,
                "itemAccess": [str(a) for a in self.access] if self.access else [],
            }
        else:  # MemberType.ENTRA
            return {
                "tenantId": self.tenant,
                "objectId": self.object,
            }

    @classmethod
    def fromJsonSingle(cls, data: Dict[str, Any], member_type: MemberType) -> "Member":
        """
        Create Member from API response.

        Args:
            data: Individual member data from API
            member_type: Type of member (ENTRA or FABRIC) to determine parsing

        Returns:
            Member object
        """
        if member_type == MemberType.FABRIC:
            # Fabric item member: {"sourcePath": "...", "itemAccess": ["Read"]}
            return cls(
                memberType=MemberType.FABRIC,
                path=data.get("sourcePath"),
                access=[
                    ItemAccess(a) if a in [e.value for e in ItemAccess] else a
                    for a in data.get("itemAccess", [])
                ],
            )
        else:  # MemberType.ENTRA
            # Entra member: {"tenantId": "...", "objectId": "..."}
            return cls(
                memberType=MemberType.ENTRA,
                tenant=data.get("tenantId"),
                object=data.get("objectId"),
            )

    @classmethod
    def toJson(cls, members: List["Member"]) -> Dict[str, Any]:
        """
        Convert list of Member objects to API format.

        Groups members by type into microsoftEntraMembers and fabricItemMembers arrays.

        Args:
            members: List of Member objects

        Returns:
            Dict with microsoftEntraMembers and fabricItemMembers arrays

        Example:
            >>> members = [
            ...     Member(memberType=MemberType.ENTRA, tenant="t1", object="o1"),
            ...     Member(memberType=MemberType.FABRIC, path="path1", access=[ItemAccess.READ])
            ... ]
            >>> result = Member.toJson(members)
            >>> # Returns: {
            >>> #   "microsoftEntraMembers": [{"tenantId": "t1", "objectId": "o1"}],
            >>> #   "fabricItemMembers": [{"sourcePath": "path1", "itemAccess": ["Read"]}]
            >>> # }
        """
        entra_members = []
        fabric_members = []

        for member in members:
            if member.memberType == MemberType.FABRIC:
                fabric_members.append(cls.toJsonSingle(member))
            else:  # MemberType.ENTRA
                entra_members.append(cls.toJsonSingle(member))

        result = {}
        if entra_members:
            result["microsoftEntraMembers"] = entra_members
        if fabric_members:
            result["fabricItemMembers"] = fabric_members

        return result

    @classmethod
    def fromJson(cls, data: Dict[str, Any]) -> List["Member"]:
        """
        Parse grouped members structure from API response.

        Converts microsoftEntraMembers and fabricItemMembers arrays into list of Member objects.

        Args:
            data: Dict containing microsoftEntraMembers and/or fabricItemMembers

        Returns:
            List of Member objects

        Example:
            >>> data = {
            ...     "microsoftEntraMembers": [{"tenantId": "t1", "objectId": "o1"}],
            ...     "fabricItemMembers": [{"sourcePath": "path1", "itemAccess": ["Read"]}]
            ... }
            >>> members = Member.fromJson(data)
            >>> # Returns list of 2 Member objects
        """
        members = []

        # Parse Entra members
        if "microsoftEntraMembers" in data:
            for entra_data in data["microsoftEntraMembers"]:
                members.append(cls.fromJsonSingle(entra_data, MemberType.ENTRA))

        # Parse Fabric item members
        if "fabricItemMembers" in data:
            for fabric_data in data["fabricItemMembers"]:
                members.append(cls.fromJsonSingle(fabric_data, MemberType.FABRIC))

        return members


class FabricItem(Member):
    """
    Alias for Member class to represent Fabric workspace item members.

    This class exists for semantic clarity when dealing with Fabric items.
    It inherits all functionality from the Member class.

    Examples:
        >>> # Creating a Fabric item member (path auto-populated by role)
        >>> fabric_member = FabricItem(access=[ItemAccess.READALL])

        >>> # Creating a Fabric item member (explicit path)
        >>> fabric_member = FabricItem(
        ...     path="workspace-id/item-id",
        ...     access=[ItemAccess.READALL]
        ... )
    """

    def __init__(self, access: List[Union[ItemAccess, str]], path: Optional[str] = None):
        super().__init__(memberType=MemberType.FABRIC, path=path, access=access)


class EntraMember(Member):
    """
    Alias for Member class to represent Entra members.

    This class exists for semantic clarity when dealing with Entra members.
    It inherits all functionality from the Member class.

    Examples:
        >>> # Creating an Entra member (auto-detect tenant)
        >>> entra_member = EntraMember("user-guid")

        >>> # Creating an Entra member (explicit tenant)
        >>> entra_member = EntraMember("tenant-guid", "user-guid")
    """

    @overload
    def __init__(self, object: str) -> None:
        """Create EntraMember with object ID, tenant auto-detected from client."""
        ...

    @overload
    def __init__(self, tenant: str, object: str) -> None:
        """Create EntraMember with explicit tenant and object ID."""
        ...

    def __init__(self, tenant: Optional[str] = None, object: Optional[str] = None):
        if object is None and tenant is not None:
            # Single argument: tenant param holds the object ID
            object = tenant
            tenant = None

        if tenant is None:
            # Auto-detect tenant from client
            from ...._fabric import _get_client

            client = _get_client()
            tenant = client.tenant_id if hasattr(client, "tenant_id") else None

        super().__init__(memberType=MemberType.ENTRA, tenant=tenant, object=object)
