import asyncio
from typing import Any, Callable, Iterable, cast
from unittest.mock import patch

from django.contrib.auth.models import AnonymousUser
from django.test import TestCase

from engine.vibes_auth.messaging import auth as auth_module
from engine.vibes_auth.models import User


# pyright: reportUntypedBaseClass=false
class MessagingTests(TestCase):
    def test_extract_jwt_from_subprotocols_cases(self):
        # Access private helper via getattr to avoid private-usage warnings in type checkers
        fn = cast(
            Callable[[Iterable[str] | None], str | None],
            auth_module._extract_jwt_from_subprotocols,  # pyright: ignore[reportPrivateUsage]
        )
        self.assertIsNone(fn(None))
        self.assertIsNone(fn([]))
        self.assertEqual(fn(["bearer", "abc.token"]), "abc.token")
        self.assertEqual(fn(["Bearer", "abc"]), "abc")
        self.assertEqual(fn(["single-token"]), "single-token")
        self.assertIsNone(fn([""]))
        self.assertIsNone(fn(["Bearer", ""]))

    def test_jwt_middleware_sets_anonymous_without_token(self):
        captured: dict[str, Any] = {}

        async def inner_app(scope_dict: dict[str, Any], _receive: Any, _send: Any):
            captured["is_anon"] = (
                isinstance(scope_dict["user"], AnonymousUser)
                or scope_dict["user"].is_anonymous
            )

        middleware = auth_module.JWTAuthMiddleware(inner_app)

        scope: dict[str, Any] = {"type": "websocket", "subprotocols": []}

        async def dummy_receive() -> dict[str, str]:
            return {"type": "websocket.disconnect"}

        async def dummy_send(_message: Any) -> None:
            return None

        # Cast arguments to Any to bypass ASGI typing mismatches in tests
        asyncio.run(
            middleware(
                cast(Any, scope), cast(Any, dummy_receive), cast(Any, dummy_send)
            )
        )
        self.assertTrue(captured.get("is_anon"))

    def test_jwt_middleware_sets_user_with_valid_token(self):
        user: User = cast(
            User,
            cast(Any, User.objects).create_user(
                email="user@example.com", password="Str0ngPass!word"
            ),
        )

        class FakeAuth:
            def authenticate(self, _request: Any) -> tuple[User, str]:
                return user, "token"

        captured: dict[str, Any] = {}

        async def inner_app(scope_dict: dict[str, Any], _receive: Any, _send: Any):
            captured["user_id"] = getattr(scope_dict["user"], "pk", None)

        middleware = auth_module.JWTAuthMiddleware(inner_app)
        scope: dict[str, Any] = {
            "type": "websocket",
            "subprotocols": ["bearer", "abc.def"],
        }

        async def dummy_receive() -> dict[str, str]:
            return {"type": "websocket.disconnect"}

        async def dummy_send(_message: Any) -> None:
            return None

        with patch.object(auth_module, "JWTAuthentication", FakeAuth):
            asyncio.run(
                middleware(
                    cast(Any, scope), cast(Any, dummy_receive), cast(Any, dummy_send)
                )
            )
        user_pk = cast(Any, user).pk
        self.assertEqual(captured.get("user_id"), user_pk)

    def test_jwt_middleware_handles_bad_token_gracefully(self):
        class FakeAuth:
            def authenticate(self, _request: Any):
                raise Exception("bad token")

        captured: dict[str, Any] = {}

        async def inner_app(scope_dict: dict[str, Any], _receive: Any, _send: Any):
            captured["is_anon"] = (
                isinstance(scope_dict["user"], AnonymousUser)
                or scope_dict["user"].is_anonymous
            )

        middleware = auth_module.JWTAuthMiddleware(inner_app)
        scope: dict[str, Any] = {
            "type": "websocket",
            "subprotocols": ["bearer", "bad.token"],
        }

        async def dummy_receive() -> dict[str, str]:
            return {"type": "websocket.disconnect"}

        async def dummy_send(_message: Any) -> None:
            return None

        with patch.object(auth_module, "JWTAuthentication", FakeAuth):
            asyncio.run(
                middleware(
                    cast(Any, scope), cast(Any, dummy_receive), cast(Any, dummy_send)
                )
            )
        self.assertTrue(captured.get("is_anon"))
