"""
WebSocket chat functions for WebAuthn registration and authentication flows.
"""

from uuid import UUID

from fastapi import WebSocket

from paskia import db
from paskia.authsession import session_ctx
from paskia.db import Credential, SessionContext
from paskia.fastapi.session import infodict
from paskia.fastapi.wsutil import validate_origin
from paskia.globals import passkey
from paskia.util import hostutil


async def register_chat(
    ws: WebSocket,
    user_uuid: UUID,
    user_name: str,
    origin: str,
    credential_ids: list[bytes] | None = None,
):
    """Run WebAuthn registration flow and return the verified credential."""
    options, challenge = passkey.instance.reg_generate_options(
        user_id=user_uuid,
        user_name=user_name,
        credential_ids=credential_ids,
    )
    await ws.send_json({"optionsJSON": options})
    response = await ws.receive_json()
    return passkey.instance.reg_verify(response, challenge, user_uuid, origin=origin)


async def authenticate_chat(
    ws: WebSocket,
    credential_ids: list[bytes] | None = None,
) -> tuple[Credential, int]:
    """Run WebAuthn authentication flow and return the credential and new sign count.

    Returns:
        tuple of (credential, new_sign_count) where new_sign_count comes from WebAuthn verification
    """
    origin = validate_origin(ws)
    options, challenge = passkey.instance.auth_generate_options(
        credential_ids=credential_ids
    )
    await ws.send_json({"optionsJSON": options})
    authcred = passkey.instance.auth_parse(await ws.receive_json())

    cred = next(
        (
            c
            for c in db.data().credentials.values()
            if c.credential_id == authcred.raw_id
        ),
        None,
    )
    if not cred:
        raise ValueError(
            f"This passkey is no longer registered with {passkey.instance.rp_name}"
        )

    verification = passkey.instance.auth_verify(authcred, challenge, cred, origin)
    return cred, verification.new_sign_count


async def authenticate_and_login(
    ws: WebSocket,
    auth: str | None = None,
) -> tuple[SessionContext, str]:
    """Run WebAuthn authentication flow, create session, and return the session context.

    If auth is provided, restrict authentication to credentials of that session's user.

    Returns:
        Tuple of (SessionContext for the authenticated session, session secret)
    """
    origin = validate_origin(ws)
    host = origin.split("://", 1)[1]
    normalized_host = hostutil.normalize_host(host)
    if not normalized_host:
        raise ValueError("Host required for session creation")
    hostname = normalized_host.split(":")[0]
    rp_id = passkey.instance.rp_id
    if not (hostname == rp_id or hostname.endswith(f".{rp_id}")):
        raise ValueError(f"Host must be the same as or a subdomain of {rp_id}")
    metadata = infodict(ws, "auth")

    # Get credential IDs if restricting to a user's credentials
    credential_ids = None
    if auth:
        existing_ctx = session_ctx(auth, host)
        if existing_ctx:
            credential_ids = existing_ctx.user.credential_ids or None

    cred, new_sign_count = await authenticate_chat(ws, credential_ids)

    # Create session and update user/credential
    secret = db.login(
        user_uuid=cred.user_uuid,
        credential_uuid=cred.uuid,
        sign_count=new_sign_count,
        host=normalized_host,
        ip=metadata["ip"],
        user_agent=metadata["user_agent"],
    )

    # Fetch and return the full session context
    ctx = session_ctx(secret, host)
    if not ctx:
        raise ValueError("Failed to create session context")
    return ctx, secret
