#!/usr/bin/env python3
"""
Prueba de conexión a relojes Anviz desde el servidor.
Uso:
  cd /var/www/reloj-farallon
  ./venv/bin/pip install -r requirements.txt
  ./venv/bin/python3 scripts/test_relojes.py
"""
from __future__ import annotations

import socket
import struct
import sys
from pathlib import Path

ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT))

try:
    from app.anviz_client import (
        STX,
        AnvizDevice,
        AnvizError,
        crc16,
        encode_comm_password,
        encode_comm_password_bcd,
    )
    from app.devices import load_relojes
except ModuleNotFoundError as exc:
    print("ERROR: faltan dependencias de Python.\n")
    print(f"  cd {ROOT}")
    print("  python3 -m venv venv")
    print("  ./venv/bin/pip install -r requirements.txt")
    print("  ./venv/bin/python3 scripts/test_relojes.py\n")
    print(f"Detalle: {exc}")
    sys.exit(1)

CMD_GET_DATETIME = 0x38


def test_tcp(host: str, port: int, timeout: float = 5.0) -> tuple[bool, str]:
    try:
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.settimeout(timeout)
        sock.connect((host, port))
        sock.close()
        return True, "Puerto TCP abierto"
    except socket.timeout:
        return False, "Timeout — no responde en ese puerto"
    except OSError as exc:
        return False, str(exc)


def raw_probe(
    host: str,
    port: int,
    device_id: int,
    password_bytes: bytes | None,
    timeout: float = 6.0,
) -> tuple[bool, str]:
    """Envía GET datetime y muestra si hay respuesta (hex)."""
    data = password_bytes or b""
    req = bytearray([STX])
    req.extend(struct.pack(">L", device_id))
    req.append(CMD_GET_DATETIME)
    req.extend(struct.pack(">H", len(data)))
    if data:
        req.extend(data)
    req.extend(crc16(req))
    packet = bytes(req)

    try:
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.settimeout(timeout)
        sock.connect((host, port))
        sock.send(packet)
        resp = sock.recv(256)
        sock.close()
    except socket.timeout:
        return False, f"sin respuesta (enviado: {packet.hex()})"
    except OSError as exc:
        return False, str(exc)

    if not resp:
        return False, f"0 bytes recibidos (enviado: {packet.hex()})"
    if resp[0] != STX:
        return False, f"respuesta no Anviz: {resp.hex()}"

    if len(resp) >= 8:
        dev_id = struct.unpack(">L", resp[1:5])[0]
        ret = resp[7]
        return True, f"respuesta OK ({len(resp)} bytes), device_id en respuesta={dev_id}, ret=0x{ret:02x}"

    return True, f"respuesta parcial: {resp.hex()}"


def try_protocol_variants(host: str, port: int, device_id: int, comm_password: str | None) -> tuple[bool, str]:
    """Prueba combinaciones de device_id y password."""
    pwd = (comm_password or "").strip()
    device_ids = []
    for d in (device_id, 5, 0, 1):
        if d not in device_ids:
            device_ids.append(d)

    pw_modes: list[tuple[str, bytes | None]] = [("sin_password", None)]
    if pwd:
        pw_modes.append(("password_numerica", encode_comm_password(pwd)))
        pw_modes.append(("password_bcd", encode_comm_password_bcd(pwd)))

    errors: list[str] = []
    for dev_id in device_ids:
        for pw_label, pw_bytes in pw_modes:
            label = f"device_id={dev_id}, {pw_label}"
            ok, msg = raw_probe(host, port, dev_id, pw_bytes)
            if ok:
                return True, f"{label} → {msg}"

            device = AnvizDevice(
                device_id=dev_id,
                host=host,
                port=port,
                timeout=8.0,
                comm_password=None,
            )
            device._comm_password_bytes = pw_bytes
            try:
                with device:
                    info = device._ping_once()
                return True, f"{label} → hora {info.get('device_time')}"
            except Exception as exc:
                errors.append(f"{label}: {exc}")

    return False, errors[0] if errors else "sin variantes"


def main() -> int:
    print("=" * 60)
    print("Diagnóstico relojes Anviz")
    print("=" * 60)

    try:
        relojes = load_relojes()
    except Exception as exc:
        print(f"\nERROR cargando relojes.json: {exc}")
        return 1

    print(f"\nRelojes configurados: {len(relojes)}\n")
    ok_count = 0

    for cfg in relojes:
        print(f"--- {cfg.name} ({cfg.id}) ---")
        print(f"    Host: {cfg.host}:{cfg.port}")
        print(f"    device_id (pantalla): {cfg.device_id}")
        print(f"    comm_password: {'***' if cfg.comm_password else '(no)'}")

        if not cfg.enabled:
            print("  [ ] Omitido — enabled: false en relojes.json")
            print()
            continue

        print(f"  [2] TCP {cfg.port}...", end=" ")
        tcp_ok, tcp_msg = test_tcp(cfg.host, cfg.port)
        print(tcp_msg)
        if not tcp_ok:
            if "No route to host" in tcp_msg or "113" in tcp_msg:
                print("    → El servidor NO tiene ruta a esa IP (cable, VLAN, IP incorrecta o reloj apagado)")
            else:
                print("    → Revise red, firewall, modo Server, puerto 5010")
            print()
            continue

        print("  [3] Probando protocolo (varias combinaciones)...")
        ok, detail = try_protocol_variants(
            cfg.host, cfg.port, cfg.device_id, cfg.comm_password
        )
        if ok:
            print(f"    OK — {detail}")
            print("    → Actualice relojes.json con el device_id y modo que funcionaron")
            ok_count += 1
        else:
            print(f"    FALLO — {detail}")
            print("    Posibles causas:")
            print("      - CrossChex u otro programa tiene el reloj ocupado (cierre esa conexión)")
            print("      - Comm.PW: pruebe OFF en el reloj solo para diagnosticar")
            print("      - device_id del menú del reloj no coincide (no es el número de empleados)")
        print()

    print("=" * 60)
    print(f"Resultado: {ok_count}/{len(relojes)} relojes con protocolo OK")
    print("=" * 60)
    return 0 if ok_count == len(relojes) else 1


if __name__ == "__main__":
    sys.exit(main())
