"""Integration tests for RBAC enumeration in the orchestrator."""

from __future__ import annotations

import pytest

from azure_discovery.adt_types import (
    AzureDiscoveryRequest,
    AzureDiscoveryResponse,
    ResourceNode,
    ResourceRelationship,
    VisualizationOptions,
    VisualizationResponse,
)
from azure_discovery.orchestrator import run_discovery


@pytest.mark.asyncio
async def test_run_discovery_includes_rbac_phase(monkeypatch: pytest.MonkeyPatch) -> None:
    """RBAC enumeration phase is included when include_rbac_assignments is True."""
    request = AzureDiscoveryRequest(
        tenant_id="tenant-1",
        include_rbac_assignments=True,
        include_metrics=True,
    )

    base_response = AzureDiscoveryResponse(
        tenant_id="tenant-1",
        discovered_subscriptions=["sub-a"],
        nodes=[],
        relationships=[],
        total_resources=0,
    )

    rbac_assignment = ResourceNode(
        id="/subscriptions/sub-a/providers/Microsoft.Authorization/roleAssignments/ra-1",
        name="ra-1",
        type="Microsoft.Authorization/roleAssignments",
        subscription_id="sub-a",
        properties={
            "principalId": "user-1",
            "scope": "/subscriptions/sub-a",
            "roleDefinitionId": "/providers/Microsoft.Authorization/roleDefinitions/rd-1",
            "roleDefinitionName": "Reader",
        },
    )

    monkeypatch.setattr(
        "azure_discovery.orchestrator.build_environment_config",
        lambda env: type("Cfg", (), {"resource_manager": "https://management.azure.com/"})(),
    )
    monkeypatch.setattr(
        "azure_discovery.orchestrator.get_credential", lambda req, cfg: object()
    )

    async def fake_enumerate_azure(req, credential=None):
        return base_response

    monkeypatch.setattr(
        "azure_discovery.orchestrator.enumerate_azure_resources", fake_enumerate_azure
    )

    # Mock the RBAC enumerator
    class FakeRBACEnumerator:
        def __init__(self, credential, base_url):
            pass

        async def enumerate_role_assignments(self, subscription_ids, scope_filter=None):
            return [rbac_assignment]

        async def enumerate_role_definitions(self, subscription_ids):
            return []

    monkeypatch.setattr(
        "azure_discovery.orchestrator.RBACEnumerator", FakeRBACEnumerator
    )

    def fake_render_visualization(response, options):
        return VisualizationResponse(html_path="x.html", nodes=0, relationships=0)

    monkeypatch.setattr(
        "azure_discovery.orchestrator.render_visualization", fake_render_visualization
    )
    monkeypatch.setattr(
        "azure_discovery.orchestrator.emit_console_summary", lambda resp: None
    )

    out = await run_discovery(request)

    assert out.metrics is not None
    phase_names = [p.name for p in out.metrics.phases]
    assert "rbac.enumeration" in phase_names

    # RBAC assignment node should be in the output
    assert any(n.type == "Microsoft.Authorization/roleAssignments" for n in out.nodes)


@pytest.mark.asyncio
async def test_run_discovery_skips_rbac_when_disabled(monkeypatch: pytest.MonkeyPatch) -> None:
    """RBAC phase is skipped when neither RBAC flag is set."""
    request = AzureDiscoveryRequest(
        tenant_id="tenant-1",
        include_metrics=True,
    )

    base_response = AzureDiscoveryResponse(
        tenant_id="tenant-1",
        discovered_subscriptions=["sub-a"],
        nodes=[],
        relationships=[],
        total_resources=0,
    )

    monkeypatch.setattr(
        "azure_discovery.orchestrator.build_environment_config",
        lambda env: type("Cfg", (), {"resource_manager": "https://management.azure.com/"})(),
    )
    monkeypatch.setattr(
        "azure_discovery.orchestrator.get_credential", lambda req, cfg: object()
    )

    async def fake_enumerate_azure(req, credential=None):
        return base_response

    monkeypatch.setattr(
        "azure_discovery.orchestrator.enumerate_azure_resources", fake_enumerate_azure
    )

    def fake_render_visualization(response, options):
        return VisualizationResponse(html_path="x.html", nodes=0, relationships=0)

    monkeypatch.setattr(
        "azure_discovery.orchestrator.render_visualization", fake_render_visualization
    )
    monkeypatch.setattr(
        "azure_discovery.orchestrator.emit_console_summary", lambda resp: None
    )

    out = await run_discovery(request)

    assert out.metrics is not None
    phase_names = [p.name for p in out.metrics.phases]
    assert "rbac.enumeration" not in phase_names


@pytest.mark.asyncio
async def test_rbac_with_entra_builds_relationships(monkeypatch: pytest.MonkeyPatch) -> None:
    """RBAC + Entra builds principal-to-assignment relationships."""
    request = AzureDiscoveryRequest(
        tenant_id="tenant-1",
        include_entra=True,
        include_rbac_assignments=True,
        include_relationships=True,
        include_metrics=True,
    )

    entra_user = ResourceNode(
        id="graph://users/user-obj-1",
        name="Test User",
        type="Microsoft.Graph/User",
        subscription_id="Tenant",
        tags={"graph_id": "user-obj-1"},
    )

    base_response = AzureDiscoveryResponse(
        tenant_id="tenant-1",
        discovered_subscriptions=["sub-a"],
        nodes=[entra_user],
        relationships=[],
        total_resources=1,
    )

    rbac_assignment = ResourceNode(
        id="/subscriptions/sub-a/providers/Microsoft.Authorization/roleAssignments/ra-1",
        name="ra-1",
        type="Microsoft.Authorization/roleAssignments",
        subscription_id="sub-a",
        properties={
            "principalId": "user-obj-1",
            "scope": "/subscriptions/sub-a",
            "roleDefinitionId": "/providers/Microsoft.Authorization/roleDefinitions/rd-1",
            "roleDefinitionName": "Reader",
        },
    )

    monkeypatch.setattr(
        "azure_discovery.orchestrator.build_environment_config",
        lambda env: type("Cfg", (), {"resource_manager": "https://management.azure.com/"})(),
    )
    monkeypatch.setattr(
        "azure_discovery.orchestrator.get_credential", lambda req, cfg: object()
    )

    async def fake_enumerate_azure(req, credential=None):
        return base_response

    async def fake_enumerate_entra(req, credential=None):
        return [entra_user], []

    monkeypatch.setattr(
        "azure_discovery.orchestrator.enumerate_azure_resources", fake_enumerate_azure
    )
    monkeypatch.setattr(
        "azure_discovery.orchestrator.enumerate_entra_resources", fake_enumerate_entra
    )

    class FakeRBACEnumerator:
        def __init__(self, credential, base_url):
            pass

        async def enumerate_role_assignments(self, subscription_ids, scope_filter=None):
            return [rbac_assignment]

        async def enumerate_role_definitions(self, subscription_ids):
            return []

    monkeypatch.setattr(
        "azure_discovery.orchestrator.RBACEnumerator", FakeRBACEnumerator
    )

    def fake_render_visualization(response, options):
        return VisualizationResponse(html_path="x.html", nodes=0, relationships=0)

    monkeypatch.setattr(
        "azure_discovery.orchestrator.render_visualization", fake_render_visualization
    )
    monkeypatch.setattr(
        "azure_discovery.orchestrator.emit_console_summary", lambda resp: None
    )

    out = await run_discovery(request)

    # Should have the entra user and the RBAC assignment
    assert any(n.type == "Microsoft.Graph/User" for n in out.nodes)
    assert any(n.type == "Microsoft.Authorization/roleAssignments" for n in out.nodes)

    # Should have relationship edges from RBAC
    rbac_rels = [r for r in out.relationships if r.relation_type == "has_role_assignment"]
    assert len(rbac_rels) >= 1
    assert rbac_rels[0].source_id == "graph://users/user-obj-1"
