"""Azure RBAC enumeration for role assignments and definitions."""

from typing import List, Optional, Any
from datetime import datetime

from azure.mgmt.authorization.aio import AuthorizationManagementClient
from azure.core.credentials_async import AsyncTokenCredential
from azure.core.exceptions import HttpResponseError

from ..adt_types.models import ResourceNode
from ..utils.logging import get_logger

LOGGER = get_logger()


class RBACEnumerator:
    """Enumerate Azure RBAC assignments and definitions."""

    def __init__(self, credential: AsyncTokenCredential, base_url: str):
        self.credential = credential
        self.base_url = base_url

    async def enumerate_role_assignments(
        self,
        subscription_ids: List[str],
        scope_filter: Optional[List[str]] = None,
    ) -> List[ResourceNode]:
        """Enumerate role assignments across subscriptions.

        Args:
            subscription_ids: List of subscription IDs to enumerate
            scope_filter: Optional list of scope prefixes to filter by

        Returns:
            List of role assignment ResourceNode objects
        """
        assignments = []

        for subscription_id in subscription_ids:
            try:
                client = AuthorizationManagementClient(
                    credential=self.credential,
                    subscription_id=subscription_id,
                    base_url=self.base_url,
                )

                LOGGER.info(
                    "Enumerating role assignments",
                    extra={"context": {"subscription_id": subscription_id}},
                )

                async for assignment in client.role_assignments.list_for_subscription():
                    try:
                        # Handle both old and new SDK versions
                        # Azure SDK may return objects with direct attributes or nested under .properties
                        props = getattr(assignment, 'properties', assignment)

                        # Try to get scope from various possible locations
                        scope = getattr(props, 'scope', None) or getattr(assignment, 'scope', None)

                        # Apply scope filter if provided
                        if scope_filter and scope and not self._matches_scope(scope, scope_filter):
                            continue

                        # Get role definition ID
                        role_definition_id = getattr(props, 'role_definition_id', None)

                        # Resolve role definition name
                        role_def = None
                        if role_definition_id:
                            role_def = await self._get_role_definition(client, role_definition_id)

                        # Extract principal information
                        principal_id = getattr(props, 'principal_id', None)
                        principal_type = getattr(props, 'principal_type', None)
                        created_on = getattr(props, 'created_on', None)
                        created_by = getattr(props, 'created_by', None)

                        # Build properties dict
                        properties = {
                            "scope": scope,
                            "roleDefinitionId": role_definition_id,
                            "roleDefinitionName": (
                                getattr(role_def.properties if hasattr(role_def, 'properties') else role_def, 'role_name', 'Unknown')
                                if role_def else "Unknown"
                            ),
                            "principalId": principal_id,
                            "principalType": principal_type,
                            "createdOn": (
                                created_on.isoformat()
                                if created_on
                                else None
                            ),
                            "createdBy": created_by,
                        }

                        # Get ID and name from assignment
                        assignment_id = getattr(assignment, 'id', None)
                        assignment_name = getattr(assignment, 'name', None)

                        if assignment_id and assignment_name:
                            assignments.append(
                                ResourceNode(
                                    id=assignment_id,
                                    name=assignment_name,
                                    type="Microsoft.Authorization/roleAssignments",
                                    subscription_id=subscription_id,
                                    location=None,
                                    properties=properties,
                                    tags={},
                                )
                            )
                    except Exception as item_exc:
                        LOGGER.debug(
                            "Failed to process individual role assignment",
                            extra={
                                "context": {
                                    "subscription_id": subscription_id,
                                    "assignment_id": getattr(assignment, 'id', 'unknown'),
                                    "error": str(item_exc),
                                }
                            },
                        )
                        continue

                await client.close()

            except HttpResponseError as exc:
                LOGGER.error(
                    "Failed to enumerate role assignments",
                    extra={
                        "context": {
                            "subscription_id": subscription_id,
                            "error": str(exc),
                        }
                    },
                )
            except Exception as exc:
                LOGGER.error(
                    "Unexpected error enumerating role assignments",
                    extra={
                        "context": {
                            "subscription_id": subscription_id,
                            "error": str(exc),
                        }
                    },
                )

        LOGGER.info(
            "Role assignment enumeration complete",
            extra={"context": {"total_assignments": len(assignments)}},
        )

        return assignments

    async def enumerate_role_definitions(
        self,
        subscription_ids: List[str],
        include_built_in: bool = True,
        include_custom: bool = True,
    ) -> List[ResourceNode]:
        """Enumerate role definitions (built-in and custom).

        Args:
            subscription_ids: List of subscription IDs to enumerate
            include_built_in: Include built-in role definitions
            include_custom: Include custom role definitions

        Returns:
            List of role definition ResourceNode objects
        """
        definitions = []
        seen_ids = set()  # De-duplicate built-in roles across subscriptions

        for subscription_id in subscription_ids:
            try:
                client = AuthorizationManagementClient(
                    credential=self.credential,
                    subscription_id=subscription_id,
                    base_url=self.base_url,
                )

                LOGGER.info(
                    "Enumerating role definitions",
                    extra={"context": {"subscription_id": subscription_id}},
                )

                async for role_def in client.role_definitions.list(
                    scope=f"/subscriptions/{subscription_id}"
                ):
                    # Handle both old and new SDK versions
                    props = getattr(role_def, 'properties', role_def)
                    role_def_id = getattr(role_def, 'id', None)
                    role_def_name = getattr(role_def, 'name', None)

                    # Filter by role type
                    role_type = getattr(props, 'role_type', None)
                    if role_type == "BuiltInRole" and not include_built_in:
                        continue
                    if role_type == "CustomRole" and not include_custom:
                        continue

                    # Skip duplicates for built-in roles
                    if role_type == "BuiltInRole" and role_def_id in seen_ids:
                        continue
                    if role_def_id:
                        seen_ids.add(role_def_id)

                    # Extract permissions
                    permissions = []
                    perms_list = getattr(props, 'permissions', None) or []
                    for perm in perms_list:
                        permissions.append(
                            {
                                "actions": getattr(perm, 'actions', None) or [],
                                "notActions": getattr(perm, 'not_actions', None) or [],
                                "dataActions": getattr(perm, 'data_actions', None) or [],
                                "notDataActions": getattr(perm, 'not_data_actions', None) or [],
                            }
                        )

                    properties = {
                        "roleName": getattr(props, 'role_name', None),
                        "roleType": role_type,
                        "description": getattr(props, 'description', None),
                        "assignableScopes": getattr(props, 'assignable_scopes', None) or [],
                        "permissions": permissions,
                    }

                    if role_def_id and role_def_name:
                        definitions.append(
                            ResourceNode(
                                id=role_def_id,
                                name=role_def_name,
                                type="Microsoft.Authorization/roleDefinitions",
                                subscription_id=subscription_id,
                                location=None,
                                properties=properties,
                                tags={},
                            )
                        )

                await client.close()

            except HttpResponseError as exc:
                LOGGER.error(
                    "Failed to enumerate role definitions",
                    extra={
                        "context": {
                            "subscription_id": subscription_id,
                            "error": str(exc),
                        }
                    },
                )
            except Exception as exc:
                LOGGER.error(
                    "Unexpected error enumerating role definitions",
                    extra={
                        "context": {
                            "subscription_id": subscription_id,
                            "error": str(exc),
                        }
                    },
                )

        LOGGER.info(
            "Role definition enumeration complete",
            extra={"context": {"total_definitions": len(definitions)}},
        )

        return definitions

    def _matches_scope(self, assignment_scope: str, scope_filter: List[str]) -> bool:
        """Check if assignment scope matches any filter pattern.

        Args:
            assignment_scope: Scope of the assignment
            scope_filter: List of scope prefixes to match

        Returns:
            True if scope matches any filter
        """
        for filter_scope in scope_filter:
            if assignment_scope.lower().startswith(filter_scope.lower()):
                return True
        return False

    async def _get_role_definition(
        self, client: AuthorizationManagementClient, role_def_id: str
    ) -> Optional[Any]:
        """Fetch role definition details.

        Args:
            client: Authorization management client
            role_def_id: Role definition ID

        Returns:
            Role definition object or None if not found
        """
        try:
            return await client.role_definitions.get_by_id(role_def_id)
        except Exception as exc:
            LOGGER.debug(
                "Could not fetch role definition",
                extra={"context": {"role_def_id": role_def_id, "error": str(exc)}},
            )
            return None
