"""
Bind WebSocket connection to identity (token or mTLS) and authorize subscribe.

Identity is read from scope (headers for token; client cert for mTLS).
is_allowed_to_subscribe(identity, job_id) implements (a) allow all; (b)/(c) documented.

Author: Vasiliy Zdanovskiy
email: vasilyvz@gmail.com
"""

from __future__ import annotations

from typing import Any, Dict, Mapping, Optional

# For Hypercorn with client cert, cert may be in scope["extensions"].get("asgi", {}).get("client_cert")
# or scope.get("asgi", {}).get("client_cert"). Document for the chosen ASGI server.


def get_identity_from_scope(scope: Mapping[str, Any]) -> Dict[str, Any]:
    """
    Extract identity from WebSocket/ASGI scope (e.g. after upgrade).

    Returns a dict with type and optional fields:
    - type "token": user_id, role (from headers / auth middleware)
    - type "mtls": fingerprint, cn (from client cert if present in scope)

    Args:
        scope: ASGI scope (e.g. request.scope or websocket.scope).

    Returns:
        Identity dict; {"type": "anonymous"} if none found.
    """
    headers = scope.get("headers") or []
    header_dict = {}
    for k, v in headers:
        try:
            header_dict[k.decode("latin-1").lower()] = (
                v.decode("latin-1") if isinstance(v, bytes) else v
            )
        except Exception:
            pass
    token = header_dict.get("x-api-key") or header_dict.get("authorization")
    if token:
        if isinstance(token, str) and token.lower().startswith("bearer "):
            token = token[7:].strip()
        return {"type": "token", "user_id": str(token)[:64], "role": None}
    asgi = scope.get("asgi") or scope.get("extensions") or {}
    if isinstance(asgi, dict):
        client_cert = asgi.get("client_cert")
    else:
        client_cert = getattr(asgi, "client_cert", None) if asgi else None
    if client_cert:
        cn = getattr(client_cert, "get", lambda k: None)("subject")
        if cn and hasattr(cn, "rfc4514_string"):
            cn = cn.rfc4514_string()
        else:
            cn = str(client_cert)[:128]
        return {"type": "mtls", "cn": cn, "fingerprint": None}
    return {"type": "anonymous"}


def is_allowed_to_subscribe(
    identity: Dict[str, Any],
    job_id: str,
    job_creator_store: Optional[Dict[str, Any]] = None,
) -> bool:
    """
    Return True if this identity may subscribe to events for job_id.

    (a) Allow all: always return True.
    (b) Only job creator: require job_creator_store and job_creator_store.get(job_id) == identity.
    (c) By role: check identity.get("role") in allowed roles.

    Args:
        identity: From get_identity_from_scope(scope).
        job_id: Job to subscribe to.
        job_creator_store: Optional mapping job_id -> creator identity (set when enqueueing).

    Returns:
        True if allowed (default: always True).
    """
    if job_creator_store is not None:
        creator = job_creator_store.get(job_id)
        if creator is not None and creator != identity:
            return False
    return True
