"""Authentication helpers combining crypto with autogenerated API functions."""

from __future__ import annotations

from arbi_client.api.user import login_user, register_user
from arbi_client.client import Client
from arbi_client.models.login_request import LoginRequest
from arbi_client.models.register_request import RegisterRequest
from arbi_client.sdk._types import SessionState, UserIdentity
from arbi_client.sdk.crypto import create_login_signature, generate_keypair

# ---------------------------------------------------------------------------
# Register
# ---------------------------------------------------------------------------


def register(
    client: Client,
    *,
    email: str,
    password: str,
    given_name: str,
    family_name: str,
    deployment_domain: str,
    verification_credential: str,
) -> UserIdentity:
    """Register a new user and return their identity (keys derived from password).

    Does **not** log in — call :func:`login` or :func:`login_with_identity` afterwards.
    """
    signing_pub_b64, signing_priv, enc_pub_b64, enc_priv = generate_keypair(password, email, deployment_domain)

    register_user.sync(
        client=client,
        body=RegisterRequest(
            email=email,
            signing_key=signing_pub_b64,
            verification_credential=verification_credential,
            given_name=given_name,
            family_name=family_name,
        ),
    )

    # Login to get the user's external_id
    sig_b64, timestamp = create_login_signature(email, signing_priv)
    login_resp = login_user.sync(
        client=client,
        body=LoginRequest(email=email, signature=sig_b64, timestamp=timestamp),
    )
    if login_resp is None or not hasattr(login_resp, "access_token"):
        raise RuntimeError(f"Login after registration failed: {login_resp}")

    return UserIdentity(
        email=email,
        external_id=login_resp.user.external_id,
        given_name=given_name,
        family_name=family_name,
        signing_private_key=signing_priv,
        signing_public_key_b64=signing_pub_b64,
        encryption_private_key=enc_priv,
        encryption_public_key_b64=enc_pub_b64,
    )


async def aregister(
    client: Client,
    *,
    email: str,
    password: str,
    given_name: str,
    family_name: str,
    deployment_domain: str,
    verification_credential: str,
) -> UserIdentity:
    """Async variant of :func:`register`."""
    signing_pub_b64, signing_priv, enc_pub_b64, enc_priv = generate_keypair(password, email, deployment_domain)

    await register_user.asyncio(
        client=client,
        body=RegisterRequest(
            email=email,
            signing_key=signing_pub_b64,
            verification_credential=verification_credential,
            given_name=given_name,
            family_name=family_name,
        ),
    )

    sig_b64, timestamp = create_login_signature(email, signing_priv)
    login_resp = await login_user.asyncio(
        client=client,
        body=LoginRequest(email=email, signature=sig_b64, timestamp=timestamp),
    )
    if login_resp is None or not hasattr(login_resp, "access_token"):
        raise RuntimeError(f"Login after registration failed: {login_resp}")

    return UserIdentity(
        email=email,
        external_id=login_resp.user.external_id,
        given_name=given_name,
        family_name=family_name,
        signing_private_key=signing_priv,
        signing_public_key_b64=signing_pub_b64,
        encryption_private_key=enc_priv,
        encryption_public_key_b64=enc_pub_b64,
    )


# ---------------------------------------------------------------------------
# Login
# ---------------------------------------------------------------------------


def login(
    client: Client,
    *,
    email: str,
    password: str,
    deployment_domain: str,
) -> tuple[UserIdentity, SessionState]:
    """Derive keys from password, log in, and return identity + session state."""
    signing_pub_b64, signing_priv, enc_pub_b64, enc_priv = generate_keypair(password, email, deployment_domain)

    sig_b64, timestamp = create_login_signature(email, signing_priv)

    login_resp = login_user.sync(
        client=client,
        body=LoginRequest(email=email, signature=sig_b64, timestamp=timestamp),
    )
    if login_resp is None or not hasattr(login_resp, "access_token"):
        raise RuntimeError(f"Login failed: {login_resp}")

    identity = UserIdentity(
        email=email,
        external_id=login_resp.user.external_id,
        given_name=login_resp.user.given_name or "",
        family_name=login_resp.user.family_name or "",
        signing_private_key=signing_priv,
        signing_public_key_b64=signing_pub_b64,
        encryption_private_key=enc_priv,
        encryption_public_key_b64=enc_pub_b64,
    )

    session = SessionState(
        access_token=login_resp.access_token,
        session_key_b64=login_resp.session_key,
    )

    return identity, session


async def alogin(
    client: Client,
    *,
    email: str,
    password: str,
    deployment_domain: str,
) -> tuple[UserIdentity, SessionState]:
    """Async variant of :func:`login`."""
    signing_pub_b64, signing_priv, enc_pub_b64, enc_priv = generate_keypair(password, email, deployment_domain)

    sig_b64, timestamp = create_login_signature(email, signing_priv)

    login_resp = await login_user.asyncio(
        client=client,
        body=LoginRequest(email=email, signature=sig_b64, timestamp=timestamp),
    )
    if login_resp is None or not hasattr(login_resp, "access_token"):
        raise RuntimeError(f"Login failed: {login_resp}")

    identity = UserIdentity(
        email=email,
        external_id=login_resp.user.external_id,
        given_name=login_resp.user.given_name or "",
        family_name=login_resp.user.family_name or "",
        signing_private_key=signing_priv,
        signing_public_key_b64=signing_pub_b64,
        encryption_private_key=enc_priv,
        encryption_public_key_b64=enc_pub_b64,
    )

    session = SessionState(
        access_token=login_resp.access_token,
        session_key_b64=login_resp.session_key,
    )

    return identity, session


# ---------------------------------------------------------------------------
# Login with existing identity (re-auth without password)
# ---------------------------------------------------------------------------


def login_with_identity(
    client: Client,
    *,
    identity: UserIdentity,
) -> SessionState:
    """Log in using an existing :class:`UserIdentity` (skips key derivation)."""
    sig_b64, timestamp = create_login_signature(identity.email, identity.signing_private_key)

    login_resp = login_user.sync(
        client=client,
        body=LoginRequest(email=identity.email, signature=sig_b64, timestamp=timestamp),
    )
    if login_resp is None or not hasattr(login_resp, "access_token"):
        raise RuntimeError(f"Login failed: {login_resp}")

    return SessionState(
        access_token=login_resp.access_token,
        session_key_b64=login_resp.session_key,
    )


async def alogin_with_identity(
    client: Client,
    *,
    identity: UserIdentity,
) -> SessionState:
    """Async variant of :func:`login_with_identity`."""
    sig_b64, timestamp = create_login_signature(identity.email, identity.signing_private_key)

    login_resp = await login_user.asyncio(
        client=client,
        body=LoginRequest(email=identity.email, signature=sig_b64, timestamp=timestamp),
    )
    if login_resp is None or not hasattr(login_resp, "access_token"):
        raise RuntimeError(f"Login failed: {login_resp}")

    return SessionState(
        access_token=login_resp.access_token,
        session_key_b64=login_resp.session_key,
    )
