"""CRUD de Permission Groups de Metabase."""

from .client import MetabaseClient


def metabase_list_groups(client: MetabaseClient) -> list[dict]:
    """Lista todos los Permission Groups de Metabase.

    Endpoint: GET /api/permissions/group. Requiere superusuario.

    Args:
        client: Cliente autenticado con permisos admin.

    Returns:
        Lista de dicts, cada uno con: id, name, member_count.

    Example:
        >>> groups = metabase_list_groups(client)
        >>> for g in groups:
        ...     print(g["id"], g["name"], g["member_count"])
    """
    return client.request("GET", "/api/permissions/group")


def metabase_get_group(client: MetabaseClient, group_id: int) -> dict:
    """Obtiene un Permission Group por su ID, incluyendo la lista completa de miembros.

    Endpoint: GET /api/permissions/group/:id. Requiere superusuario.

    Args:
        client: Cliente autenticado con permisos admin.
        group_id: ID numerico del grupo.

    Returns:
        Dict con: id, name, members (lista de dicts con user_id, email,
        first_name, last_name, membership_id).

    Raises:
        httpx.HTTPStatusError: 404 si el grupo no existe.

    Example:
        >>> group = metabase_get_group(client, 3)
        >>> print(group["name"])
        >>> for m in group["members"]:
        ...     print(m["email"])
    """
    return client.request("GET", f"/api/permissions/group/{group_id}")


def metabase_create_group(client: MetabaseClient, name: str) -> dict:
    """Crea un nuevo Permission Group en Metabase.

    Endpoint: POST /api/permissions/group. Requiere superusuario.

    Args:
        client: Cliente autenticado con permisos admin.
        name: Nombre del grupo. Debe ser unico.

    Returns:
        Dict con el grupo creado: id, name.

    Raises:
        httpx.HTTPStatusError: 400 si el nombre ya existe.

    Example:
        >>> group = metabase_create_group(client, "Analytics Team")
        >>> print(group["id"], group["name"])
    """
    return client.request("POST", "/api/permissions/group", json={"name": name})


def metabase_update_group(client: MetabaseClient, group_id: int, name: str) -> dict:
    """Renombra un Permission Group existente en Metabase.

    Endpoint: PUT /api/permissions/group/:id. Requiere superusuario.
    La API solo permite modificar el nombre del grupo.

    Args:
        client: Cliente autenticado con permisos admin.
        group_id: ID numerico del grupo a renombrar.
        name: Nuevo nombre del grupo.

    Returns:
        Dict con el grupo actualizado: id, name.

    Raises:
        httpx.HTTPStatusError: 404 si el grupo no existe.

    Example:
        >>> group = metabase_update_group(client, 3, "Data Team")
        >>> print(group["name"])
    """
    return client.request("PUT", f"/api/permissions/group/{group_id}", json={"name": name})


def metabase_delete_group(client: MetabaseClient, group_id: int) -> None:
    """Elimina permanentemente un Permission Group de Metabase.

    Endpoint: DELETE /api/permissions/group/:id. IRREVERSIBLE.
    Requiere superusuario.

    Los grupos especiales del sistema no deben borrarse:
    - id=1: "All Users" (todos los usuarios pertenecen a este grupo)
    - id=2: "Administrators"
    Esta funcion NO bloquea el borrado de esos IDs — es responsabilidad
    del caller verificar que no se pasen IDs protegidos.

    Args:
        client: Cliente autenticado con permisos admin.
        group_id: ID numerico del grupo a eliminar.

    Raises:
        httpx.HTTPStatusError: 404 si el grupo no existe.

    Example:
        >>> metabase_delete_group(client, 5)
        >>> # CUIDADO: no pasar group_id=1 (All Users) ni group_id=2 (Administrators)
    """
    client.request("DELETE", f"/api/permissions/group/{group_id}")


# --- Memberships ---


def metabase_list_memberships(client: MetabaseClient) -> dict[str, list[dict]]:
    """Lista todas las membresías de grupos de Metabase.

    Endpoint: GET /api/permissions/membership. Requiere superusuario.

    La respuesta nativa de Metabase es un dict con group_id (str) como clave,
    y una lista de membresías como valor — no una lista plana.

    Args:
        client: Cliente autenticado con permisos admin.

    Returns:
        Dict mapeando group_id (str) a lista de dicts, cada uno con:
        membership_id, user_id, group_id, is_group_manager.

    Example:
        >>> memberships = metabase_list_memberships(client)
        >>> for group_id, members in memberships.items():
        ...     for m in members:
        ...         print(m["user_id"], m["membership_id"], m["is_group_manager"])
    """
    return client.request("GET", "/api/permissions/membership")


def metabase_add_membership(
    client: MetabaseClient,
    user_id: int,
    group_id: int,
    is_group_manager: bool = False,
) -> list[dict]:
    """Añade un usuario a un Permission Group de Metabase.

    Endpoint: POST /api/permissions/membership. Requiere superusuario.

    Args:
        client: Cliente autenticado con permisos admin.
        user_id: ID del usuario a añadir al grupo.
        group_id: ID del grupo destino.
        is_group_manager: Si True, el usuario es manager del grupo.

    Returns:
        Lista de dicts con todas las membresias actuales del grupo tras la operacion.
        Cada elemento tiene: membership_id, user_id, group_id, is_group_manager.

    Raises:
        httpx.HTTPStatusError: 400 si el usuario ya es miembro del grupo.

    Example:
        >>> members = metabase_add_membership(client, user_id=5, group_id=3)
        >>> print(len(members), "miembros en el grupo")
        >>> # Como manager:
        >>> members = metabase_add_membership(client, user_id=5, group_id=3, is_group_manager=True)
    """
    body = {
        "user_id": user_id,
        "group_id": group_id,
        "is_group_manager": is_group_manager,
    }
    return client.request("POST", "/api/permissions/membership", json=body)


def metabase_delete_membership(client: MetabaseClient, membership_id: int) -> None:
    """Elimina una membresía de grupo en Metabase por su membership_id.

    Endpoint: DELETE /api/permissions/membership/:id. Requiere superusuario.

    IMPORTANTE: No se borra por user_id + group_id. Hay que conocer el
    membership_id exacto, que se obtiene via metabase_list_memberships.

    Args:
        client: Cliente autenticado con permisos admin.
        membership_id: ID de la membresía a eliminar (no el user_id ni group_id).

    Raises:
        httpx.HTTPStatusError: 404 si la membresía no existe.

    Example:
        >>> # Primero obtener el membership_id
        >>> all_memberships = metabase_list_memberships(client)
        >>> group_members = all_memberships.get("3", [])
        >>> membership_id = next(m["membership_id"] for m in group_members if m["user_id"] == 5)
        >>> metabase_delete_membership(client, membership_id)
    """
    client.request("DELETE", f"/api/permissions/membership/{membership_id}")


# --- Data Permission Graph ---


def metabase_get_permission_graph(client: MetabaseClient) -> dict:
    """Obtiene el grafo de permisos de datos (databases/schemas/tables) de Metabase.

    Endpoint: GET /api/permissions/graph. Requiere superusuario.

    El campo `revision` es CRITICO para concurrency control: el servidor rechaza
    PUT /api/permissions/graph si el revision no coincide con el actual (HTTP 409).
    Siempre traer el graph fresco antes de modificar.

    Args:
        client: Cliente autenticado con permisos admin.

    Returns:
        Dict con:
        - revision (int): numero de revision actual. Obligatorio para el PUT.
        - groups (dict): mapa group_id -> db_id -> permisos. Estructura por db:
            - schemas: "all" | "none" | dict de schema -> tabla -> permisos
            - native: "write" | "read" | "none" (acceso a SQL nativo)

    Example:
        >>> graph = metabase_get_permission_graph(client)
        >>> print("revision:", graph["revision"])
        >>> for group_id, dbs in graph["groups"].items():
        ...     for db_id, perms in dbs.items():
        ...         print(f"group={group_id} db={db_id}: {perms}")
    """
    return client.request("GET", "/api/permissions/graph")


def metabase_update_permission_graph(client: MetabaseClient, graph: dict) -> dict:
    """Actualiza el grafo de permisos de datos en Metabase.

    Endpoint: PUT /api/permissions/graph. Requiere superusuario.

    ## Control de concurrencia por revision

    El campo `graph["revision"]` es obligatorio y debe ser el valor actual del
    servidor. Si otro proceso modifico el graph entre tu GET y este PUT, Metabase
    devuelve HTTP 409 Conflict. El patron correcto es:

        1. graph = metabase_get_permission_graph(client)       # GET fresco
        2. Modificar graph["groups"][group_id][db_id] = ...    # editar en memoria
        3. graph = metabase_update_permission_graph(client, graph)  # PUT con revision

    Nunca cachear el graph — siempre hacer GET justo antes del PUT.

    Args:
        client: Cliente autenticado con permisos admin.
        graph: Dict con el graph completo incluyendo el campo `revision` actual.
               Obtenerlo via metabase_get_permission_graph antes de modificar.

    Returns:
        Dict con el nuevo graph tras la actualizacion, con `revision` incrementado.

    Raises:
        httpx.HTTPStatusError: 409 si el revision en el body no coincide con el actual.

    Example:
        >>> graph = metabase_get_permission_graph(client)
        >>> # Dar acceso completo al grupo 3 sobre la database 1
        >>> graph["groups"]["3"]["1"] = {"schemas": "all", "native": "write"}
        >>> updated = metabase_update_permission_graph(client, graph)
        >>> print("nueva revision:", updated["revision"])
    """
    return client.request("PUT", "/api/permissions/graph", json=graph)


# --- Collection Permission Graph ---


def metabase_get_collection_graph(
    client: MetabaseClient,
    namespace: str | None = None,
) -> dict:
    """Obtiene el grafo de permisos de colecciones de Metabase.

    Endpoint: GET /api/collection/graph. Requiere superusuario.

    El campo `revision` es CRITICO para concurrency control: el servidor rechaza
    PUT si el revision no coincide con el actual.

    Args:
        client: Cliente autenticado con permisos admin.
        namespace: Namespace opcional. "snippets" para snippet collections.
                   None = colecciones regulares.

    Returns:
        Dict con:
        - revision (int): numero de revision actual. Obligatorio para el PUT.
        - groups (dict): mapa group_id -> collection_id -> nivel de acceso.
            Nivel de acceso: "read" | "write" | "none".

    Example:
        >>> graph = metabase_get_collection_graph(client)
        >>> print("revision:", graph["revision"])
        >>> for group_id, colls in graph["groups"].items():
        ...     for coll_id, access in colls.items():
        ...         print(f"group={group_id} collection={coll_id}: {access}")
        >>> # Snippet collections:
        >>> snippet_graph = metabase_get_collection_graph(client, namespace="snippets")
    """
    params = {}
    if namespace is not None:
        params["namespace"] = namespace
    return client.request("GET", "/api/collection/graph", params=params or None)


def metabase_update_collection_graph(
    client: MetabaseClient,
    graph: dict,
    namespace: str | None = None,
) -> dict:
    """Actualiza el grafo de permisos de colecciones en Metabase.

    Endpoint: PUT /api/collection/graph. Requiere superusuario.

    ## Control de concurrencia por revision

    El campo `graph["revision"]` es obligatorio y debe ser el valor actual del
    servidor. Si otro proceso modifico el graph entre tu GET y este PUT, Metabase
    devuelve HTTP 409 Conflict. El patron correcto es:

        1. graph = metabase_get_collection_graph(client)            # GET fresco
        2. Modificar graph["groups"][group_id][collection_id] = ... # editar en memoria
        3. graph = metabase_update_collection_graph(client, graph)  # PUT con revision

    Nunca cachear el graph — siempre hacer GET justo antes del PUT.

    Args:
        client: Cliente autenticado con permisos admin.
        graph: Dict con el graph completo incluyendo el campo `revision` actual.
               Obtenerlo via metabase_get_collection_graph antes de modificar.
        namespace: Namespace opcional. "snippets" para snippet collections.
                   None = colecciones regulares.

    Returns:
        Dict con el nuevo graph tras la actualizacion, con `revision` incrementado.

    Raises:
        httpx.HTTPStatusError: 409 si el revision en el body no coincide con el actual.

    Example:
        >>> graph = metabase_get_collection_graph(client)
        >>> # Dar acceso write al grupo 3 sobre la coleccion 5
        >>> graph["groups"]["3"]["5"] = "write"
        >>> updated = metabase_update_collection_graph(client, graph)
        >>> print("nueva revision:", updated["revision"])
        >>> # Para snippet collections:
        >>> graph = metabase_get_collection_graph(client, namespace="snippets")
        >>> graph["groups"]["3"]["root"] = "write"
        >>> updated = metabase_update_collection_graph(client, graph, namespace="snippets")
    """
    params = {}
    if namespace is not None:
        params["namespace"] = namespace
    return client.request("PUT", "/api/collection/graph", json=graph, params=params or None)
