"""Pipeline: crea (si faltan) y añade al Permission Group una lista de usuarios.

Compone: metabase_list_groups + metabase_list_users + metabase_get_group
         + metabase_create_user + metabase_add_membership.

Reduce 22 llamadas para 9 usuarios a una sola llamada one-shot idempotente.
"""

import sys
import os

sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))

import httpx
from core import clean_email
from metabase import (
    MetabaseClient,
    metabase_list_groups,
    metabase_list_users,
    metabase_get_group,
    metabase_create_user,
    metabase_add_membership,
)


def metabase_bulk_add_users_to_group(
    client: MetabaseClient,
    group: int | str,
    targets: list[dict],
    send_reset_existing: bool = False,
) -> list[dict]:
    """Crea (si faltan) y añade al Permission Group dado una lista de usuarios.

    Idempotente: si el usuario ya existe, no lo crea; si ya es miembro, no lo
    re-añade. Soporta resolver el grupo por id (int) o por nombre (str,
    substring case-insensitive).

    Args:
        client: MetabaseClient autenticado (admin/superuser).
        group: group_id (int) o nombre del grupo (str, substring
               case-insensitive). Debe matchear exactamente 1 grupo.
        targets: lista de dicts con keys first_name, last_name, email.
                 El email se normaliza via clean_email (strip whitespace,
                 angle brackets `<...>`, comillas, lowercase). Si el raw
                 es invalido, esa fila sale como status=create_failed sin
                 hacer ninguna llamada a Metabase.
        send_reset_existing: si True, reenvia POST /api/session/forgot_password
                             a los usuarios ya existentes (no a los recien
                             creados, que ya recibieron invitacion).

    Returns:
        Lista de dicts, uno por target, en el mismo orden que targets:
        {
            "email": str,
            "user_id": int | None,
            "status": "created" | "existing" | "create_failed",
            "membership": "added" | "already_member" | "membership_failed" | "skipped",
            "reset_sent": bool | None,
            "error": str | None,
        }

    Raises:
        ValueError: si group es str y no matchea exactamente 1 grupo.
        KeyError: si algun dict de targets no tiene first_name, last_name o email.
    """
    # 1. Resolver group_id
    if isinstance(group, int):
        group_id = group
    else:
        groups = metabase_list_groups(client)
        needle = group.lower()
        matches = [g for g in groups if needle in g["name"].lower()]
        if len(matches) == 0:
            raise ValueError(
                f"No group found matching '{group}'. "
                f"Available groups: {[g['name'] for g in groups]}"
            )
        if len(matches) > 1:
            raise ValueError(
                f"Ambiguous group name '{group}': matched {len(matches)} groups: "
                + ", ".join(f"{g['id']}:{g['name']}" for g in matches)
            )
        group_id = matches[0]["id"]

    # 2. Snapshot usuarios existentes (email -> user dict)
    users_response = metabase_list_users(client, status="all", limit=1000)
    # metabase_list_users devuelve dict con key 'data'
    users_data = users_response.get("data", users_response) if isinstance(users_response, dict) else users_response
    by_email: dict[str, dict] = {u["email"].lower(): u for u in users_data}

    # 3. Snapshot miembros actuales del grupo
    group_info = metabase_get_group(client, group_id)
    member_ids: set[int] = {m["user_id"] for m in group_info.get("members", [])}

    # 4. Procesar cada target
    results: list[dict] = []

    for target in targets:
        first_name = target["first_name"]
        last_name = target["last_name"]
        raw_email = target["email"]

        row: dict = {
            "email": raw_email,
            "user_id": None,
            "status": None,
            "membership": None,
            "reset_sent": None,
            "error": None,
        }

        # Normalizar email (strip whitespace, <...>, comillas, lowercase).
        # Si el raw es invalido (no parseable como email), saltar con error.
        try:
            email = clean_email(raw_email)
        except ValueError as exc:
            row["status"] = "create_failed"
            row["membership"] = "skipped"
            row["error"] = str(exc)
            results.append(row)
            continue
        row["email"] = email
        email_lower = email  # ya lowercase tras clean_email

        # Determinar si el usuario ya existe
        if email_lower in by_email:
            uid = by_email[email_lower]["id"]
            row["user_id"] = uid
            row["status"] = "existing"
        else:
            try:
                created = metabase_create_user(client, first_name, last_name, email)
                uid = created["id"]
                row["user_id"] = uid
                row["status"] = "created"
                # Actualizar snapshot para idempotencia dentro del mismo batch
                by_email[email_lower] = created
            except Exception as exc:
                row["status"] = "create_failed"
                row["membership"] = "skipped"
                row["error"] = str(exc)
                results.append(row)
                continue

        # Determinar membresia
        if uid in member_ids:
            row["membership"] = "already_member"
        else:
            try:
                metabase_add_membership(client, uid, group_id)
                row["membership"] = "added"
                member_ids.add(uid)
            except Exception as exc:
                row["membership"] = "membership_failed"
                row["error"] = str(exc)

        # Reset email (solo para usuarios existentes, no recien creados)
        if send_reset_existing and row["status"] == "existing" and row["error"] is None:
            try:
                resp = httpx.post(
                    f"{client.base_url}/api/session/forgot_password",
                    json={"email": email},
                    timeout=10,
                )
                if resp.status_code == 204:
                    row["reset_sent"] = True
                else:
                    row["reset_sent"] = False
                    msg = f"forgot_password returned {resp.status_code}"
                    row["error"] = msg
            except Exception as exc:
                row["reset_sent"] = False
                row["error"] = f"forgot_password error: {exc}"

        results.append(row)

    return results
