# Copyright 2026 Guildhouse Dev
# SPDX-License-Identifier: Apache-2.0

"""Tests for Intune connector and device compliance cache."""

import time
import pytest
from datetime import datetime, UTC
from unittest.mock import AsyncMock, MagicMock, patch

import httpx

from gsap_broker.connectors.base import ConnectorContext
from gsap_broker.connectors.intune import IntuneConnector
from gsap_broker.intune.device_cache import DeviceComplianceCache
from gsap_broker.intune.graph_client import GraphClient
from gsap_broker.models.intune import ComplianceState


@pytest.fixture
def mock_graph():
    graph = MagicMock(spec=GraphClient)
    graph.tenant_id = "test-tenant"
    graph.client_id = "test-client"
    graph.get = AsyncMock()
    graph.post = AsyncMock()
    return graph


@pytest.fixture
def cache():
    return DeviceComplianceCache(ttl_seconds=5)


@pytest.fixture
def connector(mock_graph, cache):
    return IntuneConnector(graph_client=mock_graph, cache=cache)


@pytest.fixture
def ctx():
    return ConnectorContext(gsap_context_id="test-ac-123")


# ── list_devices ──────────────────────────────────────────────

@pytest.mark.asyncio
async def test_list_devices(connector, mock_graph, ctx):
    mock_graph.get.return_value = {
        "value": [
            {
                "id": "dev-1",
                "deviceName": "LAPTOP-001",
                "operatingSystem": "Windows",
                "osVersion": "10.0.19045",
                "complianceState": "compliant",
                "lastSyncDateTime": "2026-04-14T00:00:00Z",
                "userPrincipalName": "alice@contoso.com",
                "azureADDeviceId": "entra-dev-1",
            },
            {
                "id": "dev-2",
                "deviceName": "PHONE-001",
                "operatingSystem": "iOS",
                "osVersion": "17.0",
                "complianceState": "noncompliant",
            },
        ]
    }

    result = await connector.invoke("list_devices", {"top": 10}, ctx)

    assert result.success
    assert len(result.data) == 2
    assert result.data[0]["device_id"] == "dev-1"
    assert result.data[0]["compliance_state"] == "compliant"
    assert result.data[1]["compliance_state"] == "noncompliant"


# ── get_compliance ────────────────────────────────────────────

@pytest.mark.asyncio
async def test_get_compliance_compliant(connector, mock_graph, ctx):
    mock_graph.get.return_value = {
        "id": "dev-1",
        "complianceState": "compliant",
        "lastSyncDateTime": "2026-04-14T00:00:00Z",
    }

    result = await connector.invoke("get_compliance", {"device_id": "00000000-0000-0000-0000-000000000001"}, ctx)

    assert result.success
    assert result.data["compliant"] is True
    assert result.data["state"] == "compliant"


@pytest.mark.asyncio
async def test_get_compliance_noncompliant(connector, mock_graph, ctx):
    mock_graph.get.return_value = {
        "id": "dev-1",
        "complianceState": "noncompliant",
    }

    result = await connector.invoke("get_compliance", {"device_id": "00000000-0000-0000-0000-000000000001"}, ctx)

    assert result.success
    assert result.data["compliant"] is False
    assert result.data["state"] == "noncompliant"


@pytest.mark.asyncio
async def test_get_compliance_uses_cache(connector, mock_graph, cache, ctx):
    # Pre-populate cache
    state = ComplianceState(
        device_id="00000000-0000-0000-0000-00000000000c", compliant=True, state="compliant",
        last_evaluated=datetime.now(UTC),
    )
    await cache.set("00000000-0000-0000-0000-00000000000c", state)

    result = await connector.invoke("get_compliance", {"device_id": "00000000-0000-0000-0000-00000000000c"}, ctx)

    assert result.success
    assert result.data["compliant"] is True
    # Graph API should NOT have been called
    mock_graph.get.assert_not_called()


@pytest.mark.asyncio
async def test_get_compliance_missing_device_id(connector, ctx):
    result = await connector.invoke("get_compliance", {}, ctx)
    assert not result.success
    assert "device_id required" in result.error


# ── remote_lock ───────────────────────────────────────────────

@pytest.mark.asyncio
async def test_remote_lock(connector, mock_graph, ctx):
    resp = MagicMock()
    resp.status_code = 204
    mock_graph.post.return_value = resp

    result = await connector.invoke("remote_lock", {"device_id": "00000000-0000-0000-0000-000000000001"}, ctx)

    assert result.success
    assert result.data["locked"] is True


# ── unknown operation ─────────────────────────────────────────

@pytest.mark.asyncio
async def test_unknown_operation(connector, ctx):
    result = await connector.invoke("hack_device", {}, ctx)
    assert not result.success
    assert "Unknown operation" in result.error


# ── health_check ──────────────────────────────────────────────

def test_health_check(connector):
    assert connector.health_check() is True


def test_health_check_unconfigured():
    graph = MagicMock(spec=GraphClient)
    graph.tenant_id = ""
    graph.client_id = ""
    conn = IntuneConnector(graph_client=graph)
    assert conn.health_check() is False


# ── DeviceComplianceCache ─────────────────────────────────────

@pytest.mark.asyncio
async def test_cache_set_and_get(cache):
    state = ComplianceState(
        device_id="dev-1", compliant=True, state="compliant",
        last_evaluated=datetime.now(UTC),
    )
    await cache.set("dev-1", state)
    result = await cache.get("dev-1")
    assert result is not None
    assert result.compliant is True


@pytest.mark.asyncio
async def test_cache_ttl_expiry():
    cache = DeviceComplianceCache(ttl_seconds=0)
    state = ComplianceState(
        device_id="dev-1", compliant=True, state="compliant",
        last_evaluated=datetime.now(UTC),
    )
    await cache.set("dev-1", state)
    # TTL is 0, so it should be expired immediately
    time.sleep(0.01)
    result = await cache.get("dev-1")
    assert result is None


@pytest.mark.asyncio
async def test_cache_miss():
    cache = DeviceComplianceCache()
    result = await cache.get("nonexistent")
    assert result is None


# ── Connector catalog conditional registration ────────────────

@pytest.mark.asyncio
async def test_intune_not_in_catalog_when_disabled(client):
    """Intune connector should NOT appear when intune_enabled=False (default)."""
    resp = await client.get("/connectors/")
    assert resp.status_code == 200
    ids = [c["connector_id"] for c in resp.json()]
    assert "intune" not in ids


@pytest.fixture
async def client():
    """Reuse the broker test client fixture pattern."""
    from httpx import AsyncClient, ASGITransport
    from gsap_broker.app import app
    async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as c:
        yield c
