#!/usr/bin/env python3
"""
Sonda de la API de ISPGestion.

Sirve para descubrir que columnas devuelve realmente /api/clientes, ya que el
manual "API ISPGESTION BASICA" solo documenta los parametros de ENTRADA de
alta/modificacion, no la salida de las consultas.

Uso: rellena HOST / USER / PASS aqui abajo y lanza:

    python ispgestion_probe.py campos            # lista las columnas de un cliente
    python ispgestion_probe.py todos             # cuenta clientes y muestra 1 de ejemplo
    python ispgestion_probe.py codigo 000001     # cliente por codigo
    python ispgestion_probe.py fiscal 00000000T  # cliente por NIF
    python ispgestion_probe.py nombre jose perez # busqueda por nombre

Nada de esto escribe en ISPGestion: solo hace GET.
"""

import os
import sys

import requests
from requests.auth import HTTPBasicAuth

# ============================================================
# CONFIGURACION
# ============================================================
# Las credenciales NO se escriben aqui. Se leen del entorno, asi no quedan
# guardadas en disco ni se cuelan en copias del fichero.
#
# En PowerShell, antes de lanzar el script:
#     $env:ISPG_HOST = "xxxxx.ispgestion.com"
#     $env:ISPG_USER = "xxxxx"
#     $env:ISPG_PASS = "xxxxx"
#
# Duran solo lo que dure esa ventana de terminal.

def _cargar_dotenv():
    """Mismo cargador que usa bot.py. Busca .env junto al script y, si no,
    en el directorio desde el que se lanza."""
    candidatos = [
        os.path.join(os.path.dirname(os.path.abspath(__file__)), ".env"),
        os.path.join(os.getcwd(), ".env"),
    ]
    for ruta in candidatos:
        try:
            with open(ruta) as f:
                for linea in f:
                    linea = linea.strip()
                    if not linea or linea.startswith("#") or "=" not in linea:
                        continue
                    k, v = linea.split("=", 1)
                    os.environ.setdefault(k.strip(), v.strip().strip('"').strip("'"))
            return ruta
        except IOError:
            continue
    return None

_ENV_USADO = _cargar_dotenv()

HOST = os.environ.get("ISPG_HOST", "")
USER = os.environ.get("ISPG_USER", "")
PASS = os.environ.get("ISPG_PASS", "")


def check_config():
    faltan = [n for n, v in (("ISPG_HOST", HOST), ("ISPG_USER", USER), ("ISPG_PASS", PASS)) if not v]
    if faltan:
        sys.exit(
            "Faltan estas claves: " + ", ".join(faltan) + "\n\n"
            ".env leido: " + str(_ENV_USADO) + "\n\n"
            "Anadelas al .env (sin espacios alrededor del =):\n"
            "    ISPG_HOST=xxxxx.ispgestion.com\n"
            "    ISPG_USER=xxxxx\n"
            "    ISPG_PASS=xxxxx"
        )
    print("Credenciales leidas de: " + str(_ENV_USADO or "entorno"))


def _peticion(url, **kw):
    r = requests.get(url, timeout=30, **kw)
    print("HTTP {}  ({} bytes)".format(r.status_code, len(r.content)))
    if r.status_code == 401:
        # Puede venir el motivo en la cabecera, util para distinguir
        # "clave mala" de "el endpoint pide otro tipo de auth"
        cab = r.headers.get("WWW-Authenticate")
        if cab:
            print("WWW-Authenticate: {}".format(cab))
    cuerpo = r.text.strip()
    if cuerpo:
        print("Cuerpo: {!r}".format(cuerpo[:500]))
    else:
        print("Cuerpo vacio")
    return r


def get(path):
    """Prueba primero Basic auth; si da 401, reintenta con username/password en la query."""
    url = "https://{}/api/{}".format(HOST, path)

    print("\n[1] Basic auth  ->  GET https://{}/api/{}".format(HOST, path))
    r = _peticion(url, auth=HTTPBasicAuth(USER, PASS))

    if r.status_code == 401:
        print("\n[2] Credenciales en la query  ->  GET https://{}/api/{}?username=...&password=...".format(HOST, path))
        r = _peticion(url, params={"username": USER, "password": PASS})

    if r.status_code == 401:
        print("\nLos dos modos dan 401. Revisa:")
        print("  - que el usuario/clave sean los de la API, no los del acceso web")
        print("  - que ese usuario tenga permiso de API dado de alta en ISPGestion")
        print("  - si ISPGestion filtra por IP de origen (habria que dar de alta la tuya)")
        print("  - NO repitas el intento muchas veces seguidas, no vayas a bloquear la cuenta")
        return None

    if not r.ok:
        return None

    try:
        return r.json()
    except ValueError:
        # La API devuelve una cadena suelta cuando hay error
        print("La respuesta no es JSON (ver cuerpo arriba).")
        return None


def primer_registro(data):
    """La API devuelve un array bidimensional; saca el primer elemento."""
    if isinstance(data, list) and data:
        return data[0]
    if isinstance(data, dict):
        for v in data.values():
            if isinstance(v, list) and v:
                return v[0]
        return data
    return None


def _describe(v):
    """Describe un valor SIN revelarlo: tipo, si viene vacio y su longitud."""
    if v is None:
        return "null"
    if isinstance(v, bool):
        return "bool"
    if isinstance(v, (int, float)):
        return "{} ({} digitos)".format(type(v).__name__, len(str(v)))
    if isinstance(v, str):
        if v.strip() == "":
            return "str VACIO"
        return "str ({} chars)".format(len(v))
    if isinstance(v, list):
        return "list ({} elementos)".format(len(v))
    if isinstance(v, dict):
        return "dict ({} claves)".format(len(v))
    return type(v).__name__


def mostrar_campos(reg):
    """Imprime SOLO nombres de campo y tipo. Nunca el contenido."""
    if not isinstance(reg, dict):
        print("Registro inesperado (tipo {}). No se muestra el contenido.".format(type(reg).__name__))
        return
    print("\n{} campos (solo nombre y tipo, sin valores):\n".format(len(reg)))
    ancho = max(len(k) for k in reg)
    for k, v in reg.items():
        print("  {}  :  {}".format(k.ljust(ancho), _describe(v)))

    # Resalta lo que nos interesa para el bot de alertas
    claves = ("telefono", "movil", "prefijo", "mail", "nombre", "apellido",
              "codigo", "clienteid", "fiscal", "nif", "domicilio")
    hits = [k for k in reg if any(c in k.lower() for c in claves)]
    if hits:
        print("\nCampos utiles para el bot:")
        for k in hits:
            print("  {}  :  {}".format(k, _describe(reg[k])))


def main():
    check_config()
    args = sys.argv[1:]
    if not args:
        sys.exit(__doc__)

    cmd = args[0].lower()

    if cmd in ("campos", "todos"):
        data = get("clientes")
        if data is None:
            return
        if isinstance(data, list):
            print("Clientes devueltos: {}".format(len(data)))
        reg = primer_registro(data)
        mostrar_campos(reg)

    elif cmd == "codigo":
        if len(args) < 2:
            sys.exit("Falta el codigo. Ej: python ispgestion_probe.py codigo 000001")
        data = get("clientes/{}".format(args[1]))
        if data is not None:
            mostrar_campos(primer_registro(data))

    elif cmd == "fiscal":
        if len(args) < 2:
            sys.exit("Falta el NIF. Ej: python ispgestion_probe.py fiscal 00000000T")
        data = get("clientesfiscal/{}".format(args[1]))
        if data is not None:
            mostrar_campos(primer_registro(data))

    elif cmd == "nombre":
        if len(args) < 2:
            sys.exit("Falta el nombre. Ej: python ispgestion_probe.py nombre jose perez")
        # El manual pide sustituir espacios por guion bajo
        nombre = "_".join(args[1:])
        data = get("clientesnombre/p/{}".format(nombre))
        if data is None:
            return
        if isinstance(data, list):
            print("Coincidencias: {}".format(len(data)))
        mostrar_campos(primer_registro(data))

    else:
        sys.exit("Comando desconocido: {}\n{}".format(cmd, __doc__))


if __name__ == "__main__":
    main()
