"""Integration-style tests for the discovery orchestrator."""

from __future__ import annotations

import pytest

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


@pytest.mark.asyncio
async def test_run_discovery_enriches_response(monkeypatch: pytest.MonkeyPatch) -> None:
    request = AzureDiscoveryRequest(tenant_id="tenant-1")
    base_response = AzureDiscoveryResponse(
        tenant_id="tenant-1",
        discovered_subscriptions=["sub-a"],
        nodes=[
            ResourceNode(
                id="vm-a",
                name="vm-a",
                type="Microsoft.Compute/virtualMachines",
                subscription_id="sub-a",
            )
        ],
        relationships=[],
        total_resources=1,
    )

    monkeypatch.setattr(
        "azure_discovery.orchestrator.build_environment_config", lambda env: object()
    )
    monkeypatch.setattr(
        "azure_discovery.orchestrator.get_credential", lambda req, cfg: object()
    )

    async def fake_enumerate(
        req: AzureDiscoveryRequest, credential=None
    ) -> AzureDiscoveryResponse:
        assert req is request
        return base_response

    def fake_render_visualization(
        response: AzureDiscoveryResponse,
        options: VisualizationOptions,
    ) -> VisualizationResponse:
        assert response is base_response
        assert options == request.visualization
        return VisualizationResponse(
            html_path="artifacts/graphs/test.html",
            nodes=len(response.nodes),
            relationships=len(response.relationships),
        )

    emitted: dict[str, AzureDiscoveryResponse] = {}

    def fake_emit_console_summary(response: AzureDiscoveryResponse) -> None:
        emitted["response"] = response

    monkeypatch.setattr(
        "azure_discovery.orchestrator.enumerate_azure_resources", fake_enumerate
    )
    monkeypatch.setattr(
        "azure_discovery.orchestrator.render_visualization", fake_render_visualization
    )
    monkeypatch.setattr(
        "azure_discovery.orchestrator.emit_console_summary", fake_emit_console_summary
    )

    enriched_response = await run_discovery(request)

    assert enriched_response.html_report_path == "artifacts/graphs/test.html"
    assert enriched_response is not base_response
    assert emitted["response"] is enriched_response


@pytest.mark.asyncio
async def test_run_discovery_includes_metrics_when_enabled(monkeypatch: pytest.MonkeyPatch) -> None:
    request = AzureDiscoveryRequest(tenant_id="tenant-1", include_entra=True, include_metrics=True)

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

    entra_nodes = [
        ResourceNode(
            id="graph://user/u1",
            name="User",
            type="Microsoft.Graph/User",
            subscription_id="Tenant",
            tags={"graph_id": "u1"},
        )
    ]
    entra_relationships = [
        ResourceRelationship(
            source_id="graph://user/u1",
            target_id="graph://user/u1",
            relation_type="self",
            weight=1.0,
        )
    ]

    monkeypatch.setattr(
        "azure_discovery.orchestrator.build_environment_config", lambda env: object()
    )
    monkeypatch.setattr(
        "azure_discovery.orchestrator.get_credential", lambda req, cfg: object()
    )

    async def fake_enumerate_azure(req: AzureDiscoveryRequest, credential=None) -> AzureDiscoveryResponse:
        return base_response

    async def fake_enumerate_entra(req: AzureDiscoveryRequest, credential=None):
        return entra_nodes, entra_relationships

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

    monkeypatch.setattr(
        "azure_discovery.orchestrator.enumerate_azure_resources", fake_enumerate_azure
    )
    monkeypatch.setattr(
        "azure_discovery.orchestrator.enumerate_entra_resources", fake_enumerate_entra
    )
    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
    assert out.metrics.merge == DiscoveryMergeMetrics(
        dropped_dangling_relationships=0,
        deduped_relationships=0,
    )
    assert [p.name for p in out.metrics.phases] == [
        "azure.arm",
        "entra.graph",
        "report.render_html",
    ]


@pytest.mark.asyncio
async def test_run_discovery_materializes_missing_endpoints(monkeypatch: pytest.MonkeyPatch) -> None:
    request = AzureDiscoveryRequest(
        tenant_id="tenant-1",
        include_entra=True,
        include_metrics=True,
        materialize_missing_endpoints=True,
    )

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

    # Relationship references endpoints that aren't present as nodes.
    entra_nodes = []
    entra_relationships = [
        ResourceRelationship(
            source_id="graph://user/u1",
            target_id="graph://user/u2",
            relation_type="member",
            weight=1.0,
        )
    ]

    monkeypatch.setattr(
        "azure_discovery.orchestrator.build_environment_config", lambda env: object()
    )
    monkeypatch.setattr(
        "azure_discovery.orchestrator.get_credential", lambda req, cfg: object()
    )

    async def fake_enumerate_azure(req: AzureDiscoveryRequest, credential=None) -> AzureDiscoveryResponse:
        return base_response

    async def fake_enumerate_entra(req: AzureDiscoveryRequest, credential=None):
        return entra_nodes, entra_relationships

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

    monkeypatch.setattr(
        "azure_discovery.orchestrator.enumerate_azure_resources", fake_enumerate_azure
    )
    monkeypatch.setattr(
        "azure_discovery.orchestrator.enumerate_entra_resources", fake_enumerate_entra
    )
    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
    assert out.metrics.merge.dropped_dangling_relationships == 0
    assert len(out.relationships) == 1
    assert len(out.nodes) == 2


@pytest.mark.asyncio
async def test_run_discovery_defender_phase(monkeypatch: pytest.MonkeyPatch) -> None:
    """Run discovery with include_defender_cloud to cover Defender phase and merge."""
    request = AzureDiscoveryRequest(
        tenant_id="tenant-1",
        include_defender_cloud=True,
        include_metrics=True,
    )
    base_response = AzureDiscoveryResponse(
        tenant_id="tenant-1",
        discovered_subscriptions=["sub-a"],
        nodes=[
            ResourceNode(
                id="/subscriptions/sub-a/resourceGroups/rg/providers/Microsoft.Compute/virtualMachines/vm1",
                name="vm1",
                type="Microsoft.Compute/virtualMachines",
                subscription_id="sub-a",
            )
        ],
        relationships=[],
        total_resources=1,
    )

    mock_config = type("Config", (), {"resource_manager": "https://management.azure.com/"})()

    monkeypatch.setattr(
        "azure_discovery.orchestrator.build_environment_config",
        lambda env: mock_config,
    )
    monkeypatch.setattr(
        "azure_discovery.orchestrator.get_credential",
        lambda req, cfg: object(),
    )

    async def fake_enumerate_azure(
        req: AzureDiscoveryRequest, credential=None
    ) -> AzureDiscoveryResponse:
        return base_response

    def fake_render(
        response: AzureDiscoveryResponse, options
    ) -> "VisualizationResponse":
        return VisualizationResponse(
            html_path="out.html",
            nodes=len(response.nodes),
            relationships=len(response.relationships),
        )

    from unittest.mock import AsyncMock

    mock_defender = AsyncMock()
    # Return one alert node so defender phase builds relationships and merges (covers 269-291)
    alert_node = ResourceNode(
        id="/subscriptions/sub-a/providers/Microsoft.Security/alerts/alert1",
        name="alert1",
        type="Microsoft.Security/alerts",
        subscription_id="sub-a",
        properties={"affectedResources": ["/subscriptions/sub-a/resourceGroups/rg/providers/Microsoft.Compute/virtualMachines/vm1"]},
    )
    mock_defender.enumerate_security_alerts = AsyncMock(return_value=[alert_node])
    mock_defender.enumerate_security_assessments = AsyncMock(return_value=[])
    mock_defender.enumerate_secure_scores = AsyncMock(return_value=[])
    mock_defender.close = AsyncMock(return_value=None)

    def fake_defender_enum(credential, base_url):
        return mock_defender

    monkeypatch.setattr(
        "azure_discovery.orchestrator.enumerate_azure_resources",
        fake_enumerate_azure,
    )
    monkeypatch.setattr(
        "azure_discovery.orchestrator.render_visualization",
        fake_render,
    )
    monkeypatch.setattr(
        "azure_discovery.orchestrator.emit_console_summary",
        lambda resp: None,
    )
    monkeypatch.setattr(
        "azure_discovery.orchestrator.DefenderEnumerator",
        fake_defender_enum,
    )

    out = await run_discovery(request)

    assert out.metrics is not None
    phase_names = [p.name for p in out.metrics.phases]
    assert "defender.enumeration" in phase_names
    assert len(out.nodes) >= 1


@pytest.mark.asyncio
async def test_run_discovery_rejects_none_request() -> None:
    """run_discovery raises ValueError when request is None (covers guard)."""
    with pytest.raises(ValueError, match="request cannot be None"):
        await run_discovery(None)  # type: ignore[arg-type]
