"""Microsoft Purview enumerator for data governance and compliance discovery.

This module provides enumeration capabilities for Microsoft Purview (formerly Microsoft
Information Protection and Compliance Center), including:
- Sensitivity labels for information protection
- Data Loss Prevention (DLP) policies
- Retention policies and labels
- Information protection policies

Required Microsoft Graph API Permissions:
- InformationProtectionPolicy.Read.All (Application or Delegated)
- Policy.Read.All (Application or Delegated)
- RecordsManagement.Read.All (Application or Delegated)
"""

import logging
from typing import Any, List, Optional

from azure.core.credentials import TokenCredential
from msgraph import GraphServiceClient

try:
    from msgraph.generated.models.information_protection_label import (
        InformationProtectionLabel,
    )
except ModuleNotFoundError:
    from msgraph.generated.models.sensitivity_label import (
        SensitivityLabel as InformationProtectionLabel,
    )

try:
    from msgraph.generated.models.dlp_policy import DlpPolicy
except ModuleNotFoundError:
    DlpPolicy = Any  # type: ignore[misc, assignment]

try:
    from msgraph.generated.models.retention_policy import RetentionPolicy
except ModuleNotFoundError:
    RetentionPolicy = Any  # type: ignore[misc, assignment]

try:
    from msgraph.generated.models.retention_label import RetentionLabel
except ModuleNotFoundError:
    RetentionLabel = Any  # type: ignore[misc, assignment]

from azure_discovery.adt_types.models import PurviewConfig, ResourceNode

logger = logging.getLogger(__name__)


class PurviewEnumerator:
    """Enumerates Microsoft Purview data governance and compliance resources."""

    def __init__(
        self,
        tenant_id: str,
        credential: TokenCredential,
        config: Optional[PurviewConfig] = None,
    ):
        """Initialize the Purview enumerator.

        Args:
            tenant_id: Azure AD tenant ID
            credential: Azure credential for authentication
            config: Purview enumeration configuration
        """
        self.tenant_id = tenant_id
        self.credential = credential
        self.config = config or PurviewConfig()
        self.client = GraphServiceClient(credentials=credential)

    async def enumerate_sensitivity_labels(self) -> List[ResourceNode]:
        """Enumerate information protection sensitivity labels.

        Returns:
            List of sensitivity label nodes
        """
        logger.info("Enumerating Purview sensitivity labels...")
        labels = []

        try:
            # Sensitivity labels: current SDK uses security.data_security_and_governance.sensitivity_labels
            response = await self.client.security.data_security_and_governance.sensitivity_labels.get()

            if not response or not getattr(response, "value", None):
                logger.warning("No sensitivity labels found or insufficient permissions")
                return labels

            for label in response.value:
                # Apply filtering if configured
                if self._should_include_sensitivity_label(label):
                    node = self._build_sensitivity_label_node(label)
                    labels.append(node)

                    # Check max limit
                    if len(labels) >= self.config.sensitivity_max_labels:
                        logger.warning(
                            f"Reached max sensitivity label limit ({self.config.sensitivity_max_labels})"
                        )
                        break

            logger.info(f"Enumerated {len(labels)} sensitivity labels")

        except Exception as e:
            logger.error(f"Failed to enumerate sensitivity labels: {e}")
            # Don't raise - allow discovery to continue

        return labels

    async def enumerate_dlp_policies(self) -> List[ResourceNode]:
        """Enumerate Data Loss Prevention (DLP) policies.

        Returns:
            List of DLP policy nodes
        """
        logger.info("Enumerating Purview DLP policies...")
        policies = []

        try:
            # DLP policies: try information_protection.data_loss_prevention_policies (legacy) or skip if not in SDK
            get_dlp = getattr(
                getattr(self.client, "information_protection", None),
                "data_loss_prevention_policies",
                None,
            )
            if get_dlp is None:
                logger.warning("DLP policies API not available in this SDK version")
                return policies
            response = await get_dlp.get()
            if not response or not getattr(response, "value", None):
                logger.warning("No DLP policies found or insufficient permissions")
                return policies

            for policy in response.value:
                # Apply filtering if configured
                if self._should_include_dlp_policy(policy):
                    node = self._build_dlp_policy_node(policy)
                    policies.append(node)

                    # Check max limit
                    if len(policies) >= self.config.dlp_max_policies:
                        logger.warning(f"Reached max DLP policy limit ({self.config.dlp_max_policies})")
                        break

            logger.info(f"Enumerated {len(policies)} DLP policies")

        except Exception as e:
            logger.error(f"Failed to enumerate DLP policies: {e}")
            # Don't raise - allow discovery to continue

        return policies

    async def enumerate_retention_policies(self) -> List[ResourceNode]:
        """Enumerate retention and deletion policies.

        Returns:
            List of retention policy nodes
        """
        logger.info("Enumerating Purview retention policies...")
        policies = []

        try:
            # Retention policies: try security.retention_policies (may not exist in all SDK versions)
            rp = getattr(self.client.security, "retention_policies", None)
            if rp is None:
                logger.warning("Retention policies API not available in this SDK version")
                return policies
            response = await rp.get()
            if not response or not getattr(response, "value", None):
                logger.warning("No retention policies found or insufficient permissions")
                return policies

            for policy in response.value:
                # Apply filtering if configured
                if self._should_include_retention_policy(policy):
                    node = self._build_retention_policy_node(policy)
                    policies.append(node)

                    # Check max limit
                    if len(policies) >= self.config.retention_max_policies:
                        logger.warning(
                            f"Reached max retention policy limit ({self.config.retention_max_policies})"
                        )
                        break

            logger.info(f"Enumerated {len(policies)} retention policies")

        except Exception as e:
            logger.error(f"Failed to enumerate retention policies: {e}")
            # Don't raise - allow discovery to continue

        return policies

    async def enumerate_retention_labels(self) -> List[ResourceNode]:
        """Enumerate retention labels for records management.

        Returns:
            List of retention label nodes
        """
        logger.info("Enumerating Purview retention labels...")
        labels = []

        try:
            # Get retention labels from Microsoft Graph
            response = await self.client.security.labels.retention_labels.get()

            if not response or not response.value:
                logger.warning("No retention labels found or insufficient permissions")
                return labels

            for label in response.value:
                # Apply filtering if configured
                if self._should_include_retention_label(label):
                    node = self._build_retention_label_node(label)
                    labels.append(node)

                    # Check max limit
                    if len(labels) >= self.config.retention_max_labels:
                        logger.warning(
                            f"Reached max retention label limit ({self.config.retention_max_labels})"
                        )
                        break

            logger.info(f"Enumerated {len(labels)} retention labels")

        except Exception as e:
            logger.error(f"Failed to enumerate retention labels: {e}")
            # Don't raise - allow discovery to continue

        return labels

    async def enumerate_information_protection_policies(self) -> List[ResourceNode]:
        """Enumerate information protection policies.

        Returns:
            List of information protection policy nodes
        """
        logger.info("Enumerating Purview information protection policies...")
        policies = []

        try:
            # Information protection policy: SDK may expose as information_protection.policy or not at all
            policy_builder = getattr(
                getattr(self.client, "information_protection", None), "policy", None
            )
            if policy_builder is None:
                logger.warning("Information protection policy API not available in this SDK version")
                return policies
            response = await policy_builder.get()
            if not response:
                logger.warning("No information protection policy found or insufficient permissions")
                return policies

            # Build policy node
            node = self._build_information_protection_policy_node(response)
            policies.append(node)

            logger.info("Enumerated information protection policy")

        except Exception as e:
            logger.error(f"Failed to enumerate information protection policies: {e}")
            # Don't raise - allow discovery to continue

        return policies

    # Private helper methods for filtering

    def _should_include_sensitivity_label(self, label: InformationProtectionLabel) -> bool:
        """Check if sensitivity label should be included based on filters."""
        if not label or not label.name:
            return False

        # Apply name filter if configured
        if self.config.sensitivity_label_filter:
            label_name = label.name.lower()
            if not any(filter_pattern.lower() in label_name for filter_pattern in self.config.sensitivity_label_filter):
                return False

        return True

    def _should_include_dlp_policy(self, policy: DlpPolicy) -> bool:
        """Check if DLP policy should be included based on filters."""
        if not policy or not policy.name:
            return False

        # Skip disabled policies if configured
        if not self.config.dlp_include_disabled and hasattr(policy, 'is_enabled') and not policy.is_enabled:
            return False

        # Apply name filter if configured
        if self.config.dlp_policy_filter:
            policy_name = policy.name.lower()
            if not any(filter_pattern.lower() in policy_name for filter_pattern in self.config.dlp_policy_filter):
                return False

        return True

    def _should_include_retention_policy(self, policy: RetentionPolicy) -> bool:
        """Check if retention policy should be included based on filters."""
        if not policy or not policy.display_name:
            return False

        # Skip disabled policies if configured
        if not self.config.retention_include_disabled and hasattr(policy, 'is_enabled') and not policy.is_enabled:
            return False

        # Apply name filter if configured
        if self.config.retention_policy_filter:
            policy_name = policy.display_name.lower()
            if not any(filter_pattern.lower() in policy_name for filter_pattern in self.config.retention_policy_filter):
                return False

        return True

    def _should_include_retention_label(self, label: RetentionLabel) -> bool:
        """Check if retention label should be included based on filters."""
        if not label or not label.display_name:
            return False

        # Apply name filter if configured
        if self.config.retention_label_filter:
            label_name = label.display_name.lower()
            if not any(filter_pattern.lower() in label_name for filter_pattern in self.config.retention_label_filter):
                return False

        return True

    # Private helper methods for node building

    def _build_sensitivity_label_node(self, label: InformationProtectionLabel) -> ResourceNode:
        """Build a resource node for a sensitivity label."""
        return ResourceNode(
            id=f"purview://sensitivity-labels/{label.id}",
            name=label.name or "Unknown Label",
            type="Microsoft.Purview/sensitivityLabels",
            subscription_id=self.tenant_id,
            properties={
                "labelId": label.id,
                "displayName": getattr(label, 'display_name', label.name),
                "description": getattr(label, 'description', None),
                "color": getattr(label, 'color', None),
                "sensitivity": getattr(label, 'sensitivity', None),
                "tooltip": getattr(label, 'tooltip', None),
                "isActive": getattr(label, 'is_active', True),
                "parentId": getattr(label, 'parent_id', None),
            },
            tags={
                "labelId": label.id,
                "isActive": str(getattr(label, 'is_active', True)),
            },
        )

    def _build_dlp_policy_node(self, policy: DlpPolicy) -> ResourceNode:
        """Build a resource node for a DLP policy."""
        return ResourceNode(
            id=f"purview://dlp-policies/{policy.id}",
            name=policy.name or "Unknown Policy",
            type="Microsoft.Purview/dlpPolicies",
            subscription_id=self.tenant_id,
            properties={
                "policyId": policy.id,
                "displayName": getattr(policy, 'display_name', policy.name),
                "description": getattr(policy, 'description', None),
                "isEnabled": getattr(policy, 'is_enabled', True),
                "mode": getattr(policy, 'mode', None),
                "priority": getattr(policy, 'priority', None),
                "locations": getattr(policy, 'locations', []),
            },
            tags={
                "policyId": policy.id,
                "isEnabled": str(getattr(policy, 'is_enabled', True)),
                "mode": getattr(policy, 'mode', 'Unknown'),
            },
        )

    def _build_retention_policy_node(self, policy: RetentionPolicy) -> ResourceNode:
        """Build a resource node for a retention policy."""
        return ResourceNode(
            id=f"purview://retention-policies/{policy.id}",
            name=policy.display_name or "Unknown Policy",
            type="Microsoft.Purview/retentionPolicies",
            subscription_id=self.tenant_id,
            properties={
                "policyId": policy.id,
                "displayName": policy.display_name,
                "description": getattr(policy, 'description', None),
                "isEnabled": getattr(policy, 'is_enabled', True),
                "retentionDuration": getattr(policy, 'retention_duration', None),
                "actionType": getattr(policy, 'action_type', None),
            },
            tags={
                "policyId": policy.id,
                "isEnabled": str(getattr(policy, 'is_enabled', True)),
            },
        )

    def _build_retention_label_node(self, label: RetentionLabel) -> ResourceNode:
        """Build a resource node for a retention label."""
        return ResourceNode(
            id=f"purview://retention-labels/{label.id}",
            name=label.display_name or "Unknown Label",
            type="Microsoft.Purview/retentionLabels",
            subscription_id=self.tenant_id,
            properties={
                "labelId": label.id,
                "displayName": label.display_name,
                "description": getattr(label, 'description', None),
                "isInUse": getattr(label, 'is_in_use', False),
                "retentionDuration": getattr(label, 'retention_duration', None),
                "actionAfterRetentionPeriod": getattr(label, 'action_after_retention_period', None),
            },
            tags={
                "labelId": label.id,
                "isInUse": str(getattr(label, 'is_in_use', False)),
            },
        )

    def _build_information_protection_policy_node(self, policy: any) -> ResourceNode:
        """Build a resource node for information protection policy."""
        policy_id = getattr(policy, 'id', 'default-policy')
        name = getattr(policy, 'display_name', None) or "Information Protection Policy"
        return ResourceNode(
            id=f"purview://information-protection-policies/{policy_id}",
            name=name,
            type="Microsoft.Purview/informationProtectionPolicies",
            subscription_id=self.tenant_id,
            properties={
                "policyId": policy_id,
                "displayName": getattr(policy, 'display_name', 'Default Policy'),
                "description": getattr(policy, 'description', None),
            },
            tags={
                "policyId": policy_id,
            },
        )
