"""Unit tests for Purview helper functions."""

import pytest

from azure_discovery.adt_types.models import ResourceNode, ResourceRelationship
from azure_discovery.utils.purview_helpers import (
    build_purview_relationships,
    get_dlp_policies,
    get_retention_policies,
    get_sensitivity_labels,
    get_retention_labels,
    group_purview_by_type,
    filter_policies_by_location_type,
)


@pytest.fixture
def mock_sensitivity_label():
    """Create a mock sensitivity label node."""
    return ResourceNode(
        id="purview://sensitivityLabels/label-123",
        name="Confidential",
        type="Microsoft.Purview/sensitivityLabels",
        subscription_id="Tenant",
        properties={
            "description": "Confidential information",
            "color": "#FF0000",
            "sensitivity": 3,
        },
        tags={
            "labelId": "label-123",
        },
    )


@pytest.fixture
def mock_dlp_policy():
    """Create a mock DLP policy node."""
    return ResourceNode(
        id="purview://dlpPolicies/dlp-456",
        name="GDPR Compliance Policy",
        type="Microsoft.Purview/dlpPolicies",
        subscription_id="Tenant",
        properties={
            "description": "Protect GDPR sensitive data",
            "state": "enabled",
            "mode": "Enforce",
        },
        tags={
            "policyId": "dlp-456",
            "locations": [
                {"location": "https://contoso.sharepoint.com/sites/hr", "locationType": "SharePoint"},
                {"location": "All", "locationType": "Teams"},
            ],
        },
    )


@pytest.fixture
def mock_retention_policy():
    """Create a mock retention policy node."""
    return ResourceNode(
        id="purview://retentionPolicies/retention-789",
        name="7 Year Retention Policy",
        type="Microsoft.Purview/retentionPolicies",
        subscription_id="Tenant",
        properties={
            "description": "Retain records for 7 years",
            "isEnabled": True,
            "retentionDuration": 2555,
        },
        tags={
            "policyId": "retention-789",
            "locations": [
                {"location": "All", "locationType": "OneDrive"},
            ],
        },
    )


@pytest.fixture
def mock_retention_label():
    """Create a mock retention label node."""
    return ResourceNode(
        id="purview://retentionLabels/label-abc",
        name="Permanent Record",
        type="Microsoft.Purview/retentionLabels",
        subscription_id="Tenant",
        properties={
            "description": "Keep permanently",
            "isInUse": True,
        },
        tags={
            "labelId": "label-abc",
        },
    )


@pytest.fixture
def mock_sharepoint_site():
    """Create a mock SharePoint site node."""
    return ResourceNode(
        id="sharepoint://sites/site-123",
        name="HR Site",
        type="Microsoft.SharePoint/sites",
        subscription_id="Tenant",
        properties={
            "webUrl": "https://contoso.sharepoint.com/sites/hr",
        },
        tags={
            "siteId": "site-123",
        },
    )


@pytest.fixture
def mock_team():
    """Create a mock Team node."""
    return ResourceNode(
        id="teams://teams/team-456",
        name="Product Development",
        type="Microsoft.Teams/teams",
        subscription_id="Tenant",
        properties={
            "mail": "productdev@contoso.com",
        },
        tags={
            "teamId": "team-456",
        },
    )


@pytest.fixture
def mock_onedrive_drive():
    """Create a mock OneDrive drive node."""
    return ResourceNode(
        id="onedrive://drives/drive-789",
        name="Alice's OneDrive",
        type="Microsoft.OneDrive/drives",
        subscription_id="Tenant",
        properties={
            "driveType": "business",
            "ownerUpn": "alice@contoso.com",
        },
        tags={
            "driveId": "drive-789",
        },
    )


def test_get_sensitivity_labels(mock_sensitivity_label, mock_dlp_policy):
    """Test filtering nodes to sensitivity labels."""
    nodes = [mock_sensitivity_label, mock_dlp_policy]
    labels = get_sensitivity_labels(nodes)
    
    assert len(labels) == 1
    assert labels[0].type == "Microsoft.Purview/sensitivityLabels"


def test_get_dlp_policies(mock_dlp_policy, mock_retention_policy):
    """Test filtering nodes to DLP policies."""
    nodes = [mock_dlp_policy, mock_retention_policy]
    policies = get_dlp_policies(nodes)
    
    assert len(policies) == 1
    assert policies[0].type == "Microsoft.Purview/dlpPolicies"


def test_get_retention_policies(mock_dlp_policy, mock_retention_policy):
    """Test filtering nodes to retention policies."""
    nodes = [mock_dlp_policy, mock_retention_policy]
    policies = get_retention_policies(nodes)
    
    assert len(policies) == 1
    assert policies[0].type == "Microsoft.Purview/retentionPolicies"


def test_get_retention_labels(mock_retention_label, mock_sensitivity_label):
    """Test filtering nodes to retention labels."""
    nodes = [mock_retention_label, mock_sensitivity_label]
    labels = get_retention_labels(nodes)
    
    assert len(labels) == 1
    assert labels[0].type == "Microsoft.Purview/retentionLabels"


def test_group_purview_by_type(
    mock_sensitivity_label,
    mock_dlp_policy,
    mock_retention_policy,
    mock_retention_label,
):
    """Test grouping Purview nodes by type."""
    nodes = [
        mock_sensitivity_label,
        mock_dlp_policy,
        mock_retention_policy,
        mock_retention_label,
    ]
    grouped = group_purview_by_type(nodes)
    
    assert "Microsoft.Purview/sensitivityLabels" in grouped
    assert "Microsoft.Purview/dlpPolicies" in grouped
    assert "Microsoft.Purview/retentionPolicies" in grouped
    assert "Microsoft.Purview/retentionLabels" in grouped
    
    assert len(grouped["Microsoft.Purview/sensitivityLabels"]) == 1
    assert len(grouped["Microsoft.Purview/dlpPolicies"]) == 1
    assert len(grouped["Microsoft.Purview/retentionPolicies"]) == 1
    assert len(grouped["Microsoft.Purview/retentionLabels"]) == 1


def test_filter_policies_by_location_type_sharepoint(mock_dlp_policy):
    """Test filtering policies by SharePoint location type."""
    policies = [mock_dlp_policy]
    filtered = filter_policies_by_location_type(policies, "SharePoint")
    
    assert len(filtered) == 1
    assert filtered[0].id == "purview://dlpPolicies/dlp-456"


def test_filter_policies_by_location_type_teams(mock_dlp_policy):
    """Test filtering policies by Teams location type."""
    policies = [mock_dlp_policy]
    filtered = filter_policies_by_location_type(policies, "Teams")
    
    assert len(filtered) == 1
    assert filtered[0].id == "purview://dlpPolicies/dlp-456"


def test_filter_policies_by_location_type_onedrive(mock_retention_policy):
    """Test filtering policies by OneDrive location type."""
    policies = [mock_retention_policy]
    filtered = filter_policies_by_location_type(policies, "OneDrive")
    
    assert len(filtered) == 1
    assert filtered[0].id == "purview://retentionPolicies/retention-789"


def test_filter_policies_by_location_type_no_match(mock_dlp_policy):
    """Test filtering policies with no matching location type."""
    policies = [mock_dlp_policy]
    filtered = filter_policies_by_location_type(policies, "Exchange")
    
    # DLP policy has SharePoint and Teams, not Exchange
    assert len(filtered) == 0


def test_build_dlp_sharepoint_relationships(mock_dlp_policy, mock_sharepoint_site):
    """Test building DLP policy -> SharePoint site relationships."""
    purview_nodes = [mock_dlp_policy]
    m365_nodes = [mock_sharepoint_site]
    
    relationships, _ = build_purview_relationships(
        purview_nodes=purview_nodes,
        m365_nodes=m365_nodes,
    )

    assert len(relationships) == 1
    assert relationships[0].relation_type == "applies_to"
    assert relationships[0].source_id == "purview://dlpPolicies/dlp-456"
    assert relationships[0].target_id == "sharepoint://sites/site-123"


def test_build_dlp_teams_relationships(mock_dlp_policy, mock_team):
    """Test building DLP policy -> Teams relationships."""
    purview_nodes = [mock_dlp_policy]
    m365_nodes = [mock_team]
    
    relationships, _ = build_purview_relationships(
        purview_nodes=purview_nodes,
        m365_nodes=m365_nodes,
    )

    # DLP policy has 'All' Teams, so should apply to the team
    assert len(relationships) == 1
    assert relationships[0].relation_type == "applies_to"
    assert relationships[0].source_id == "purview://dlpPolicies/dlp-456"
    assert relationships[0].target_id == "teams://teams/team-456"


def test_build_retention_onedrive_relationships(mock_retention_policy, mock_onedrive_drive):
    """Test building retention policy -> OneDrive relationships."""
    purview_nodes = [mock_retention_policy]
    m365_nodes = [mock_onedrive_drive]
    
    relationships, _ = build_purview_relationships(
        purview_nodes=purview_nodes,
        m365_nodes=m365_nodes,
    )

    # Retention policy has 'All' OneDrive, so should apply to the drive
    assert len(relationships) == 1
    assert relationships[0].relation_type == "retains"
    assert relationships[0].source_id == "purview://retentionPolicies/retention-789"
    assert relationships[0].target_id == "onedrive://drives/drive-789"


def test_build_relationships_all_location_types(
    mock_dlp_policy,
    mock_retention_policy,
    mock_sharepoint_site,
    mock_team,
    mock_onedrive_drive,
):
    """Test building relationships for all Purview policy and M365 workload types."""
    purview_nodes = [mock_dlp_policy, mock_retention_policy]
    m365_nodes = [mock_sharepoint_site, mock_team, mock_onedrive_drive]
    
    relationships, _ = build_purview_relationships(
        purview_nodes=purview_nodes,
        m365_nodes=m365_nodes,
    )

    # DLP applies to SharePoint (1) + Teams (1) = 2
    # Retention applies to OneDrive (1) = 1
    # Total = 3 relationships
    assert len(relationships) >= 3

    # Verify relationship types
    rel_types = {r.relation_type for r in relationships}
    assert "applies_to" in rel_types  # DLP policies
    assert "retains" in rel_types     # Retention policies


def test_build_relationships_specific_url_matching(mock_sharepoint_site):
    """Test DLP policy with specific URL matches only that site."""
    # Create DLP policy with specific URL (matches mock site)
    dlp_specific = ResourceNode(
        id="purview://dlpPolicies/dlp-specific",
        name="Specific DLP Policy",
        type="Microsoft.Purview/dlpPolicies",
        subscription_id="Tenant",
        properties={},
        tags={
            "policyId": "dlp-specific",
            "locations": [
                {"location": "https://contoso.sharepoint.com/sites/hr", "locationType": "SharePoint"},
            ],
        },
    )
    
    # Create site that doesn't match
    other_site = ResourceNode(
        id="sharepoint://sites/site-other",
        name="Engineering Site",
        type="Microsoft.SharePoint/sites",
        subscription_id="Tenant",
        properties={
            "webUrl": "https://contoso.sharepoint.com/sites/engineering",
        },
        tags={"siteId": "site-other"},
    )
    
    purview_nodes = [dlp_specific]
    m365_nodes = [mock_sharepoint_site, other_site]
    
    relationships, _ = build_purview_relationships(
        purview_nodes=purview_nodes,
        m365_nodes=m365_nodes,
    )

    # Should only match HR site
    assert len(relationships) == 1
    assert relationships[0].target_id == "sharepoint://sites/site-123"


def test_build_relationships_all_location_matches_all_resources(mock_team):
    """Test policy with 'All' location matches all resources of that type."""
    # Create policy with 'All' Teams location
    dlp_all_teams = ResourceNode(
        id="purview://dlpPolicies/dlp-all-teams",
        name="All Teams DLP Policy",
        type="Microsoft.Purview/dlpPolicies",
        subscription_id="Tenant",
        properties={},
        tags={
            "policyId": "dlp-all-teams",
            "locations": [
                {"location": "All", "locationType": "Teams"},
            ],
        },
    )
    
    # Create multiple teams
    team2 = ResourceNode(
        id="teams://teams/team-999",
        name="Engineering Team",
        type="Microsoft.Teams/teams",
        subscription_id="Tenant",
        properties={},
        tags={"teamId": "team-999"},
    )
    
    purview_nodes = [dlp_all_teams]
    m365_nodes = [mock_team, team2]
    
    relationships, _ = build_purview_relationships(
        purview_nodes=purview_nodes,
        m365_nodes=m365_nodes,
    )

    # Should match both teams
    assert len(relationships) == 2
    target_ids = {r.target_id for r in relationships}
    assert "teams://teams/team-456" in target_ids
    assert "teams://teams/team-999" in target_ids


def test_build_relationships_empty_purview_nodes():
    """Test building relationships with empty Purview nodes."""
    relationships, _ = build_purview_relationships(
        purview_nodes=[],
        m365_nodes=[],
    )

    assert len(relationships) == 0


def test_build_relationships_empty_m365_nodes(mock_dlp_policy):
    """Test building relationships with empty M365 nodes."""
    relationships, _ = build_purview_relationships(
        purview_nodes=[mock_dlp_policy],
        m365_nodes=[],
    )

    # No M365 resources to apply to
    assert len(relationships) == 0


def test_build_relationships_no_location_tags():
    """Test handling Purview policies without location tags."""
    # Create policy without locations tag
    policy_no_locations = ResourceNode(
        id="purview://dlpPolicies/dlp-no-locations",
        name="Policy Without Locations",
        type="Microsoft.Purview/dlpPolicies",
        subscription_id="Tenant",
        properties={},
        tags={"policyId": "dlp-no-locations"},  # No locations
    )
    
    purview_nodes = [policy_no_locations]
    m365_nodes = []
    
    relationships, _ = build_purview_relationships(
        purview_nodes=purview_nodes,
        m365_nodes=m365_nodes,
    )

    # Should not create relationships for policies without locations
    assert len(relationships) == 0


def test_build_relationships_multiple_policies_same_resource(
    mock_dlp_policy,
    mock_retention_policy,
    mock_sharepoint_site,
):
    """Test multiple policies applying to the same resource."""
    # Create DLP policy that also applies to SharePoint
    dlp_sharepoint = ResourceNode(
        id="purview://dlpPolicies/dlp-sp",
        name="SharePoint DLP Policy",
        type="Microsoft.Purview/dlpPolicies",
        subscription_id="Tenant",
        properties={},
        tags={
            "policyId": "dlp-sp",
            "locations": [
                {"location": "All", "locationType": "SharePoint"},
            ],
        },
    )
    
    # Create retention policy that applies to SharePoint
    retention_sharepoint = ResourceNode(
        id="purview://retentionPolicies/retention-sp",
        name="SharePoint Retention Policy",
        type="Microsoft.Purview/retentionPolicies",
        subscription_id="Tenant",
        properties={},
        tags={
            "policyId": "retention-sp",
            "locations": [
                {"location": "All", "locationType": "SharePoint"},
            ],
        },
    )
    
    purview_nodes = [dlp_sharepoint, retention_sharepoint]
    m365_nodes = [mock_sharepoint_site]
    
    relationships, _ = build_purview_relationships(
        purview_nodes=purview_nodes,
        m365_nodes=m365_nodes,
    )

    # Both policies should apply to the site
    assert len(relationships) == 2
    
    # One applies_to (DLP), one retains (Retention)
    rel_types = [r.relation_type for r in relationships]
    assert "applies_to" in rel_types
    assert "retains" in rel_types


def test_filtering_helpers_empty_input():
    """Test filtering helpers with empty input."""
    assert len(get_sensitivity_labels([])) == 0
    assert len(get_dlp_policies([])) == 0
    assert len(get_retention_policies([])) == 0
    assert len(get_retention_labels([])) == 0


def test_group_purview_by_type_empty_input():
    """Test grouping with empty input."""
    grouped = group_purview_by_type([])
    assert len(grouped) == 0


def test_filter_policies_by_location_type_empty_input():
    """Test location type filtering with empty input."""
    filtered = filter_policies_by_location_type([], "SharePoint")
    assert len(filtered) == 0
