"""Strict permission tests — every role × every route with exact expected codes.

ALLOWED → status must be 200 or 302 (never 403/404/500)
DENIED  → status must be 403 (strict — not 302!)
ANONYMOUS → protected routes get 302 (redirect to login)
           → public routes get 200

Dynamic routes (with path params like {slug}) are skipped here.
"""

import pytest
from tests.permission_config import ROUTE_PERMISSIONS, DYNAMIC_ROUTES, ROLES


def _generate_test_cases():
    """Yield (role, method, path, expected) tuples."""
    for (method, path), allowed_roles in ROUTE_PERMISSIONS.items():
        if (method, path) in DYNAMIC_ROUTES:
            continue
        for role in ROLES:
            if role in allowed_roles or "authenticated" in allowed_roles:
                yield (role, method, path, "allow")
            else:
                yield (role, method, path, "deny")

        # Anonymous
        if "anonymous" in allowed_roles:
            yield ("anonymous", method, path, "allow")
        else:
            yield ("anonymous", method, path, "deny")


@pytest.mark.parametrize("role,method,path,expected", list(_generate_test_cases()))
def test_access(role_client, role, method, path, expected):
    client = role_client[role]
    resp = client.request(method, path)

    if expected == "allow":
        assert resp.status_code not in (403, 404), (
            f"[{role}] {method} {path}: got {resp.status_code}, expected non-403/404"
        )
        if resp.status_code == 500:
            pytest.fail(f"[{role}] {method} {path}: 500 Internal Server Error")
    else:
        if role == "anonymous":
            assert resp.status_code in (302, 403), (
                f"[{role}] {method} {path}: got {resp.status_code}, expected 302/403"
            )
        else:
            assert resp.status_code == 403, (
                f"[{role}] {method} {path}: got {resp.status_code}, expected 403"
            )
