"""Unit tests for M365 enumerator."""

from unittest.mock import AsyncMock, MagicMock

import pytest
from msgraph_core import APIVersion

from azure_discovery.enumerators.m365 import M365Enumerator
from azure_discovery.adt_types.models import M365Config, ResourceNode


@pytest.fixture
def mock_credential():
    """Mock Azure credential."""
    return AsyncMock()


@pytest.fixture
def m365_config():
    """Create M365Config instance."""
    return M365Config(
        include_sharepoint_sites=True,
        include_teams=True,
        include_onedrive=True,
        include_exchange_mailboxes=False,
        sharepoint_max_sites=1000,
        teams_max_teams=1000,
        onedrive_max_drives=1000,
        teams_filter_archived=True,
    )


@pytest.fixture
def m365_enumerator(mock_credential, m365_config):
    """Create M365Enumerator instance."""
    return M365Enumerator(
        tenant_id="tenant-123",
        credential=mock_credential,
        config=m365_config,
    )


@pytest.fixture
def mock_sharepoint_site():
    """Create a mock SharePoint site."""
    site = MagicMock()
    site.id = "contoso.sharepoint.com,site-guid-123,web-guid-456"
    site.web_url = "https://contoso.sharepoint.com/sites/engineering"
    site.display_name = "Engineering Team Site"
    site.description = "Team site for engineering projects"
    site.created_date_time = "2024-01-15T10:30:00Z"
    site.last_modified_date_time = "2024-02-01T15:45:00Z"
    site.additional_data = {}
    return site


@pytest.fixture
def mock_team():
    """Create a mock Microsoft Team."""
    team = MagicMock()
    team.id = "team-guid-789"
    team.display_name = "Product Development"
    team.description = "Product development team collaboration"
    team.mail = "productdev@contoso.com"
    team.visibility = "Private"
    team.is_archived = False
    team.web_url = "https://teams.microsoft.com/l/team/team-guid-789"
    team.additional_data = {"resourceProvisioningOptions": ["Team"]}
    return team


@pytest.fixture
def mock_onedrive_drive():
    """Create a mock OneDrive drive."""
    drive = MagicMock()
    drive.id = "drive-guid-abc"
    drive.drive_type = "business"
    drive.name = "OneDrive - Contoso"
    drive.web_url = "https://contoso-my.sharepoint.com/personal/alice_contoso_com"
    drive.owner = MagicMock()
    drive.owner.user = MagicMock()
    drive.owner.user.id = "user-guid-alice"
    drive.owner.user.display_name = "Alice Anderson"
    drive.owner.user.email = "alice@contoso.com"
    drive.owner.user.user_principal_name = "alice@contoso.com"
    drive.owner.user.additional_data = {}
    drive.quota = MagicMock()
    drive.quota.total = 1099511627776  # 1 TB
    drive.quota.used = 549755813888    # 512 GB
    drive.quota.remaining = 549755813888
    drive.additional_data = {}
    return drive


@pytest.fixture
def mock_mailbox_user():
    """Create a mock user with mailbox."""
    user = MagicMock()
    user.id = "user-guid-bob"
    user.user_principal_name = "bob@contoso.com"
    user.mail = "bob@contoso.com"
    user.display_name = "Bob Brown"
    user.additional_data = {}
    return user


@pytest.mark.asyncio
async def test_enumerate_sharepoint_sites_success(m365_enumerator, mock_sharepoint_site):
    """Test successful enumeration of SharePoint sites."""
    mock_response = MagicMock()
    mock_response.value = [mock_sharepoint_site]
    mock_client = MagicMock()
    mock_client.sites.get = AsyncMock(return_value=mock_response)
    m365_enumerator.graph_client = mock_client

    sites = await m365_enumerator.enumerate_sharepoint_sites()

    assert len(sites) == 1
    site_node = sites[0]
    assert isinstance(site_node, ResourceNode)
    assert site_node.type == "Microsoft.SharePoint/sites"
    assert site_node.name == "Engineering Team Site"
    assert site_node.properties["web_url"] == "https://contoso.sharepoint.com/sites/engineering"
    assert site_node.properties["description"] == "Team site for engineering projects"
    assert "siteId" in site_node.tags


@pytest.mark.asyncio
async def test_enumerate_sharepoint_sites_with_filter(m365_enumerator, mock_sharepoint_site):
    """Test SharePoint site enumeration with URL filter."""
    m365_enumerator.config.sharepoint_site_filter = [
        "contoso.sharepoint.com/sites/engineering"
    ]

    filtered_site = MagicMock()
    filtered_site.id = "contoso.sharepoint.com,hr-guid,web-guid"
    filtered_site.web_url = "https://contoso.sharepoint.com/sites/hr"
    filtered_site.display_name = "HR Site"
    filtered_site.name = None
    filtered_site.description = None
    filtered_site.created_date_time = None
    filtered_site.last_modified_date_time = None
    filtered_site.additional_data = {}

    mock_response = MagicMock()
    mock_response.value = [mock_sharepoint_site, filtered_site]
    mock_client = MagicMock()
    mock_client.sites.get = AsyncMock(return_value=mock_response)
    m365_enumerator.graph_client = mock_client

    sites = await m365_enumerator.enumerate_sharepoint_sites()

    assert len(sites) == 1
    assert sites[0].properties["web_url"] == "https://contoso.sharepoint.com/sites/engineering"


@pytest.mark.asyncio
async def test_enumerate_sharepoint_sites_max_limit(m365_enumerator, mock_sharepoint_site):
    """Test SharePoint site enumeration respects max limit."""
    m365_enumerator.config.sharepoint_max_sites = 2

    mock_response = MagicMock()
    mock_response.value = [mock_sharepoint_site] * 5
    mock_client = MagicMock()
    mock_client.sites.get = AsyncMock(return_value=mock_response)
    m365_enumerator.graph_client = mock_client

    sites = await m365_enumerator.enumerate_sharepoint_sites()

    assert len(sites) == 2


@pytest.mark.asyncio
async def test_enumerate_teams_success(m365_enumerator, mock_team):
    """Test successful enumeration of Microsoft Teams."""
    mock_response = MagicMock()
    mock_response.value = [mock_team]
    mock_client = MagicMock()
    mock_client.groups.get = AsyncMock(return_value=mock_response)
    m365_enumerator.graph_client = mock_client

    teams = await m365_enumerator.enumerate_teams()

    assert len(teams) == 1
    team_node = teams[0]
    assert isinstance(team_node, ResourceNode)
    assert team_node.type == "Microsoft.Teams/teams"
    assert team_node.name == "Product Development"
    assert team_node.properties["mail"] == "productdev@contoso.com"
    assert team_node.properties["visibility"] == "Private"
    assert team_node.properties["is_archived"] is False


@pytest.mark.asyncio
async def test_enumerate_teams_filter_archived(m365_enumerator, mock_team):
    """Test Teams enumeration filters out archived teams."""
    # Create archived team
    archived_team = MagicMock()
    archived_team.id = "archived-team-123"
    archived_team.display_name = "Archived Team"
    archived_team.is_archived = True
    archived_team.additional_data = {"resourceProvisioningOptions": ["Team"]}

    mock_response = MagicMock()
    mock_response.value = [mock_team, archived_team]
    mock_client = MagicMock()
    mock_client.groups.get = AsyncMock(return_value=mock_response)
    m365_enumerator.graph_client = mock_client

    teams = await m365_enumerator.enumerate_teams()

    assert len(teams) == 1
    assert teams[0].properties["is_archived"] is False


@pytest.mark.asyncio
async def test_enumerate_teams_include_archived(m365_enumerator, mock_team):
    """Test Teams enumeration includes archived teams when filter disabled."""
    # Disable archived filter
    m365_enumerator.config.teams_filter_archived = False

    # Create archived team
    archived_team = MagicMock()
    archived_team.id = "archived-team-123"
    archived_team.display_name = "Archived Team"
    archived_team.is_archived = True
    archived_team.mail = "archived@contoso.com"
    archived_team.visibility = "Public"
    archived_team.web_url = "https://teams.microsoft.com/l/team/archived-team-123"
    archived_team.additional_data = {"resourceProvisioningOptions": ["Team"]}

    mock_response = MagicMock()
    mock_response.value = [mock_team, archived_team]
    mock_client = MagicMock()
    mock_client.groups.get = AsyncMock(return_value=mock_response)
    m365_enumerator.graph_client = mock_client

    teams = await m365_enumerator.enumerate_teams()

    assert len(teams) == 2


@pytest.mark.asyncio
async def test_enumerate_onedrive_drives_success(m365_enumerator, mock_onedrive_drive):
    """Test successful enumeration of OneDrive drives."""
    mock_onedrive_drive.created_date_time = None
    mock_onedrive_drive.last_modified_date_time = None
    mock_response = MagicMock()
    mock_response.value = [mock_onedrive_drive]
    mock_client = MagicMock()
    mock_client.drives.get = AsyncMock(return_value=mock_response)
    m365_enumerator.graph_client = mock_client

    drives = await m365_enumerator.enumerate_onedrive_drives()

    assert len(drives) == 1
    drive_node = drives[0]
    assert isinstance(drive_node, ResourceNode)
    assert drive_node.type == "Microsoft.OneDrive/drives"
    assert drive_node.name == "OneDrive - Contoso"
    assert drive_node.properties["drive_type"] == "business"
    assert drive_node.properties["owner_upn"] == "alice@contoso.com"
    assert drive_node.properties["quota_total"] == 1099511627776
    assert drive_node.properties["quota_used"] == 549755813888


@pytest.mark.asyncio
async def test_enumerate_onedrive_drives_with_user_filter(m365_enumerator, mock_onedrive_drive):
    """Test OneDrive enumeration with user filter."""
    # Configure user filter
    m365_enumerator.config.onedrive_user_filter = ["alice@contoso.com"]

    # Create drive that doesn't match filter
    unfiltered_drive = MagicMock()
    unfiltered_drive.id = "drive-guid-xyz"
    unfiltered_drive.name = "Bob's OneDrive"
    unfiltered_drive.owner = MagicMock()
    unfiltered_drive.owner.user = MagicMock()
    unfiltered_drive.owner.user.user_principal_name = "bob@contoso.com"
    unfiltered_drive.owner.user.additional_data = {}
    unfiltered_drive.drive_type = "business"
    unfiltered_drive.web_url = None
    unfiltered_drive.quota = None
    unfiltered_drive.created_date_time = None
    unfiltered_drive.last_modified_date_time = None

    mock_response = MagicMock()
    mock_response.value = [mock_onedrive_drive, unfiltered_drive]
    mock_client = MagicMock()
    mock_client.drives.get = AsyncMock(return_value=mock_response)
    m365_enumerator.graph_client = mock_client

    drives = await m365_enumerator.enumerate_onedrive_drives()

    assert len(drives) == 1
    assert drives[0].properties["owner_upn"] == "alice@contoso.com"


@pytest.mark.asyncio
async def test_enumerate_exchange_mailboxes_success(m365_enumerator, mock_mailbox_user):
    """Test successful enumeration of Exchange mailboxes."""
    m365_enumerator.config.include_exchange_mailboxes = True
    mock_mailbox_user.display_name = "Bob Brown"

    mock_response = MagicMock()
    mock_response.value = [mock_mailbox_user]
    mock_client = MagicMock()
    mock_client.users.get = AsyncMock(return_value=mock_response)
    m365_enumerator.graph_client = mock_client

    mailboxes = await m365_enumerator.enumerate_exchange_mailboxes()

    assert len(mailboxes) == 1
    mailbox_node = mailboxes[0]
    assert isinstance(mailbox_node, ResourceNode)
    assert mailbox_node.type == "Microsoft.Exchange/mailboxes"
    assert mailbox_node.name == "Bob Brown"
    assert mailbox_node.properties["user_principal_name"] == "bob@contoso.com"
    assert mailbox_node.properties["mail"] == "bob@contoso.com"


@pytest.mark.asyncio
async def test_enumerate_exchange_mailboxes_with_user_filter(m365_enumerator, mock_mailbox_user):
    """Test Exchange mailbox enumeration with user filter."""
    # Enable Exchange with user filter
    m365_enumerator.config.include_exchange_mailboxes = True
    m365_enumerator.config.exchange_user_filter = ["bob@contoso.com"]

    # Create user that doesn't match filter
    unfiltered_user = MagicMock()
    unfiltered_user.id = "user-guid-charlie"
    unfiltered_user.user_principal_name = "charlie@contoso.com"
    unfiltered_user.mail = "charlie@contoso.com"
    unfiltered_user.display_name = "Charlie Chen"
    unfiltered_user.additional_data = {}

    mock_response = MagicMock()
    mock_response.value = [mock_mailbox_user, unfiltered_user]
    mock_client = MagicMock()
    mock_client.users.get = AsyncMock(return_value=mock_response)
    m365_enumerator.graph_client = mock_client

    mailboxes = await m365_enumerator.enumerate_exchange_mailboxes()

    assert len(mailboxes) == 1
    assert mailboxes[0].properties["user_principal_name"] == "bob@contoso.com"


@pytest.mark.asyncio
async def test_enumerate_empty_response(m365_enumerator):
    """Test enumeration with empty API response."""
    mock_response = MagicMock()
    mock_response.value = []
    mock_client = MagicMock()
    mock_client.sites.get = AsyncMock(return_value=mock_response)
    m365_enumerator.graph_client = mock_client

    sites = await m365_enumerator.enumerate_sharepoint_sites()

    assert len(sites) == 0


@pytest.mark.asyncio
async def test_enumerate_sites_api_error(m365_enumerator):
    """Test SharePoint enumeration handles API errors gracefully."""
    mock_client = MagicMock()
    mock_client.sites.get = AsyncMock(side_effect=Exception("Graph API error"))
    m365_enumerator.graph_client = mock_client

    sites = await m365_enumerator.enumerate_sharepoint_sites()
    assert len(sites) == 0


@pytest.mark.asyncio
async def test_enumerate_teams_api_error(m365_enumerator):
    """Test Teams enumeration handles API errors gracefully."""
    mock_client = MagicMock()
    mock_client.groups.get = AsyncMock(side_effect=Exception("Graph API error"))
    m365_enumerator.graph_client = mock_client

    teams = await m365_enumerator.enumerate_teams()
    assert len(teams) == 0


@pytest.mark.asyncio
async def test_m365_config_defaults():
    """Test M365Config default values."""
    config = M365Config()
    
    assert config.include_sharepoint_sites is True
    assert config.include_teams is True
    assert config.include_onedrive is True
    assert config.include_exchange_mailboxes is False
    assert config.sharepoint_max_sites == 1000
    assert config.teams_max_teams == 1000
    assert config.onedrive_max_drives == 1000
    assert config.teams_filter_archived is True
    assert config.include_site_owners is True
    assert config.include_team_members is True
