"""
Test Rule 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_rule():
    """Test POST /rules creates a rule and publishes CREATE audit event."""
    c = _client()

    payload = {
        "name": "test-rule",
        "rule_type": "PERMISSION",
        "condition": {"field": "user.role", "operator": "equals", "value": "admin"},
        "description": "Test rule",
        "priority": 100,
        "enabled": True,
    }

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

    assert r.status_code == 200
    body = r.json()
    assert body["name"] == "test-rule"
    assert body["rule_type"] == "PERMISSION"
    assert body["enabled"] is True
    rule_id = body["id"]

    # Verify audit event was created
    audit_r = c.get(
        "/audit-logs",
        headers={"Authorization": "Bearer dev-token"},
        params={"entity_type": "Rule", "entity_id": rule_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"] == "Rule"
    assert logs[0]["entity_id"] == rule_id


def test_update_rule():
    """Test PUT /rules/{id} updates a rule and publishes UPDATE audit event."""
    c = _client()

    # Create rule first
    create_payload = {
        "name": "original-name",
        "rule_type": "PERMISSION",
        "condition": {"field": "user.role", "operator": "equals", "value": "user"},
        "priority": 100,
        "enabled": True,
    }

    create_r = c.post(
        "/rules",
        headers={"Authorization": "Bearer dev-token", "X-Actor-Id": "test-user"},
        json=create_payload,
    )
    assert create_r.status_code == 200
    rule_id = create_r.json()["id"]

    # Update the rule
    update_payload = {
        "name": "updated-name",
        "enabled": False,
        "priority": 200,
    }

    update_r = c.put(
        f"/rules/{rule_id}",
        headers={"Authorization": "Bearer dev-token", "X-Actor-Id": "test-user"},
        json=update_payload,
    )

    assert update_r.status_code == 200
    body = update_r.json()
    assert body["name"] == "updated-name"
    assert body["enabled"] is False
    assert body["priority"] == 200

    # Verify UPDATE audit event was created
    audit_r = c.get(
        "/audit-logs",
        headers={"Authorization": "Bearer dev-token"},
        params={"entity_type": "Rule", "entity_id": rule_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"
    assert update_log["entity_type"] == "Rule"

    # Verify changes are tracked
    changes = update_log["changes"]
    assert "name" in changes
    assert changes["name"]["old"] == "original-name"
    assert changes["name"]["new"] == "updated-name"
    assert "enabled" in changes
    assert changes["enabled"]["old"] is True
    assert changes["enabled"]["new"] is False


def test_patch_rule():
    """Test PATCH /rules/{id} updates a rule (same as PUT)."""
    c = _client()

    # Create rule
    create_r = c.post(
        "/rules",
        headers={"Authorization": "Bearer dev-token", "X-Actor-Id": "test-user"},
        json={
            "name": "patch-test",
            "rule_type": "CONDITION",
            "condition": {"field": "status", "operator": "equals", "value": "active"},
            "enabled": True,
        },
    )
    rule_id = create_r.json()["id"]

    # Patch the rule
    patch_r = c.patch(
        f"/rules/{rule_id}",
        headers={"Authorization": "Bearer dev-token", "X-Actor-Id": "test-user"},
        json={"description": "Added description via PATCH"},
    )

    assert patch_r.status_code == 200
    assert patch_r.json()["description"] == "Added description via PATCH"


def test_delete_rule():
    """Test DELETE /rules/{id} soft-deletes a rule and publishes DELETE audit event."""
    c = _client()

    # Create rule
    create_r = c.post(
        "/rules",
        headers={"Authorization": "Bearer dev-token", "X-Actor-Id": "test-user"},
        json={
            "name": "to-be-deleted",
            "rule_type": "CONSTRAINT",
            "condition": {"type": "rate_limit", "max": 100},
            "enabled": True,
        },
    )
    rule_id = create_r.json()["id"]

    # Delete the rule
    delete_r = c.delete(
        f"/rules/{rule_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 rule is soft-deleted (404 on get)
    get_r = c.get(
        f"/rules/{rule_id}",
        headers={"Authorization": "Bearer dev-token"},
    )
    assert get_r.status_code == 404

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


def test_update_nonexistent_rule():
    """Test updating a non-existent rule returns 404."""
    c = _client()

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

    assert r.status_code == 404


def test_delete_nonexistent_rule():
    """Test deleting a non-existent rule returns 404."""
    c = _client()

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

    assert r.status_code == 404


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

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


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

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


def test_update_rule_no_changes():
    """Test updating a rule with no actual changes doesn't create audit event."""
    c = _client()

    # Create rule
    create_r = c.post(
        "/rules",
        headers={"Authorization": "Bearer dev-token", "X-Actor-Id": "test-user"},
        json={
            "name": "no-change-test",
            "rule_type": "PERMISSION",
            "condition": {"field": "action", "operator": "equals", "value": "read"},
            "enabled": True,
        },
    )
    rule_id = create_r.json()["id"]

    # Update with same values
    update_r = c.put(
        f"/rules/{rule_id}",
        headers={"Authorization": "Bearer dev-token", "X-Actor-Id": "test-user"},
        json={"name": "no-change-test"},  # 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": "Rule", "entity_id": rule_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
