"""Tests for API hardening: CORS, auth dependency, versioned routes."""

from __future__ import annotations

from unittest.mock import AsyncMock, patch

import pytest
from fastapi.testclient import TestClient


@pytest.fixture
def api_client(monkeypatch):
    """Create a test client with auth=none (default)."""
    # Ensure no API config file is loaded
    monkeypatch.delenv("AZURE_DISCOVERY_API_CONFIG", raising=False)
    monkeypatch.delenv("AZURE_DISCOVERY_CONFIG", raising=False)

    # Force re-import to get fresh app with defaults
    import importlib
    import azure_discovery.api

    importlib.reload(azure_discovery.api)
    return TestClient(azure_discovery.api.app)


class TestCORSHeaders:
    """Test CORS middleware is applied."""

    def test_cors_headers_present(self, api_client):
        """Verify CORS headers are returned for cross-origin requests."""
        response = api_client.get(
            "/healthz",
            headers={"Origin": "http://localhost:3000"},
        )
        assert response.status_code == 200
        assert "access-control-allow-origin" in response.headers


class TestHealthEndpoints:
    """Test health check endpoints."""

    def test_root_health(self, api_client):
        """Root healthz returns ok."""
        response = api_client.get("/healthz")
        assert response.status_code == 200
        assert response.json()["status"] == "ok"

    def test_v1_health(self, api_client):
        """v1 healthz returns ok with version."""
        response = api_client.get("/v1/healthz")
        assert response.status_code == 200
        data = response.json()
        assert data["status"] == "ok"
        assert data["version"] == "v1"


class TestAPIVersioning:
    """Test that versioned routes are available."""

    def test_v1_discover_route_exists(self, api_client):
        """v1/discover route is registered."""
        # Sending an invalid body should return 422, not 404
        response = api_client.post("/v1/discover", json={})
        assert response.status_code == 422  # validation error, not 404

    def test_root_discover_route_exists(self, api_client):
        """Root /discover route is still available (backward compat)."""
        response = api_client.post("/discover", json={})
        assert response.status_code == 422  # validation error, not 404

    def test_v1_discover_success_returns_response(self, api_client):
        """POST /v1/discover with valid body runs discovery and returns response."""
        from azure_discovery.adt_types import AzureDiscoveryResponse

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

        with patch(
            "azure_discovery.api.run_discovery",
            new_callable=AsyncMock,
            return_value=mock_response,
        ):
            response = api_client.post(
                "/v1/discover",
                json={"tenant_id": "tenant-1"},
            )
        assert response.status_code == 200
        data = response.json()
        assert data["tenant_id"] == "tenant-1"
        assert "discovered_subscriptions" in data

    def test_root_discover_success_returns_response(self, api_client):
        """POST /discover with valid body runs discovery and returns response."""
        from azure_discovery.adt_types import AzureDiscoveryResponse

        mock_response = AzureDiscoveryResponse(
            tenant_id="tenant-2",
            discovered_subscriptions=[],
            nodes=[],
            relationships=[],
            total_resources=0,
        )

        with patch(
            "azure_discovery.api.run_discovery",
            new_callable=AsyncMock,
            return_value=mock_response,
        ):
            response = api_client.post(
                "/discover",
                json={"tenant_id": "tenant-2"},
            )
        assert response.status_code == 200
        assert response.json()["tenant_id"] == "tenant-2"


class TestVisualizationEndpoint:
    """Test /visuals/{file_name} endpoint."""

    def test_visualization_missing_file_returns_404(self, api_client):
        """GET /visuals/nonexistent.html returns 404 when file is not under artifacts/graphs."""
        response = api_client.get("/visuals/nonexistent.html")
        assert response.status_code == 404
        assert "not found" in response.json()["detail"].lower()


class TestScaleControlConfig:
    """Test ScaleControlConfig model."""

    def test_default_scale_control_config(self):
        from azure_discovery.adt_types.models import ScaleControlConfig

        config = ScaleControlConfig()
        assert config.enabled is True
        assert config.initial_rps == 10.0
        assert config.max_concurrent_batches == 5
        assert config.initial_batch_size == 1000

    def test_scale_control_in_request(self):
        from azure_discovery.adt_types import AzureDiscoveryRequest

        request = AzureDiscoveryRequest(
            tenant_id="test-tenant",
            scale_controls={"initial_rps": 20.0, "max_concurrent_batches": 10},
        )
        assert request.scale_controls.initial_rps == 20.0
        assert request.scale_controls.max_concurrent_batches == 10


class TestAzureDiscoveryRequestValidation:
    """Test AzureDiscoveryRequest field validation."""

    def test_tenant_id_strips_whitespace(self):
        from azure_discovery.adt_types import AzureDiscoveryRequest

        request = AzureDiscoveryRequest(tenant_id="  my-tenant-id  ")
        assert request.tenant_id == "my-tenant-id"


class TestRBACRequestFields:
    """Test RBAC fields on AzureDiscoveryRequest."""

    def test_rbac_defaults(self):
        from azure_discovery.adt_types import AzureDiscoveryRequest

        request = AzureDiscoveryRequest(tenant_id="test-tenant")
        assert request.include_rbac_assignments is False
        assert request.include_rbac_definitions is False
        assert request.rbac_scope_filter is None

    def test_rbac_enabled(self):
        from azure_discovery.adt_types import AzureDiscoveryRequest

        request = AzureDiscoveryRequest(
            tenant_id="test-tenant",
            include_rbac_assignments=True,
            include_rbac_definitions=True,
            rbac_scope_filter=["/subscriptions/sub-a"],
        )
        assert request.include_rbac_assignments is True
        assert request.include_rbac_definitions is True
        assert request.rbac_scope_filter == ["/subscriptions/sub-a"]


class TestResourceNodeProperties:
    """Test the new properties field on ResourceNode."""

    def test_properties_default_empty(self):
        from azure_discovery.adt_types import ResourceNode

        node = ResourceNode(
            id="test-id",
            name="test",
            type="Microsoft.Compute/virtualMachines",
            subscription_id="sub-a",
        )
        assert node.properties == {}

    def test_properties_populated(self):
        from azure_discovery.adt_types import ResourceNode

        node = ResourceNode(
            id="test-id",
            name="test",
            type="Microsoft.Authorization/roleAssignments",
            subscription_id="sub-a",
            properties={"principalId": "user-1", "roleDefinitionName": "Reader"},
        )
        assert node.properties["principalId"] == "user-1"
        assert node.properties["roleDefinitionName"] == "Reader"
