"""M365Enumerator for Microsoft 365 workload discovery."""

import logging
from typing import Any, Dict, List, Optional

from azure.identity import DefaultAzureCredential
from msgraph import GraphServiceClient
from msgraph.generated.models.o_data_errors.o_data_error import ODataError

from azure_discovery.adt_types.models import M365Config, ResourceNode

logger = logging.getLogger(__name__)


class M365Enumerator:
    """Enumerates Microsoft 365 resources (SharePoint, Teams, OneDrive, Exchange)."""

    def __init__(
        self,
        tenant_id: str,
        credential: Optional[DefaultAzureCredential] = None,
        config: Optional[M365Config] = None,
    ):
        """Initialize M365 enumerator.

        Args:
            tenant_id: Azure AD tenant ID
            credential: Azure credential for authentication (uses DefaultAzureCredential if not provided)
            config: M365 enumeration configuration
        """
        self.tenant_id = tenant_id
        self.credential = credential or DefaultAzureCredential()
        self.config = config or M365Config()

        # Initialize Microsoft Graph client
        self.graph_client = GraphServiceClient(self.credential, scopes=["https://graph.microsoft.com/.default"])

        logger.info(
            f"Initialized M365Enumerator for tenant {tenant_id} "
            f"(SharePoint: {self.config.include_sharepoint_sites}, "
            f"Teams: {self.config.include_teams}, "
            f"OneDrive: {self.config.include_onedrive}, "
            f"Exchange: {self.config.include_exchange_mailboxes})"
        )

    async def enumerate_sharepoint_sites(self) -> List[ResourceNode]:
        """Enumerate SharePoint sites.

        Returns:
            List of ResourceNode objects representing SharePoint sites
        """
        if not self.config.include_sharepoint_sites:
            logger.debug("SharePoint site enumeration disabled")
            return []

        logger.info("Enumerating SharePoint sites...")
        sites: List[ResourceNode] = []
        site_count = 0

        try:
            # Get all sites (paginated)
            result = await self.graph_client.sites.get()

            if not result or not result.value:
                logger.warning("No SharePoint sites found")
                return []

            for site in result.value:
                if site_count >= self.config.sharepoint_max_sites:
                    logger.info(
                        f"Reached max SharePoint sites limit ({self.config.sharepoint_max_sites}), stopping enumeration"
                    )
                    break

                # Apply site filter if configured
                if self.config.sharepoint_site_filter:
                    if not any(pattern in (site.web_url or "") for pattern in self.config.sharepoint_site_filter):
                        continue

                # Build ResourceNode
                node = ResourceNode(
                    id=f"https://graph.microsoft.com/v1.0/sites/{site.id}",
                    name=site.display_name or site.name or "Unknown Site",
                    type="Microsoft.SharePoint/sites",
                    subscription_id=self.tenant_id,  # M365 resources use tenant_id as subscription_id
                    location="global",
                    properties={
                        "site_id": site.id,
                        "web_url": site.web_url,
                        "description": site.description,
                        "created_datetime": str(site.created_date_time) if site.created_date_time else None,
                        "last_modified_datetime": str(site.last_modified_date_time)
                        if site.last_modified_date_time
                        else None,
                    },
                    tags={"siteId": site.id},
                )

                sites.append(node)
                site_count += 1

            logger.info(f"Enumerated {len(sites)} SharePoint sites")

        except ODataError as e:
            logger.error(f"Error enumerating SharePoint sites: {e.error.message if e.error else str(e)}")
        except Exception as e:
            logger.error(f"Unexpected error enumerating SharePoint sites: {e}")

        return sites

    async def enumerate_teams(self) -> List[ResourceNode]:
        """Enumerate Microsoft Teams.

        Returns:
            List of ResourceNode objects representing Teams
        """
        if not self.config.include_teams:
            logger.debug("Teams enumeration disabled")
            return []

        logger.info("Enumerating Microsoft Teams...")
        teams: List[ResourceNode] = []
        team_count = 0

        try:
            # Get all teams (groups with resourceProvisioningOptions containing 'Team')
            result = await self.graph_client.groups.get(
                request_configuration=lambda req_config: (
                    req_config.query_parameters.update(
                        {
                            "$filter": "resourceProvisioningOptions/any(x:x eq 'Team')",
                            "$select": "id,displayName,description,mail,visibility,createdDateTime,isArchived,resourceProvisioningOptions",
                        }
                    )
                )
            )

            if not result or not result.value:
                logger.warning("No Teams found")
                return []

            for team in result.value:
                if team_count >= self.config.teams_max_teams:
                    logger.info(f"Reached max Teams limit ({self.config.teams_max_teams}), stopping enumeration")
                    break

                # Filter archived teams if configured
                if self.config.teams_filter_archived and team.is_archived:
                    continue

                # Build ResourceNode
                node = ResourceNode(
                    id=f"https://graph.microsoft.com/v1.0/teams/{team.id}",
                    name=team.display_name or "Unknown Team",
                    type="Microsoft.Teams/teams",
                    subscription_id=self.tenant_id,
                    location="global",
                    properties={
                        "team_id": team.id,
                        "description": team.description,
                        "mail": team.mail,
                        "visibility": team.visibility,
                        "created_datetime": str(team.created_date_time) if team.created_date_time else None,
                        "is_archived": team.is_archived,
                    },
                )

                teams.append(node)
                team_count += 1

            logger.info(f"Enumerated {len(teams)} Microsoft Teams")

        except ODataError as e:
            logger.error(f"Error enumerating Teams: {e.error.message if e.error else str(e)}")
        except Exception as e:
            logger.error(f"Unexpected error enumerating Teams: {e}")

        return teams

    async def enumerate_onedrive_drives(self) -> List[ResourceNode]:
        """Enumerate OneDrive drives.

        Returns:
            List of ResourceNode objects representing OneDrive drives
        """
        if not self.config.include_onedrive:
            logger.debug("OneDrive enumeration disabled")
            return []

        logger.info("Enumerating OneDrive drives...")
        drives: List[ResourceNode] = []
        drive_count = 0

        try:
            # Get all drives (personal OneDrive drives)
            result = await self.graph_client.drives.get()

            if not result or not result.value:
                logger.warning("No OneDrive drives found")
                return []

            for drive in result.value:
                if drive_count >= self.config.onedrive_max_drives:
                    logger.info(
                        f"Reached max OneDrive drives limit ({self.config.onedrive_max_drives}), stopping enumeration"
                    )
                    break

                # Apply user filter if configured
                if self.config.onedrive_user_filter and drive.owner:
                    owner_upn = getattr(drive.owner.user, "user_principal_name", None)
                    if owner_upn not in self.config.onedrive_user_filter:
                        continue

                # Build ResourceNode
                node = ResourceNode(
                    id=f"https://graph.microsoft.com/v1.0/drives/{drive.id}",
                    name=drive.name or drive.id or "Unknown Drive",
                    type="Microsoft.OneDrive/drives",
                    subscription_id=self.tenant_id,
                    location="global",
                    properties={
                        "drive_id": drive.id,
                        "drive_type": drive.drive_type,
                        "web_url": drive.web_url,
                        "quota_total": drive.quota.total if drive.quota else None,
                        "quota_used": drive.quota.used if drive.quota else None,
                        "quota_remaining": drive.quota.remaining if drive.quota else None,
                        "created_datetime": str(drive.created_date_time) if drive.created_date_time else None,
                        "last_modified_datetime": str(drive.last_modified_date_time)
                        if drive.last_modified_date_time
                        else None,
                        "owner_upn": getattr(drive.owner.user, "user_principal_name", None) if drive.owner else None,
                    },
                )

                drives.append(node)
                drive_count += 1

            logger.info(f"Enumerated {len(drives)} OneDrive drives")

        except ODataError as e:
            logger.error(f"Error enumerating OneDrive drives: {e.error.message if e.error else str(e)}")
        except Exception as e:
            logger.error(f"Unexpected error enumerating OneDrive drives: {e}")

        return drives

    async def enumerate_exchange_mailboxes(self) -> List[ResourceNode]:
        """Enumerate Exchange Online mailboxes.

        Note: Requires Mail.Read.All or Exchange.ManageAsApp permission.
        This is often not granted for discovery scenarios.

        Returns:
            List of ResourceNode objects representing Exchange mailboxes
        """
        if not self.config.include_exchange_mailboxes:
            logger.debug("Exchange mailbox enumeration disabled")
            return []

        logger.warning(
            "Exchange mailbox enumeration requires Mail.Read.All or Exchange.ManageAsApp permission. "
            "This is typically not granted for discovery scenarios."
        )

        logger.info("Enumerating Exchange mailboxes...")
        mailboxes: List[ResourceNode] = []
        mailbox_count = 0

        try:
            # Get all users with mailboxes
            result = await self.graph_client.users.get(
                request_configuration=lambda req_config: (
                    req_config.query_parameters.update(
                        {
                            "$filter": "assignedLicenses/$count ne 0 and accountEnabled eq true",
                            "$select": "id,userPrincipalName,displayName,mail,mailboxSettings",
                            "$count": "true",
                        }
                    )
                )
            )

            if not result or not result.value:
                logger.warning("No Exchange mailboxes found")
                return []

            for user in result.value:
                if mailbox_count >= self.config.exchange_max_mailboxes:
                    logger.info(
                        f"Reached max Exchange mailboxes limit ({self.config.exchange_max_mailboxes}), stopping enumeration"
                    )
                    break

                # Apply user filter if configured
                if self.config.exchange_user_filter and user.user_principal_name not in self.config.exchange_user_filter:
                    continue

                # Skip users without mail
                if not user.mail:
                    continue

                # Build ResourceNode
                node = ResourceNode(
                    id=f"https://graph.microsoft.com/v1.0/users/{user.id}/mailbox",
                    name=user.display_name or user.user_principal_name or "Unknown Mailbox",
                    type="Microsoft.Exchange/mailboxes",
                    subscription_id=self.tenant_id,
                    location="global",
                    properties={
                        "user_id": user.id,
                        "user_principal_name": user.user_principal_name,
                        "mail": user.mail,
                        "mailbox_settings": user.mailbox_settings,
                    },
                )

                mailboxes.append(node)
                mailbox_count += 1

            logger.info(f"Enumerated {len(mailboxes)} Exchange mailboxes")

        except ODataError as e:
            if "Authorization_RequestDenied" in str(e) or "Insufficient privileges" in str(e):
                logger.warning(
                    f"Insufficient permissions to enumerate Exchange mailboxes: {e.error.message if e.error else str(e)}"
                )
            else:
                logger.error(f"Error enumerating Exchange mailboxes: {e.error.message if e.error else str(e)}")
        except Exception as e:
            logger.error(f"Unexpected error enumerating Exchange mailboxes: {e}")

        return mailboxes
