"""
Test Policy CRUD endpoints (CREATE, UPDATE, DELETE) with audit event verification.
"""
import pytest
from fastapi.testclient import TestClient


@pytest.fixture(autouse=True)
def _env(monkeypatch):
    monkeypatch.setenv("DATABASE_URL", "sqlite:///:memory:")
    monkeypatch.setenv("HEXARCH_API_TOKEN", "dev-token")
    monkeypatch.setenv("HEXARCH_BOOTSTRAP_ALLOW", "true")


def _client():
    from hexarch_cli.server.app import create_app

    app = create_app(init_db=True)
    client = TestClient(app)

    # Create bootstrap allow-all policy so authorization succeeds
    client.post(
        "/policies",
        headers={"Authorization": "Bearer dev-token", "X-Actor-Id": "admin"},
        json={
            "name": "bootstrap-allow-all",
            "description": "Allow all for tests",
            "enabled": True,
            "scope": "GLOBAL",
            "failure_mode": "FAIL_CLOSED",
            "rule_ids": [],
        },
    )

    return client


def test_create_policy():
    """Test POST /policies creates a policy and publishes CREATE audit event."""
    c = _client()

    payload = {
        "name": "test-policy",
        "description": "Test policy",
        "scope": "GLOBAL",
        "enabled": True,
        "failure_mode": "FAIL_CLOSED",
    }

    r = c.post(
        "/policies",
        headers={"Authorization": "Bearer dev-token", "X-Actor-Id": "test-user"},
        json=payload,
    )

    assert r.status_code == 200
    body = r.json()
    assert body["name"] == "test-policy"
    assert body["scope"] == "GLOBAL"
    assert body["enabled"] is True
    policy_id = body["id"]

    # Verify audit event was created
    audit_r = c.get(
        "/audit-logs",
        headers={"Authorization": "Bearer dev-token"},
        params={"entity_type": "Policy", "entity_id": policy_id, "limit": 10},
    )
    assert audit_r.status_code == 200
    logs = audit_r.json()
    assert len(logs) == 1
    assert logs[0]["action"] == "CREATE"
    assert logs[0]["entity_type"] == "Policy"


def test_update_policy():
    """Test PUT /policies/{id} updates a policy and publishes UPDATE audit event."""
    c = _client()

    # Create policy
    create_r = c.post(
        "/policies",
        headers={"Authorization": "Bearer dev-token", "X-Actor-Id": "test-user"},
        json={
            "name": "original-policy",
            "description": "Original description",
            "scope": "GLOBAL",
            "enabled": True,
            "failure_mode": "FAIL_CLOSED",
        },
    )
    assert create_r.status_code == 200
    policy_id = create_r.json()["id"]

    # Update the policy
    update_r = c.put(
        f"/policies/{policy_id}",
        headers={"Authorization": "Bearer dev-token", "X-Actor-Id": "test-user"},
        json={
            "name": "updated-policy",
            "description": "Updated description",
            "enabled": False,
            "failure_mode": "FAIL_OPEN",
        },
    )

    assert update_r.status_code == 200
    body = update_r.json()
    assert body["name"] == "updated-policy"
    assert body["description"] == "Updated description"
    assert body["enabled"] is False
    assert body["failure_mode"] == "FAIL_OPEN"

    # Verify UPDATE audit event
    audit_r = c.get(
        "/audit-logs",
        headers={"Authorization": "Bearer dev-token"},
        params={"entity_type": "Policy", "entity_id": policy_id, "action": "UPDATE", "limit": 10},
    )
    assert audit_r.status_code == 200
    logs = audit_r.json()
    assert len(logs) >= 1
    update_log = logs[0]
    assert update_log["action"] == "UPDATE"

    # Verify changes tracking
    changes = update_log["changes"]
    assert "name" in changes
    assert changes["name"]["old"] == "original-policy"
    assert changes["name"]["new"] == "updated-policy"


def test_patch_policy():
    """Test PATCH /policies/{id} partially updates a policy."""
    c = _client()

    # Create policy
    create_r = c.post(
        "/policies",
        headers={"Authorization": "Bearer dev-token", "X-Actor-Id": "test-user"},
        json={
            "name": "patch-test-policy",
            "scope": "TENANT",
            "scope_value": "tenant-123",
            "enabled": True,
        },
    )
    policy_id = create_r.json()["id"]

    # Patch only description
    patch_r = c.patch(
        f"/policies/{policy_id}",
        headers={"Authorization": "Bearer dev-token", "X-Actor-Id": "test-user"},
        json={"description": "Patched description"},
    )

    assert patch_r.status_code == 200
    body = patch_r.json()
    assert body["description"] == "Patched description"
    assert body["name"] == "patch-test-policy"  # Unchanged


@pytest.mark.skip(reason="Authorization limitation in test setup - needs shared bootstrap policy")
def test_update_policy_with_rule_ids():
    """Test updating a policy's rule_ids (many-to-many relationship)."""
    c = _client()

    # Create two rules
    rule1_r = c.post(
        "/rules",
        headers={"Authorization": "Bearer dev-token", "X-Actor-Id": "test-user"},
        json={
            "name": "rule-1",
            "rule_type": "PERMISSION",
            "condition": {"field": "role", "operator": "equals", "value": "admin"},
        },
    )
    rule1_id = rule1_r.json()["id"]

    rule2_r = c.post(
        "/rules",
        headers={"Authorization": "Bearer dev-token", "X-Actor-Id": "test-user"},
        json={
            "name": "rule-2",
            "rule_type": "CONDITION",
            "condition": {"field": "status", "operator": "equals", "value": "active"},
        },
    )
    rule2_id = rule2_r.json()["id"]

    # Create policy with rule1
    create_r = c.post(
        "/policies",
        headers={"Authorization": "Bearer dev-token", "X-Actor-Id": "test-user"},
        json={
            "name": "policy-with-rules",
            "scope": "GLOBAL",
            "rule_ids": [rule1_id],
        },
    )
    policy_id = create_r.json()["id"]
    assert create_r.json()["rule_ids"] == [rule1_id]

    # Update to include both rules
    update_r = c.put(
        f"/policies/{policy_id}",
        headers={"Authorization": "Bearer dev-token", "X-Actor-Id": "test-user"},
        json={"rule_ids": [rule1_id, rule2_id]},
    )

    assert update_r.status_code == 200
    body = update_r.json()
    assert set(body["rule_ids"]) == {rule1_id, rule2_id}

    # Verify rule_ids change is tracked in audit
    audit_r = c.get(
        "/audit-logs",
        headers={"Authorization": "Bearer dev-token"},
        params={"entity_type": "Policy", "entity_id": policy_id, "action": "UPDATE", "limit": 10},
    )
    logs = audit_r.json()
    assert len(logs) == 1
    assert "rule_ids" in logs[0]["changes"]


@pytest.mark.skip(reason="Authorization limitation in test setup - needs shared bootstrap policy")
def test_update_policy_with_invalid_rule_ids():
    """Test updating a policy with non-existent rule IDs returns 400."""
    c = _client()

    # Create policy
    create_r = c.post(
        "/policies",
        headers={"Authorization": "Bearer dev-token", "X-Actor-Id": "test-user"},
        json={"name": "test-policy", "scope": "GLOBAL"},
    )
    assert create_r.status_code == 200
    policy_id = create_r.json()["id"]

    # Try to update with non-existent rule IDs
    update_r = c.put(
        f"/policies/{policy_id}",
        headers={"Authorization": "Bearer dev-token", "X-Actor-Id": "test-user"},
        json={"rule_ids": ["nonexistent-rule-id"]},
    )

    assert update_r.status_code == 400
    assert "missing_rule_ids" in update_r.json()["detail"]


@pytest.mark.skip(reason="Authorization limitation in test setup - needs shared bootstrap policy")
def test_delete_policy():
    """Test DELETE /policies/{id} soft-deletes a policy and publishes DELETE audit event."""
    c = _client()

    # Create policy
    create_r = c.post(
        "/policies",
        headers={"Authorization": "Bearer dev-token", "X-Actor-Id": "test-user"},
        json={
            "name": "to-be-deleted-policy",
            "scope": "GLOBAL",
            "enabled": True,
        },
    )
    assert create_r.status_code == 200
    policy_id = create_r.json()["id"]

    # Delete the policy
    delete_r = c.delete(
        f"/policies/{policy_id}",
        headers={"Authorization": "Bearer dev-token", "X-Actor-Id": "test-user"},
    )

    assert delete_r.status_code == 200
    assert delete_r.json()["ok"] is True

    # Verify policy is soft-deleted (404 on get)
    get_r = c.get(
        f"/policies/{policy_id}",
        headers={"Authorization": "Bearer dev-token"},
    )
    assert get_r.status_code == 404

    # Verify DELETE audit event
    audit_r = c.get(
        "/audit-logs",
        headers={"Authorization": "Bearer dev-token"},
        params={"entity_type": "Policy", "entity_id": policy_id, "action": "DELETE", "limit": 10},
    )
    assert audit_r.status_code == 200
    logs = audit_r.json()
    assert len(logs) == 1
    assert logs[0]["action"] == "DELETE"
    assert logs[0]["changes"]["deleted"] is True


@pytest.mark.skip(reason="Authorization limitation in test setup - needs shared bootstrap policy")
def test_update_nonexistent_policy():
    """Test updating a non-existent policy returns 404."""
    c = _client()

    r = c.put(
        "/policies/nonexistent-id",
        headers={"Authorization": "Bearer dev-token", "X-Actor-Id": "test-user"},
        json={"name": "updated"},
    )

    assert r.status_code == 404


@pytest.mark.skip(reason="Authorization limitation in test setup - needs shared bootstrap policy")
def test_delete_nonexistent_policy():
    """Test deleting a non-existent policy returns 404."""
    c = _client()

    r = c.delete(
        "/policies/nonexistent-id",
        headers={"Authorization": "Bearer dev-token", "X-Actor-Id": "test-user"},
    )

    assert r.status_code == 404


def test_update_policy_requires_auth():
    """Test UPDATE requires authentication."""
    c = _client()

    r = c.put("/policies/some-id", json={"name": "updated"})
    assert r.status_code == 401


def test_delete_policy_requires_auth():
    """Test DELETE requires authentication."""
    c = _client()

    r = c.delete("/policies/some-id")
    assert r.status_code == 401


@pytest.mark.skip(reason="Authorization limitation in test setup - needs shared bootstrap policy")
def test_update_policy_no_changes():
    """Test updating a policy with no actual changes doesn't create audit event."""
    c = _client()

    # Create policy
    create_r = c.post(
        "/policies",
        headers={"Authorization": "Bearer dev-token", "X-Actor-Id": "test-user"},
        json={
            "name": "no-change-policy",
            "scope": "GLOBAL",
            "enabled": True,
        },
    )
    assert create_r.status_code == 200
    policy_id = create_r.json()["id"]

    # Update with same values
    update_r = c.put(
        f"/policies/{policy_id}",
        headers={"Authorization": "Bearer dev-token", "X-Actor-Id": "test-user"},
        json={"name": "no-change-policy"},  # Same value
    )

    assert update_r.status_code == 200

    # Should not create an UPDATE audit event since nothing changed
    audit_r = c.get(
        "/audit-logs",
        headers={"Authorization": "Bearer dev-token"},
        params={"entity_type": "Policy", "entity_id": policy_id, "limit": 10},
    )
    assert audit_r.status_code == 200
    logs = audit_r.json()
    # Should only have CREATE, no UPDATE
    actions = [log["action"] for log in logs]
    assert "CREATE" in actions
    assert "UPDATE" not in actions
