from __future__ import annotations

from typing import Any, Dict, List, Tuple, Optional
from urllib.parse import urlencode

from ..base import ZoneOperation, ParamSpec


def _request(
    client: Any,
    method: str,
    params_list: List[Tuple[str, str]],
    *,
    data: Any = None,
    headers: Dict[str, str] | None = None,
) -> Any:
    var_string = urlencode(params_list)
    return client.request(
        "",
        method=method,
        params=params_list,  # lista para permitir where repetidos si hace falta
        var_string=var_string,
        data=data,
        headers=headers or {},
    )


def _raw_wrap(resp: Any, params_list: List[Tuple[str, str]]) -> Dict[str, Any]:
    return {
        "data": resp,
        "meta": {
            "params": params_list,
            "var_string": urlencode(params_list),
        },
    }


def _compact_client_obj(c: Dict[str, Any]) -> Dict[str, Any]:
    """
    Compactación estable para AI/CLI:
    conserva identificadores y campos típicos de búsqueda/UX.
    Si hay joins, conserva contadores o preview pequeño.
    """
    out = {
        "clientid": c.get("clientid"),
        "clientname": c.get("clientname"),
        "shortname": c.get("shortname"),
        "firstname": c.get("firstname"),
        "surname": c.get("surname"),
        "email": c.get("email"),
        "phone": c.get("phone"),
        "mobile": c.get("mobile"),
        "abn": c.get("abn"),
        "postable": c.get("postable"),
        "archived": c.get("archived"),
        "lastupdatedutc": c.get("lastupdatedutc"),
        "lastupdateddatetimeutc": c.get("lastupdateddatetimeutc"),
        "datecreated": c.get("datecreated"),
    }

    # joins comunes (si vienen)
    orgs = c.get("orgs")
    if isinstance(orgs, list):
        out["orgs_count"] = len(orgs)
        out["orgs_preview"] = [
            {"orgid": o.get("orgid"), "orgname": o.get("orgname"), "archived": o.get("archived")}
            for o in orgs[:3]
            if isinstance(o, dict)
        ]

    contacts = c.get("contacts")
    if isinstance(contacts, list):
        out["contacts_count"] = len(contacts)
        out["contacts_preview"] = [
            {
                "userid": u.get("userid"),
                "givennames": u.get("givennames"),
                "surname": u.get("surname"),
                "email": u.get("email"),
                "mobile": u.get("mobile"),
                "phone": u.get("phone"),
                "archived": u.get("archived"),
            }
            for u in contacts[:3]
            if isinstance(u, dict)
        ]

    locations = c.get("locations")
    if isinstance(locations, list):
        out["locations_count"] = len(locations)

    customfields = c.get("customfields")
    if isinstance(customfields, list):
        out["customfields_count"] = len(customfields)

    return out


def _compact_clients_response(resp: Any, *, max_items: int = 20) -> Any:
    """
    Recorta y compacta SOLO si reconoce la estructura típica:
    resp["zoneresponse"]["clients"].
    """
    if not isinstance(resp, dict):
        return resp
    zr = resp.get("zoneresponse")
    if not isinstance(zr, dict):
        return resp
    clients = zr.get("clients")
    if not isinstance(clients, list):
        return resp

    trimmed = clients[: max(1, int(max_items or 20))]
    zr2 = dict(zr)
    zr2["clients"] = [_compact_client_obj(c) for c in trimmed if isinstance(c, dict)]
    zr2["compact_applied"] = True
    zr2["compact_max_items"] = len(zr2["clients"])
    zr2["original_items_hint"] = zr.get("currentpageresults")  # aroflo suele mandar esto

    out = dict(resp)
    out["zoneresponse"] = zr2
    return out


def _build_reset_postable_xml(client_ids: List[str]) -> str:
    items = []
    for cid in client_ids:
        cid = (cid or "").strip()
        if not cid:
            continue
        items.append(f"<client><clientid>{cid}</clientid><postable>false</postable></client>")
    return f"<clients>{''.join(items)}</clients>"


def _add_opt(params_list: List[Tuple[str, str]], key: str, value: Optional[Any]) -> None:
    if value is None:
        return
    s = str(value).strip()
    if s == "":
        return
    params_list.append((key, s))


def get_operations() -> List[ZoneOperation]:
    return [
        ZoneOperation(
            code="list_clients",
            label="List Clients",
            description="Lista clients paginado (zone=clients). WHERE es opcional. Por defecto aplica filtro default del servidor.",
            http_method="GET",
            side_effect="read",
            idempotent=True,
            default_params={
                "where": None,
                "page": 1,
                "page_size": None,   # si None, NO enviamos maxpageresults (AroFlo decide)
                "compact": True,
                "max_items": 20,
                "raw": False,
            },
            params=[
                ParamSpec("where", "string", False, "Cláusula WHERE estilo AroFlo (opcional)."),
                ParamSpec("page", "integer", False, "Número de página (1..N)."),
                ParamSpec("page_size", "integer", False, "maxpageresults (opcional). Si no se envía, AroFlo usa su default."),
                ParamSpec("compact", "boolean", False, "Si true, compacta y recorta localmente para outputs controlados."),
                ParamSpec("max_items", "integer", False, "Recorte local de clients[] cuando compact=true."),
                ParamSpec("raw", "boolean", False, "Si true, devuelve respuesta cruda + meta (sin compactación)."),
            ],
            category="clients",
            use_cases=["Listar clients", "Filtrar clients usando WHERE"],
            risk_level="low",
            requires_confirmation=False,
        ),
        ZoneOperation(
            code="get_client",
            label="Get Client",
            description="Obtiene un client específico filtrando por clientid (WHERE).",
            http_method="GET",
            side_effect="read",
            idempotent=True,
            default_params={"raw": False, "compact": True},
            params=[
                ParamSpec("clientid", "string", True, "ClientID codificado en AroFlo."),
                ParamSpec("raw", "boolean", False, "Si true, devuelve respuesta cruda + meta."),
                ParamSpec("compact", "boolean", False, "Si true, compacta el objeto devuelto."),
            ],
            category="clients",
            use_cases=["Consultar detalle de un client por clientid"],
            risk_level="low",
            requires_confirmation=False,
        ),
        ZoneOperation(
            code="get_postable_updated_clients",
            label="Get Postable Updated Clients",
            description="Devuelve clients con postable=true (incremental). Luego típicamente se resetea postable=false vía POST.",
            http_method="GET",
            side_effect="read",
            idempotent=True,
            default_params={"page": 1, "page_size": None, "compact": True, "max_items": 20, "raw": False},
            params=[
                ParamSpec("page", "integer", False, "Número de página (1..N)."),
                ParamSpec("page_size", "integer", False, "maxpageresults (opcional)."),
                ParamSpec("compact", "boolean", False, "Compacta y recorta localmente."),
                ParamSpec("max_items", "integer", False, "Recorte local de clients[] cuando compact=true."),
                ParamSpec("raw", "boolean", False, "Si true, devuelve respuesta cruda + meta."),
            ],
            category="clients",
            use_cases=["Sync incremental de clients usando postable=true"],
            risk_level="low",
            requires_confirmation=False,
        ),
        ZoneOperation(
            code="reset_postable_flags",
            label="Reset Postable Flags",
            description="Resetea postable=false para una lista de clientids (side-effect).",
            http_method="POST",
            side_effect="write",
            idempotent=False,
            default_params={"raw": False},
            params=[
                ParamSpec(
                    "client_ids",
                    "object",
                    True,
                    "Objeto con lista de IDs. Formato: {\"ids\": [\"id1\",\"id2\", ...]}",
                ),
                ParamSpec("raw", "boolean", False, "Si true, devuelve respuesta cruda + meta."),
            ],
            category="clients",
            use_cases=["Resetear postable después de sync incremental"],
            risk_level="medium",
            requires_confirmation=True,
        ),
    ]


def supports(operation_code: str) -> bool:
    return any(op.code == operation_code for op in get_operations())


def execute(operation_code: str, client: Any, params: Dict[str, Any]) -> Any:
    raw = bool(params.get("raw", False))

    # compactación por defecto (para agente/CLI)
    compact = bool(params.get("compact", True))
    max_items = int(params.get("max_items", 20) or 20)

    # page_size opcional: si None, NO enviamos maxpageresults
    page_size = params.get("page_size", None)
    page_size_str = str(int(page_size)) if page_size is not None else None

    if operation_code == "list_clients":
        page = str(params.get("page", 1))
        where = params.get("where", None)

        params_list: List[Tuple[str, str]] = [("zone", "clients"), ("page", page)]
        if where:
            params_list.append(("where", str(where)))
        _add_opt(params_list, "maxpageresults", page_size_str)

        resp = _request(client, "GET", params_list)
        if raw:
            return _raw_wrap(resp, params_list)
        return _compact_clients_response(resp, max_items=max_items) if compact else resp

    if operation_code == "get_client":
        clientid = params["clientid"]
        where = f"and|clientid|=|{clientid}"

        params_list: List[Tuple[str, str]] = [("zone", "clients"), ("where", where), ("page", "1")]
        # no forzamos maxpageresults=1: AroFlo puede devolver 1 igual, y si no, compact reduce
        resp = _request(client, "GET", params_list)

        if raw:
            return _raw_wrap(resp, params_list)
        return _compact_clients_response(resp, max_items=1) if compact else resp

    if operation_code == "get_postable_updated_clients":
        page = str(params.get("page", 1))
        where = "and|postable|=|true"

        params_list: List[Tuple[str, str]] = [("zone", "clients"), ("where", where), ("page", page)]
        _add_opt(params_list, "maxpageresults", page_size_str)

        resp = _request(client, "GET", params_list)
        if raw:
            return _raw_wrap(resp, params_list)
        return _compact_clients_response(resp, max_items=max_items) if compact else resp

    if operation_code == "reset_postable_flags":
        obj = params.get("client_ids") or {}
        ids = obj.get("ids") if isinstance(obj, dict) else None
        if not isinstance(ids, list) or not ids:
            raise ValueError("client_ids debe ser {\"ids\": [\"id1\", ...]} con al menos un id.")

        xml_payload = _build_reset_postable_xml([str(x) for x in ids])

        params_list: List[Tuple[str, str]] = [("zone", "clients")]
        headers = {"Content-Type": "application/x-www-form-urlencoded", "Accept": "text/json"}
        form_body = {"postxml": xml_payload}  # AroFlo espera postxml

        resp = _request(client, "POST", params_list, data=form_body, headers=headers)
        if raw:
            # no devolvemos el XML completo en meta (evita outputs gigantes)
            return _raw_wrap(resp, params_list + [("postxml", "[xml omitted]")])
        return resp

    raise ValueError(f"[Clients.base] Operación no soportada: {operation_code}")
