# rfid_sdk_lite.py
import ctypes
import os
import sys
import struct
import time
from typing import Optional, List, Tuple

# --- CONFIGURAÇÃO DA DLL E FUNÇÕES ---
dll_name = "UHFRFID.dll"

# Tentativa de carregar a DLL
try:
    if hasattr(sys, '_MEIPASS'):
        dll_path = os.path.join(sys._MEIPASS, dll_name)
    else:
        # Assumindo que a DLL está na mesma pasta do script
        dll_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), dll_name)
    
    # O objeto rfid_sdk é a ponte para as funções da DLL
    rfid_sdk = ctypes.CDLL(dll_path)
    
    # Mapeamento de Funções da DLL (essenciais)
    rfid_sdk.UHF_RFID_Open.argtypes = [ctypes.c_ubyte, ctypes.c_int]; rfid_sdk.UHF_RFID_Open.restype = ctypes.c_int
    rfid_sdk.UHF_RFID_Close.argtypes = [ctypes.c_ubyte]; rfid_sdk.UHF_RFID_Close.restype = ctypes.c_int
    rfid_sdk.UHF_RFID_Set.argtypes = [ctypes.c_int, ctypes.c_char_p, ctypes.c_uint, ctypes.c_char_p, ctypes.POINTER(ctypes.c_uint)]; rfid_sdk.UHF_RFID_Set.restype = ctypes.c_int

    # Comandos da DLL
    RFID_CMD_SET_TXPOWER = 0x10
    RFID_CMD_SET_FREQ_TABLE = 0x14
    RFID_CMD_INV_TAG = 0x80
    
    BAUD_RATE = 115200
    DEFAULT_MAX_POWER_DBM = 25
    DEFAULT_MIN_POWER_DBM = 5

    DLL_LOADED = True
    print(f"✅ RFID DLL '{dll_name}' carregada com sucesso.")
except OSError as e:
    print(f"❌ ERRO CRÍTICO: Não foi possível carregar a DLL RFID '{dll_name}'. Razão: {e}")
    print("Certifique-se de que a DLL está na mesma pasta do script ou que o caminho está correto.")
    rfid_sdk = None
    DLL_LOADED = False


def open_rfid_reader(com_port_num: int) -> bool:
    """Abre a porta COM para o leitor RFID."""
    if not DLL_LOADED: return False
    
    com_byte = ctypes.c_ubyte(com_port_num)
    ret = rfid_sdk.UHF_RFID_Open(com_byte, BAUD_RATE)
    
    if ret == 0:
        print(f"✅ Leitor RFID na COM{com_port_num} aberto.")
        return True
    else:
        print(f"❌ Falha ao abrir a porta COM{com_port_num} para o Leitor RFID (Código: {ret}).")
        return False

def close_rfid_reader(com_port_num: int) -> None:
    """Fecha a porta COM do leitor RFID."""
    if not DLL_LOADED: return
    com_byte = ctypes.c_ubyte(com_port_num)
    rfid_sdk.UHF_RFID_Close(com_byte)
    print(f"✅ Leitor RFID na COM{com_port_num} fechado.")

# --- UTILITÁRIOS DE INVENTÁRIO / EPC ---
def _buffer_to_hex(buf: ctypes.c_char_p, length: int) -> str:
    try:
        raw = ctypes.string_at(buf, length)
        return raw.hex().upper()
    except Exception:
        return ""

def parse_epc_and_eoc_from_frame(buf: ctypes.Array, length: int) -> Tuple[Optional[str], Optional[str]]:
    """Extrai EPC e EOC (24 hex) como o FastSurance: EPC = bytes[2:len-3]. EOC = últimos 24 hex do EPC."""
    try:
        data = bytes(ctypes.string_at(buf, length))
        if length <= 5:
            return None, None
        epc_bytes = data[2:length-3]
        epc_hex = epc_bytes.hex().upper()
        eoc_hex = epc_hex[-24:] if len(epc_hex) >= 24 else None
        return epc_hex, eoc_hex
    except Exception:
        return None, None

def inventory_once(com_port_num: int) -> Optional[str]:
    """Executa um inventário simples e retorna o EPC (hex) se houver."""
    if not DLL_LOADED: return None
    output_buffer, output_len = ctypes.create_string_buffer(256), ctypes.c_uint(0)
    inv_tag_input_data = bytes([0x00, 0x1E])
    ret = rfid_sdk.UHF_RFID_Set(RFID_CMD_INV_TAG, ctypes.c_char_p(inv_tag_input_data), 2, output_buffer, ctypes.byref(output_len))
    if ret == 0 and output_len.value > 5:
        epc_hex, _ = parse_epc_and_eoc_from_frame(output_buffer, output_len.value)
        return epc_hex
    return None

def inventory_scan(com_port_num: int, attempts: int = 10, delay_s: float = 0.05) -> Optional[str]:
    """Tenta múltiplos inventários e devolve o EPC completo (hex) detectado."""
    if not DLL_LOADED: return None
    for _ in range(attempts):
        output_buffer, output_len = ctypes.create_string_buffer(256), ctypes.c_uint(0)
        inv_tag_input_data = bytes([0x00, 0x1E])
        ret = rfid_sdk.UHF_RFID_Set(RFID_CMD_INV_TAG, ctypes.c_char_p(inv_tag_input_data), 2, output_buffer, ctypes.byref(output_len))
        if ret == 0 and output_len.value > 5:
            epc_hex, eoc_hex = parse_epc_and_eoc_from_frame(output_buffer, output_len.value)
            if epc_hex:
                print(f"📋 EPC registrado: EPC completo={epc_hex} (EOC últimos 24={eoc_hex if eoc_hex else 'N/A'})")
                return epc_hex
        time.sleep(delay_s)
    print(f"⚠️ Nenhum EPC detectado após {attempts} tentativas")
    return None

# IMPLEMENTAÇÃO COM HARD TIMEOUT PARA EVITAR TRAVAMENTO
def measure_threshold(com_port_num: int, frequency: float, filter_epc_hex: Optional[str] = None) -> Optional[float]:
    """
    Executa a varredura de potência para encontrar o threshold de uma tag em uma frequência.
    Retorna o valor do threshold em dBm ou uma string indicando fora da faixa, ou None em caso de timeout/falha.
    """
    if not DLL_LOADED: return None

    if filter_epc_hex:
        print(f"🎯 Iniciando threshold com filtro EPC: '{filter_epc_hex}' (tamanho: {len(filter_epc_hex)})")
    else:
        print(f"🎯 Iniciando threshold sem filtro")

    # Configura frequência
    freq_data = b'\x01' + int(frequency * 1000).to_bytes(3, 'big')
    output_buffer, output_len = ctypes.create_string_buffer(256), ctypes.c_uint(0)
    rfid_sdk.UHF_RFID_Set(RFID_CMD_SET_FREQ_TABLE, ctypes.c_char_p(freq_data), 4, output_buffer, ctypes.byref(output_len))

    start_power_for_run = DEFAULT_MAX_POWER_DBM # 25 dBm
    min_power = DEFAULT_MIN_POWER_DBM # 5 dBm
    stop_value = (min_power * 10) - 5 

    last_successful_power = None
    consecutive_failures_count = 0  # Contador de falhas seguidas para confirmar limiar
    
    HARD_TIMEOUT_S = 20.0  # Limite maior para permitir confirmações
    start_time = time.time() # Registra o tempo de início
    
    # Loop de busca de threshold: de 25.0 dBm até 5.0 dBm, em passos de 0.5 dBm
    for power_int_x10 in range(start_power_for_run * 10, stop_value, -5):
        
        # Checagem de Timeout
        if time.time() - start_time > HARD_TIMEOUT_S:
            print(f"⏱️ TIMEOUT DE HARDWARE: Sem resposta do RFID em {frequency} MHz.")
            return None # Retorna None para sinalizar falha

        power_dbm = power_int_x10 / 10.0
        
        # Log apenas a cada 2 dBm para não poluir muito
        if power_int_x10 % 20 == 0:
            print(f"🔍 Testando potência: {power_dbm:.1f} dBm...")
        
        # Configura potência
        power_val = int(power_dbm * 100)
        power_data = bytes([0x00, 0x00]) + power_val.to_bytes(2, 'big') * 2
        rfid_sdk.UHF_RFID_Set(RFID_CMD_SET_TXPOWER, ctypes.c_char_p(power_data), 6, output_buffer, ctypes.byref(output_len))

        time.sleep(0.02)

        # Múltiplas tentativas de inventário por potência (estilo FastSurance)
        # Critério: após já ter sucesso, se falhar todas as tentativas, considera limiar atingido
        power_detected = False
        ATTEMPTS_PER_POWER = 6  # Aumentado para robustez (igual FastSurance)
        for attempt_num in range(ATTEMPTS_PER_POWER):
            if time.time() - start_time > HARD_TIMEOUT_S:
                return None

            output_len.value = 0
            # 0x64 ~ janela mais longa, como usado no FastSurance
            inv_tag_input_data = bytes([0x00, 0x64])
            ret = rfid_sdk.UHF_RFID_Set(RFID_CMD_INV_TAG, ctypes.c_char_p(inv_tag_input_data), 2, output_buffer, ctypes.byref(output_len))

            detected = False
            if ret == 0 and output_len.value > 5:
                if filter_epc_hex:
                    epc_hex, eoc_hex = parse_epc_and_eoc_from_frame(output_buffer, output_len.value)
                    filter_upper = filter_epc_hex.upper().strip()
                    if epc_hex:
                        # Compara EPC completo com EPC completo
                        if filter_upper == epc_hex:
                            detected = True
                            # Log apenas na primeira detecção de cada potência
                            if not power_detected:
                                print(f"✅ EPC MATCH em {power_dbm:.1f} dBm: '{epc_hex}'")
                            power_detected = True
                        else:
                            # Debug apenas na primeira tentativa de cada potência
                            if attempt_num == 0:
                                print(f"⚠️ EPC diferente: detectado='{epc_hex}' (len={len(epc_hex)}), esperado='{filter_upper}' (len={len(filter_upper)})")
                    else:
                        # Log apenas na primeira tentativa se não extraiu EPC
                        if attempt_num == 0:
                            print(f"⚠️ Frame recebido mas EPC não extraído (len={output_len.value})")
                else:
                    detected = True
                    power_detected = True
            elif ret != 0:
                # Log apenas na primeira tentativa se ret != 0
                if attempt_num == 0:
                    print(f"⚠️ Inventário retornou erro: ret={ret}")

            if detected:
                last_successful_power = power_dbm
                consecutive_failures_count = 0  # Reset ao detectar
            time.sleep(0.05)

        # Se não detectou nada nesta potência mas já teve sucesso anterior,
        # CONTINUA descendo até realmente não detectar mais ou chegar em 5 dBm
        if not power_detected and last_successful_power is not None:
            # Incrementa contador de falhas seguidas
            consecutive_failures_count += 1
            
            # Se falhou 3 potências seguidas, usa a última potência com sucesso como limiar
            if consecutive_failures_count >= 3:
                print(f"🔍 Limiar encontrado: {last_successful_power:.1f} dBm (sem detecção por {consecutive_failures_count} potências seguidas)")
                break
            else:
                # Ainda não foram 3 falhas seguidas, continua descendo
                print(f"⚠️ Não detectado em {power_dbm:.1f} dBm (falha {consecutive_failures_count}/3), continuando...")
        else:
            # Se detectou, reseta contador de falhas
            if power_detected:
                consecutive_failures_count = 0

    # Processa o resultado final (se não houve timeout)
    if last_successful_power is None or last_successful_power == DEFAULT_MAX_POWER_DBM:
        result = f">{DEFAULT_MAX_POWER_DBM:.1f}"
        print(f"📊 Resultado threshold: {result} (nenhuma detecção ou sempre na potência máxima)")
        return result
    elif last_successful_power == DEFAULT_MIN_POWER_DBM:
        result = f"<{DEFAULT_MIN_POWER_DBM:.1f}"
        print(f"📊 Resultado threshold: {result}")
        return result
    else:
        print(f"📊 Resultado threshold: {last_successful_power:.1f} dBm")
        return last_successful_power