"""Tests for the PyAuthz Flask integration."""

from flask import Flask

from pyauthz import (
    Condition,
    Effect,
    Policy,
    PyAuthz,
    Resource,
    Role,
    Subject,
)
from pyauthz_flask import PyAuthzExtension, get_subject, requires


def make_app(rp: PyAuthz, subject: Subject | None = None) -> Flask:
    """Create a test Flask app with PyAuthz configured."""
    app = Flask(__name__)
    app.config["TESTING"] = True

    def resolve_user(request) -> Subject:
        if subject is not None:
            return subject
        user_id = request.headers.get("X-User-Id", "anonymous")
        roles = (
            request.headers.get("X-User-Roles", "").split(",")
            if request.headers.get("X-User-Roles")
            else []
        )
        return Subject(id=user_id, roles=roles)

    PyAuthzExtension(app, pyauthz=rp, subject_resolver=resolve_user)
    return app


class TestFlaskAllowedDenied:
    def test_allowed_request(self):
        rp = PyAuthz()
        rp.add_role(Role(name="admin", permissions=["*:*"]))
        admin = Subject(id="alice", roles=["admin"])
        app = make_app(rp, admin)

        @app.route("/items")
        @requires("documents:read")
        def get_items():
            return {"ok": True}

        client = app.test_client()
        resp = client.get("/items")
        assert resp.status_code == 200
        assert resp.get_json() == {"ok": True}

    def test_denied_request_returns_403(self):
        rp = PyAuthz()
        rp.add_role(Role(name="viewer", permissions=["documents:read"]))
        viewer = Subject(id="bob", roles=["viewer"])
        app = make_app(rp, viewer)

        @app.route("/docs/<doc_id>", methods=["DELETE"])
        @requires("documents:delete")
        def delete_doc(doc_id):
            return {"deleted": True}

        client = app.test_client()
        resp = client.delete("/docs/123")
        assert resp.status_code == 403
        body = resp.get_json()
        assert body["error"] == "forbidden"
        assert "reasoning" in body


class TestFlaskSubjectResolution:
    def test_get_subject_helper(self):
        rp = PyAuthz()
        rp.add_role(Role(name="admin", permissions=["*:*"]))
        app = make_app(rp)

        @app.route("/me")
        def me():
            subject = get_subject()
            return {"id": subject.id, "roles": subject.roles}

        client = app.test_client()
        resp = client.get("/me", headers={"X-User-Id": "alice", "X-User-Roles": "admin"})
        assert resp.status_code == 200
        assert resp.get_json()["id"] == "alice"

    def test_header_based_resolution(self):
        rp = PyAuthz()
        rp.add_role(Role(name="viewer", permissions=["documents:read"]))
        app = make_app(rp)

        @app.route("/items")
        @requires("documents:read")
        def get_items():
            return {"ok": True}

        client = app.test_client()
        resp = client.get("/items", headers={"X-User-Id": "carol", "X-User-Roles": "viewer"})
        assert resp.status_code == 200

    def test_anonymous_denied(self):
        rp = PyAuthz()
        rp.add_role(Role(name="viewer", permissions=["documents:read"]))
        app = make_app(rp)

        @app.route("/items")
        @requires("documents:read")
        def get_items():
            return {"ok": True}

        client = app.test_client()
        resp = client.get("/items")
        assert resp.status_code == 403


class TestFlaskResourceResolver:
    def test_custom_resource_resolver(self):
        rp = PyAuthz()
        rp.add_role(Role(name="editor", permissions=["documents:write"]))
        rp.add_policy(
            Policy(
                name="owner-edit",
                effect=Effect.ALLOW,
                roles=["editor"],
                permissions=["documents:write"],
                conditions=[
                    Condition(field="resource.owner_id", op="==", value="subject.id"),
                ],
            )
        )
        alice = Subject(id="alice", roles=["editor"])
        app = make_app(rp, alice)

        def resolve_document(request, **kwargs) -> Resource:
            return Resource(id=kwargs.get("doc_id", ""), type="documents", owner_id="alice")

        @app.route("/docs/<doc_id>", methods=["PUT"])
        @requires("documents:write", resource_resolver=resolve_document)
        def update_doc(doc_id):
            return {"updated": True}

        client = app.test_client()
        resp = client.put("/docs/doc-1")
        assert resp.status_code == 200

    def test_non_owner_denied(self):
        rp = PyAuthz()
        rp.add_role(Role(name="editor", permissions=["documents:write"]))
        rp.add_policy(
            Policy(
                name="owner-edit",
                effect=Effect.ALLOW,
                roles=["editor"],
                permissions=["documents:write"],
                conditions=[
                    Condition(field="resource.owner_id", op="==", value="subject.id"),
                ],
            )
        )
        bob = Subject(id="bob", roles=["editor"])
        app = make_app(rp, bob)

        def resolve_document(request, **kwargs) -> Resource:
            return Resource(id=kwargs.get("doc_id", ""), type="documents", owner_id="alice")

        @app.route("/docs/<doc_id>", methods=["PUT"])
        @requires("documents:write", resource_resolver=resolve_document)
        def update_doc(doc_id):
            return {"updated": True}

        client = app.test_client()
        resp = client.put("/docs/doc-1")
        assert resp.status_code == 403
