"""
ASGI middleware that authenticates websocket chat connections.

The mobile app can't attach a normal Authorization header to a WebSocket
handshake, so the JWT access token travels as a `?token=...` query param
instead. This middleware validates that token, resolves the connecting
user's school (mirroring core.middleware.school_context.SchoolContextMiddleware),
and resolves *who* they are (teacher / parent, and — for the "view my
child's chats" flow — the specific student they're allowed to act as),
stamping the result onto `scope` for ChatConsumer to trust.

Nothing about `user_type`/`user_id` is ever taken from the client directly —
it's derived server-side from the validated token, so a connection can't
claim to be a different teacher/parent/student than the one the token
actually belongs to.
"""

import logging
from urllib.parse import parse_qs

from channels.db import database_sync_to_async
from rest_framework_simplejwt.exceptions import InvalidToken, TokenError
from rest_framework_simplejwt.tokens import AccessToken

logger = logging.getLogger(__name__)


class JWTAuthMiddleware:
    """Channels ASGI middleware — see module docstring."""

    def __init__(self, app):
        self.app = app

    async def __call__(self, scope, receive, send):
        scope["user_type"] = None
        scope["user_id"] = None
        scope["school_db"] = None

        query_string = scope.get("query_string", b"").decode()
        params = parse_qs(query_string)
        token = (params.get("token") or [None])[0]
        requested_student_id = (params.get("student_id") or [None])[0]

        if token:
            resolved = await self._authenticate(token, requested_student_id)
            if resolved:
                scope["user_type"], scope["user_id"], scope["school_db"] = resolved

        return await self.app(scope, receive, send)

    @database_sync_to_async
    def _authenticate(self, token, requested_student_id):
        from django.contrib.auth.models import User
        from django.db import connections

        from master_admin.models import UserProfile
        from people.models import Parent, StudentParent, Teacher

        try:
            validated = AccessToken(token)
            user = User.objects.get(id=validated["user_id"])
        except (TokenError, InvalidToken, User.DoesNotExist, KeyError):
            return None

        profile = getattr(user, "userprofile", None) or getattr(user, "profile", None)
        if not profile or not getattr(profile, "school", None):
            return None
        school = profile.school

        # Point the shared "school" DB connection at this user's school —
        # same trick as SchoolContextMiddleware, safe here because this
        # entire lookup runs as one self-contained database_sync_to_async
        # unit (see chat/consumers.py::school_scoped for why that matters
        # under Channels' concurrent websocket connections).
        connections["school"].settings_dict.update(
            {
                "NAME": school.db_name,
                "USER": school.db_user,
                "PASSWORD": school.db_password,
                "HOST": school.db_host,
                "PORT": school.db_port,
            }
        )
        connections["school"].close()

        school_db = {
            "name": school.db_name,
            "user": school.db_user,
            "password": school.db_password,
            "host": school.db_host,
            "port": school.db_port,
        }

        teacher = Teacher.objects.filter(
            external_user_id=str(user.id), is_active=True
        ).first()
        if teacher:
            return "teacher", teacher.id, school_db

        parent = Parent.objects.filter(
            external_user_id=str(user.id), is_active=True
        ).first()
        if parent:
            if requested_student_id:
                # Parent viewing/acting as one specific child's chats — only
                # allow it if that child is actually theirs.
                link = StudentParent.objects.filter(
                    parent=parent, student_id=requested_student_id, is_active=True
                ).first()
                if link:
                    return "student", int(requested_student_id), school_db
                return None
            return "parent", parent.id, school_db

        return None
