#!/usr/bin/env python3
"""
Sistema de Controlo de Acesso — ZKTeco QR50 + Raspberry Pi
-----------------------------------------------------------
Fase 1: Lê QR Code e dá alerta luminoso/sonoro
Fase 2: Consulta API para validação (preparado mas comentado)

Ligação:
  QR50 USB → Raspberry Pi USB  (modo HID Keyboard no DEMO software)

Hardware (opcional mas recomendado):
  LED Verde  → GPIO 17 (acesso permitido)
  LED Vermelho → GPIO 27 (acesso negado)
  Buzzer     → GPIO 22

Instalar dependências:
  pip install requests RPi.GPIO
"""

import sys
import time
import threading

# ── GPIO (só funciona no Raspberry Pi) ──────────────────────────────────────
try:
    import RPi.GPIO as GPIO
    GPIO_AVAILABLE = True
except ImportError:
    GPIO_AVAILABLE = False
    print("[AVISO] RPi.GPIO não encontrado — a correr em modo simulação.")

# ── Configuração dos pinos ────────────────────────────────────────────────────
PIN_LED_VERDE    = 17
PIN_LED_VERMELHO = 27
PIN_BUZZER       = 22

# ── Configuração da API (para Fase 2) ────────────────────────────────────────
API_URL     = "https://minha-api.exemplo.com/validar"
API_KEY     = "MINHA_CHAVE_SECRETA"
API_TIMEOUT = 5  # segundos


# ─────────────────────────────────────────────────────────────────────────────
# INICIALIZAÇÃO GPIO
# ─────────────────────────────────────────────────────────────────────────────
def inicializar_gpio():
    if not GPIO_AVAILABLE:
        return
    GPIO.setmode(GPIO.BCM)
    GPIO.setwarnings(False)
    GPIO.setup(PIN_LED_VERDE,    GPIO.OUT, initial=GPIO.LOW)
    GPIO.setup(PIN_LED_VERMELHO, GPIO.OUT, initial=GPIO.LOW)
    GPIO.setup(PIN_BUZZER,       GPIO.OUT, initial=GPIO.LOW)
    print("[GPIO] Pinos configurados.")


def limpar_gpio():
    if GPIO_AVAILABLE:
        GPIO.cleanup()


# ─────────────────────────────────────────────────────────────────────────────
# ALERTAS LUMINOSOS E SONOROS
# ─────────────────────────────────────────────────────────────────────────────
def _set_pin(pin, estado):
    if GPIO_AVAILABLE:
        GPIO.output(pin, estado)


def alerta_permitido():
    """LED verde acende + 1 bip curto."""
    print("[✅] ACESSO PERMITIDO")
    _set_pin(PIN_LED_VERDE, GPIO.HIGH if GPIO_AVAILABLE else 1)
    _set_pin(PIN_BUZZER,    GPIO.HIGH if GPIO_AVAILABLE else 1)
    time.sleep(0.2)
    _set_pin(PIN_BUZZER,    GPIO.LOW  if GPIO_AVAILABLE else 0)
    time.sleep(1.3)
    _set_pin(PIN_LED_VERDE, GPIO.LOW  if GPIO_AVAILABLE else 0)


def alerta_negado():
    """LED vermelho acende + 3 bips curtos."""
    print("[❌] ACESSO NEGADO")
    _set_pin(PIN_LED_VERMELHO, GPIO.HIGH if GPIO_AVAILABLE else 1)
    for _ in range(3):
        _set_pin(PIN_BUZZER, GPIO.HIGH if GPIO_AVAILABLE else 1)
        time.sleep(0.1)
        _set_pin(PIN_BUZZER, GPIO.LOW  if GPIO_AVAILABLE else 0)
        time.sleep(0.1)
    time.sleep(1.0)
    _set_pin(PIN_LED_VERMELHO, GPIO.LOW if GPIO_AVAILABLE else 0)


def alerta_erro():
    """LED vermelho pisca + bip longo."""
    print("[⚠️] ERRO DE COMUNICAÇÃO")
    for _ in range(2):
        _set_pin(PIN_LED_VERMELHO, GPIO.HIGH if GPIO_AVAILABLE else 1)
        _set_pin(PIN_BUZZER,       GPIO.HIGH if GPIO_AVAILABLE else 1)
        time.sleep(0.5)
        _set_pin(PIN_LED_VERMELHO, GPIO.LOW  if GPIO_AVAILABLE else 0)
        _set_pin(PIN_BUZZER,       GPIO.LOW  if GPIO_AVAILABLE else 0)
        time.sleep(0.2)


# ─────────────────────────────────────────────────────────────────────────────
# VALIDAÇÃO  (Fase 1: local | Fase 2: API)
# ─────────────────────────────────────────────────────────────────────────────
def validar_local(codigo_qr: str) -> bool:
    """
    Fase 1 — validação simples local.
    Substitua esta lista por base de dados ou ficheiro JSON.
    """
    codigos_validos = {
        "ACESSO-001",
        "ACESSO-002",
        "VISITANTE-2024",
    }
    return codigo_qr.strip() in codigos_validos


# def validar_api(codigo_qr: str) -> bool:
#     """
#     Fase 2 — consulta API externa.
#     Descomente quando a API estiver pronta.
#     """
#     import requests
#     try:
#         resposta = requests.post(
#             API_URL,
#             json={"qr_code": codigo_qr},
#             headers={"Authorization": f"Bearer {API_KEY}"},
#             timeout=API_TIMEOUT
#         )
#         if resposta.status_code == 200:
#             dados = resposta.json()
#             return dados.get("permitido", False)
#         return False
#     except requests.exceptions.Timeout:
#         print("[ERRO] API não respondeu a tempo.")
#         return None   # None = erro de rede
#     except requests.exceptions.RequestException as e:
#         print(f"[ERRO] Falha na API: {e}")
#         return None


def processar_qr(codigo_qr: str):
    """Recebe o código, valida e acciona o alerta correcto."""
    codigo_qr = codigo_qr.strip()
    if not codigo_qr:
        return

    print(f"\n[QR] Código lido: {codigo_qr}")

    # ── Fase 1: validação local ───────────────────────────────────────────
    resultado = validar_local(codigo_qr)

    # ── Fase 2: descomentar a linha abaixo e comentar a de cima ──────────
    # resultado = validar_api(codigo_qr)

    # Accionar alerta em thread separada (não bloqueia a leitura)
    if resultado is True:
        threading.Thread(target=alerta_permitido, daemon=True).start()
    elif resultado is False:
        threading.Thread(target=alerta_negado, daemon=True).start()
    else:
        threading.Thread(target=alerta_erro, daemon=True).start()


# ─────────────────────────────────────────────────────────────────────────────
# LEITURA DO QR50 via USB HID (modo teclado)
# ─────────────────────────────────────────────────────────────────────────────
def ler_qr50_stdin():
    """
    O QR50 em modo HID Keyboard envia o código como teclas
    seguido de ENTER. Lemos linha a linha via stdin.

    Execução:
        python3 qr_access_control.py

    Para testar sem o leitor físico, basta digitar o código
    no terminal e carregar ENTER.
    """
    print("=" * 50)
    print("  Sistema de Controlo de Acesso — QR50")
    print("  Aguardando leitura de QR Code...")
    print("=" * 50)

    try:
        while True:
            linha = input()          # bloqueia até receber ENTER do QR50
            if linha:
                processar_qr(linha)
    except KeyboardInterrupt:
        print("\n[INFO] Sistema encerrado pelo utilizador.")
    finally:
        limpar_gpio()


# ─────────────────────────────────────────────────────────────────────────────
# ENTRADA PRINCIPAL
# ─────────────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
    inicializar_gpio()
    ler_qr50_stdin()
