"""Every route, mount, page and SPA path is in exactly one class of the access policy.

`services/access.py` is both the middleware's policy and this inventory: a route in no
class is refused before its handler runs, and fails here so it cannot ship unnoticed. The
walk covers every `APIRoute` for every method, every mount, the page routes (the site
mounted over a temporary shell so they exist) and every `<Route path>` in `App.tsx`; the
PUBLIC_WHEN_OPEN set must equal `tests/fixtures/open_routes.json`, so opening a route to the
public is a reviewed diff; every public write carries a reason; every permission string is
one the kit enforces. No network, no database.
"""

import json
import pathlib
import re

import pytest
from fastapi.routing import APIRoute
from starlette.routing import Mount

from app import main
from app.services import access
from app.vendor import bw_accounts as bwa

MAIN = pathlib.Path(__file__).resolve().parents[1]
SHELL = "<!doctype html><html><head><title>t</title></head><body><div id=\"root\"></div></body></html>"


@pytest.fixture
def site(tmp_path):
    """The page routes mounted on the real app for one test, then removed again."""
    (tmp_path / "assets").mkdir()
    (tmp_path / "fonts").mkdir()
    (tmp_path / "medals").mkdir()
    (tmp_path / "index.html").write_text(SHELL)
    (tmp_path / "logo.png").write_bytes(b"png")
    before = len(main.app.routes)
    main.mount_site(main.app, tmp_path)
    try:
        yield main.app
    finally:
        del main.app.routes[before:]
        access.ROOT_FILES = frozenset()


def walk(routes):
    for route in routes:
        if isinstance(route, APIRoute | Mount):
            yield route
        elif hasattr(route, "routes"):
            yield from walk(route.routes)
        elif hasattr(route, "original_router"):
            yield from walk(route.original_router.routes)


def every_key(app) -> set[str]:
    keys = set()
    for route in walk(app.routes):
        if isinstance(route, Mount):
            keys.add(f"MOUNT {route.path}")
        else:
            for method in route.methods - {"HEAD", "OPTIONS"}:
                keys.add(f"{method} {route.path}")
    return keys


def spa_paths() -> set[str]:
    src = (MAIN / "web" / "src" / "App.tsx").read_text()
    found = set(re.findall(r'<Route\s+path="([^"]+)"', src)) | set(re.findall(r'path="([^"]+)"\s*\n', src))
    return {p for p in found if p != "*"}


def classes_of(key: str) -> list[str]:
    out = []
    if key in access.PUBLIC_ALWAYS:
        out.append("public_always")
    if key in access.PUBLIC_WHEN_OPEN:
        out.append("public_when_open")
    if key in access.MEMBER:
        out.append("member")
    if key in access.PERMISSION:
        out.append("permission")
    return out


def test_every_route_and_mount_is_in_exactly_one_class(site):
    keys = every_key(site)
    assert len(keys) > 80, "the walker stopped seeing included routers"
    problems = []
    for key in sorted(keys):
        cls, _ = access.classify(key, "/")
        n = len(classes_of(key))
        if cls is None or n != 1:
            problems.append(f"{key}: {n} class(es)")
    assert not problems, "classify each in exactly one class of services/access.py:\n  " + "\n  ".join(problems)


def test_every_spa_path_is_classified_deliberately():
    known = (set(access.SPA_PUBLIC_ALWAYS) | set(access.SPA_PUBLIC_WHEN_OPEN)
             | set(access.SPA_MEMBER) | set(access.SPA_PERMISSION))
    missing = []
    for path in sorted(spa_paths()):
        probe = re.sub(r":\w+", "example", path)
        if probe in known or probe.startswith(access.SPA_PUBLIC_WHEN_OPEN_PREFIXES):
            continue
        missing.append(path)
    assert not missing, f"App.tsx routes not named in services/access.py: {missing}"
    # ...and nothing is named twice.
    sets = [set(access.SPA_PUBLIC_ALWAYS), set(access.SPA_PUBLIC_WHEN_OPEN), set(access.SPA_MEMBER), set(access.SPA_PERMISSION)]
    for i, a in enumerate(sets):
        for b in sets[i + 1:]:
            assert not (a & b), a & b


def test_public_when_open_matches_the_reviewed_fixture():
    fixture = json.loads((MAIN / "tests" / "fixtures" / "open_routes.json").read_text())
    assert sorted(access.PUBLIC_WHEN_OPEN) == fixture["routes"]
    assert sorted(access.SPA_PUBLIC_WHEN_OPEN) == fixture["spa_paths"]
    assert list(access.SPA_PUBLIC_WHEN_OPEN_PREFIXES) == fixture["spa_prefixes"]


def test_every_public_write_states_why():
    for key, reason in access.PUBLIC_ALWAYS.items():
        assert reason.strip(), key
    writes = {k for k in access.PUBLIC_WHEN_OPEN if not k.startswith(("GET ", "MOUNT "))}
    assert writes == set(access.PUBLIC_WRITES_WHEN_OPEN)
    for key, reason in access.PUBLIC_WRITES_WHEN_OPEN.items():
        assert reason.strip(), key


def test_every_permission_string_is_enforced():
    for key, perm in list(access.PERMISSION.items()) + list(access.SPA_PERMISSION.items()):
        assert perm == "owner" or bwa.is_enforced(perm), (key, perm)


def test_the_kill_switch_and_the_plan_stay_the_owners_by_absence():
    assert access.PERMISSION["POST /api/sources/{slug}/enabled"] == "sources.manage"
    assert access.SPA_PERMISSION["/plan"] == "plan.view"
    for perm in ("plan.view", "items.act", "discussion.curate", "sources.manage"):
        assert perm not in {p for spec in main.accounts.SEED_LEVELS.values() for p in spec["permissions"]}


def test_machine_files_are_storefront_reads():
    assert access.MACHINE_FILES <= access.PUBLIC_WHEN_OPEN
