/**
 * SmartLabMQTT Library - Implementação
 *
 * Processo de Conexão:
 * 1. Envia device ID em devices/connection/request
 * 2. Aguarda confirmação em devices/connection/response/{device_id}
 * 3. Inicia ping periódico em devices/ping/{device_id}
 * 4. Recebe comandos em devices/withdraw/{device_id}/{slot}
 *
 * @author Vitor Chaves
 * @version 1.0.0
 */
#ifndef SMARTLAB_MQTT_H
#define SMARTLAB_MQTT_H
#include <WiFi.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
// Estados de conexão
enum ConnectionState {
  DISCONNECTED,
  WIFI_CONNECTING,
  MQTT_CONNECTING,
  WAITING_RESPONSE,
  CONNECTED
};
class SmartLabMQTT {
private:
  WiFiClient _espClient;
  PubSubClient _client;
  // Configurações
  const char* _device_id;
  const char* _ssid;
  const char* _password;
  const char* _mqtt_server;
  int _mqtt_port;
  const char* _mqtt_user;
  const char* _mqtt_password;
  // Estado da conexão
  ConnectionState _state;
  unsigned long _lastConnectionRequest;
  unsigned long _lastPing;
  unsigned long _connectionTimeout;
  unsigned long _pingInterval;
  // Callbacks
  void (*_onConnectedHandler)();
  void (*_onSlotMessageHandler)(int slotNumber);
  void (*_onErrorHandler)(const String& error);
  // Callback interno do MQTT
  static void mqttCallback(char* topic, byte* payload, unsigned int length);
  static SmartLabMQTT* _instance;
  // Tópicos
  String _topicConnectionRequest;
  String _topicConnectionResponse;
  String _topicPing;
  void handleMessage(char* topic, byte* payload, unsigned int length);
  void processConnectionResponse(const String& message);
  void sendConnectionRequest();
  void sendPing();
  void logError(const String& error);
  void changeState(ConnectionState newState);
public:
  /**
   * Construtor da biblioteca SmartLabMQTT
   */
  SmartLabMQTT(const char* device_id, const char* ssid, const char* password,
               const char* mqtt_server, int mqtt_port = 1883,
               const char* mqtt_user = "", const char* mqtt_password = "");
  /**
   * Define handler chamado quando conexão é estabelecida com sucesso
   */
  void onConnected(void (*handler)());
  /**
   * Define handler chamado quando recebe comando de withdraw
   */
  void onSlotMessage(void (*handler)(int slotNumber));
  /**
   * Define handler chamado quando ocorre erro
   */
  void onError(void (*handler)(const String& error));
  /**
   * Define timeout para resposta de conexão (padrão: 10 segundos)
   */
  void setConnectionTimeout(unsigned long timeout);
  /**
   * Define intervalo de ping (padrão: 30 segundos)
   */
  void setPingInterval(unsigned long interval);
  /**
   * Inicializa a biblioteca e inicia processo de conexão
   */
  void begin();
  /**
   * Deve ser chamado no loop() principal
   */
  void loop();
  /**
   * Verifica se está totalmente conectado (handshake completo)
   */
  bool isConnected();
  /**
   * Retorna estado atual da conexão
   */
  ConnectionState getState();
  /**
   * Retorna string do estado atual
   */
  const char* getStateString();
  /**
   * Publica mensagem em tópico MQTT (apenas se conectado)
   */
  bool publish(const char* topic, const char* message);
  /**
   * Retorna o ID do dispositivo
   */
  String getDeviceId();
};
// ========== IMPLEMENTAÇÃO ==========
// Instância estática para callback
SmartLabMQTT* SmartLabMQTT::_instance = nullptr;
// Construtor
SmartLabMQTT::SmartLabMQTT(const char* device_id, const char* ssid, const char* password,
                           const char* mqtt_server, int mqtt_port,
                           const char* mqtt_user, const char* mqtt_password) 
  : _client(_espClient) {
  
  _device_id = device_id;
  _ssid = ssid;
  _password = password;
  _mqtt_server = mqtt_server;
  _mqtt_port = mqtt_port;
  _mqtt_user = mqtt_user;
  _mqtt_password = mqtt_password;
  
  _state = DISCONNECTED;
  _lastConnectionRequest = 0;
  _lastPing = 0;
  _connectionTimeout = 10000; // 10 segundos
  _pingInterval = 30000;      // 30 segundos
  
  _onConnectedHandler = nullptr;
  _onSlotMessageHandler = nullptr;
  _onErrorHandler = nullptr;
  
  // Define tópicos
  _topicConnectionRequest = "devices/connection/request";
  _topicConnectionResponse = "devices/connection/response/" + String(_device_id);
  _topicPing = "devices/ping/" + String(_device_id);
  
  // Define instância estática para callback
  _instance = this;
}
void SmartLabMQTT::onConnected(void (*handler)()) {
  _onConnectedHandler = handler;
}
void SmartLabMQTT::onSlotMessage(void (*handler)(int slotNumber)) {
  _onSlotMessageHandler = handler;
}
void SmartLabMQTT::onError(void (*handler)(const String& error)) {
  _onErrorHandler = handler;
}
void SmartLabMQTT::setConnectionTimeout(unsigned long timeout) {
  _connectionTimeout = timeout;
}
void SmartLabMQTT::setPingInterval(unsigned long interval) {
  _pingInterval = interval;
}
void SmartLabMQTT::begin() {
  Serial.println("🚀 SmartLabMQTT: Iniciando...");
  
  // Conecta WiFi
  changeState(WIFI_CONNECTING);
  WiFi.begin(_ssid, _password);
  Serial.print("Conectando WiFi");
  
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  
  Serial.println(" ✓ WiFi conectado!");
  Serial.println("IP: " + WiFi.localIP().toString());
  
  // Configura MQTT
  _client.setServer(_mqtt_server, _mqtt_port);
  _client.setCallback(mqttCallback);
}
void SmartLabMQTT::loop() {
  // Verifica conexão WiFi
  if (WiFi.status() != WL_CONNECTED) {
    logError("WiFi desconectado");
    changeState(WIFI_CONNECTING);
    return;
  }
  
  // Verifica conexão MQTT
  if (!_client.connected()) {
    changeState(MQTT_CONNECTING);
    
    Serial.print("Conectando MQTT...");
    String clientId = String(_device_id) + "_" + String(random(0xffff), HEX);
    
    bool connected = false;
    if (strlen(_mqtt_user) > 0) {
      connected = _client.connect(clientId.c_str(), _mqtt_user, _mqtt_password);
    } else {
      connected = _client.connect(clientId.c_str());
    }
    
    if (connected) {
      Serial.println(" ✓ MQTT conectado!");
      
      // Subscreve aos tópicos necessários
      _client.subscribe(_topicConnectionResponse.c_str());
      
      // Subscreve aos tópicos de withdraw para os 4 slots
      for (int i = 1; i <= 4; i++) {
        String withdrawTopic = "devices/withdraw/" + String(_device_id) + "/" + String(i);
        _client.subscribe(withdrawTopic.c_str());
      }
      
      // Envia request de conexão
      sendConnectionRequest();
    } else {
      Serial.println(" ✗ Falha MQTT: " + String(_client.state()));
      logError("Falha conexão MQTT: " + String(_client.state()));
      delay(5000);
      return;
    }
  }
  
  _client.loop();
  
  // Verifica timeouts
  if (_state == WAITING_RESPONSE && 
      millis() - _lastConnectionRequest > _connectionTimeout) {
    Serial.println("⏰ Timeout conexão, reenviando...");
    sendConnectionRequest();
  }
  
  // Envia ping periódico
  if (_state == CONNECTED && 
      millis() - _lastPing > _pingInterval) {
    sendPing();
  }
}
void SmartLabMQTT::sendConnectionRequest() {
  if (!_client.connected()) return;
  
  StaticJsonDocument<200> doc;
  doc["device_id"] = _device_id;
  doc["device_type"] = "smart_dispenser";
  doc["firmware_version"] = "1.0.0";
  doc["timestamp"] = millis();
  
  String jsonString;
  serializeJson(doc, jsonString);
  
  Serial.println("📤 Enviando connection request:");
  Serial.println("   Tópico: " + _topicConnectionRequest);
  Serial.println("   Payload: " + jsonString);
  
  bool sent = _client.publish(_topicConnectionRequest.c_str(), jsonString.c_str());
  if (sent) {
    Serial.println("✓ Connection request enviado!");
    changeState(WAITING_RESPONSE);
    _lastConnectionRequest = millis();
  } else {
    logError("Falha ao enviar connection request");
  }
}
void SmartLabMQTT::sendPing() {
  if (!_client.connected()) return;
  
  StaticJsonDocument<100> doc;
  doc["device_id"] = _device_id;
  doc["timestamp"] = millis();
  doc["status"] = "online";
  
  String jsonString;
  serializeJson(doc, jsonString);
  
  bool sent = _client.publish(_topicPing.c_str(), jsonString.c_str());
  if (sent) {
    Serial.println("💓 Ping enviado");
    _lastPing = millis();
  } else {
    logError("Falha ao enviar ping");
  }
}
void SmartLabMQTT::mqttCallback(char* topic, byte* payload, unsigned int length) {
  if (_instance) {
    _instance->handleMessage(topic, payload, length);
  }
}
void SmartLabMQTT::handleMessage(char* topic, byte* payload, unsigned int length) {
  String msg;
  for (int i = 0; i < length; i++) {
    msg += (char)payload[i];
  }
  
  Serial.printf("📨 Mensagem recebida:\n");
  Serial.printf("   Tópico: %s\n", topic);
  Serial.printf("   Payload: %s\n", msg.c_str());
  
  String topicStr = String(topic);
  
  // Verifica se é resposta de conexão
  if (topicStr == _topicConnectionResponse) {
    processConnectionResponse(msg);
  }
  // Verifica se é comando de withdraw
  else if (topicStr.startsWith("devices/withdraw/" + String(_device_id) + "/")) {
    // Extrai número do slot do tópico
    int lastSlashIndex = topicStr.lastIndexOf('/');
    if (lastSlashIndex != -1) {
      int slotNumber = topicStr.substring(lastSlashIndex + 1).toInt();
      if (slotNumber >= 1 && slotNumber <= 4 && _onSlotMessageHandler) {
        _onSlotMessageHandler(slotNumber);
      }
    }
  }
}
void SmartLabMQTT::processConnectionResponse(const String& message) {
  Serial.println("🔗 Processando resposta de conexão...");
  
  StaticJsonDocument<200> doc;
  DeserializationError error = deserializeJson(doc, message);
  
  if (error) {
    logError("Erro ao parsear JSON da resposta");
    return;
  }
  
  String status = doc["status"];
  String deviceId = doc["device_id"];
  
  if (status == "approved" && deviceId == _device_id) {
    Serial.println("✅ Conexão aprovada!");
    changeState(CONNECTED);
    _lastPing = millis();
    
    if (_onConnectedHandler) {
      _onConnectedHandler();
    }
  } else {
    String errorMsg = "Conexão negada: " + status;
    Serial.println("❌ " + errorMsg);
    logError(errorMsg);
    changeState(DISCONNECTED);
  }
}
void SmartLabMQTT::changeState(ConnectionState newState) {
  if (_state != newState) {
    Serial.println("🔄 Estado: " + String(getStateString()) + " → " + String(getStateString()));
    _state = newState;
  }
}
void SmartLabMQTT::logError(const String& error) {
  Serial.println("❌ ERRO: " + error);
  if (_onErrorHandler) {
    _onErrorHandler(error);
  }
}
bool SmartLabMQTT::isConnected() {
  return _state == CONNECTED;
}
ConnectionState SmartLabMQTT::getState() {
  return _state;
}
const char* SmartLabMQTT::getStateString() {
  switch (_state) {
    case DISCONNECTED: return "DISCONNECTED";
    case WIFI_CONNECTING: return "WIFI_CONNECTING";
    case MQTT_CONNECTING: return "MQTT_CONNECTING";
    case WAITING_RESPONSE: return "WAITING_RESPONSE";
    case CONNECTED: return "CONNECTED";
    default: return "UNKNOWN";
  }
}
bool SmartLabMQTT::publish(const char* topic, const char* message) {
  if (_state == CONNECTED && _client.connected()) {
    return _client.publish(topic, message);
  }
  return false;
}
String SmartLabMQTT::getDeviceId() {
  return String(_device_id);
}
#endif
// ========== EXEMPLO DE USO ==========
#include <ESP32Servo.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
// Instância da biblioteca
SmartLabMQTT smartLab("smartlab_001", "Wokwi-GUEST", "", "52.67.135.210", 1883);
// Hardware
LiquidCrystal_I2C lcd(0x27, 16, 2);
Servo servo1, servo2, servo3, servo4;
#define SERVO1_PIN 13
#define SERVO2_PIN 12
#define SERVO3_PIN 14
#define SERVO4_PIN 27
// Estado
int estoque[4] = {3, 2, 1, 0};
// Callbacks da biblioteca
void onConnected() {
  Serial.println("🎉 SmartLab totalmente conectado!");
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("SmartLab");
  lcd.setCursor(0, 1);
  lcd.print("Conectado!");
}
void onSlotMessage(int slotNumber) {
  Serial.println("🎯 Comando recebido para slot " + String(slotNumber));
  liberarItem(slotNumber - 1); // Ajusta para índice 0-3
}
void onError(const String& error) {
  Serial.println("💥 Erro SmartLab: " + error);
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("ERRO:");
  lcd.setCursor(0, 1);
  lcd.print(error.substring(0, 16)); // Limita a 16 chars
}
void liberarItem(int slot) {
  if (slot < 0 || slot > 3) return;
  
  Servo* servos[] = {&servo1, &servo2, &servo3, &servo4};
  
  if (estoque[slot] > 0) {
    estoque[slot]--;
    
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("Liberando Slot");
    lcd.setCursor(0, 1);
    lcd.print(String(slot + 1) + " (" + String(estoque[slot]) + " rest.)");
    
    Serial.printf("🎯 Liberando slot %d. Restam: %d\n", slot + 1, estoque[slot]);
    
    servos[slot]->write(90);
    delay(1000);
    servos[slot]->write(0);
    
    delay(2000);
    
    if (smartLab.isConnected()) {
      lcd.clear();
      lcd.setCursor(0, 0);
      lcd.print("SmartLab");
      lcd.setCursor(0, 1);
      lcd.print("Conectado!");
    }
    
  } else {
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("Slot " + String(slot + 1));
    lcd.setCursor(0, 1);
    lcd.print("Esgotado!");
    
    Serial.printf("⚠️ Slot %d sem estoque!\n", slot + 1);
    delay(2000);
  }
}
void setup() {
  Serial.begin(115200);
  Serial.println("\n🚀 SmartLab com biblioteca iniciando...");
  
  // Inicializa hardware
  lcd.init();
  lcd.backlight();
  lcd.setCursor(0, 0);
  lcd.print("SmartLab MQTT");
  lcd.setCursor(0, 1);
  lcd.print("Iniciando...");
  
  servo1.attach(SERVO1_PIN);
  servo2.attach(SERVO2_PIN);
  servo3.attach(SERVO3_PIN);
  servo4.attach(SERVO4_PIN);
  
  // Reset servos
  servo1.write(0);
  servo2.write(0);
  servo3.write(0);
  servo4.write(0);
  
  // Configura callbacks da biblioteca
  smartLab.onConnected(onConnected);
  smartLab.onSlotMessage(onSlotMessage);
  smartLab.onError(onError);
  
  // Configura timeouts (opcional)
  smartLab.setConnectionTimeout(30000); // 30 segundos
  smartLab.setPingInterval(5000);      // 5 segundos
  
  // Inicia biblioteca
  smartLab.begin();
  
  Serial.println("\n📋 INSTRUÇÕES PARA TESTE:");
  Serial.println("1. Subscribe: devices/connection/request");
  Serial.println("2. Responder: devices/connection/response/smartlab_001");
  Serial.println("   Payload: {\"status\":\"approved\",\"device_id\":\"smartlab_001\"}");
  Serial.println("3. Comandar: devices/withdraw/smartlab_001/{1,2,3,4}");
  Serial.println("   Payload: qualquer mensagem");
}
void loop() {
  smartLab.loop(); // Chama o loop da biblioteca
  
  // Status no Serial a cada 10 segundos
  static unsigned long lastStatus = 0;
  if (millis() - lastStatus > 10000) {
    Serial.println("\n📊 STATUS:");
    Serial.println("   Estado: " + String(smartLab.getStateString()));
    Serial.println("   Conectado: " + String(smartLab.isConnected() ? "SIM" : "NÃO"));
    Serial.println("   Device ID: " + smartLab.getDeviceId());
    Serial.printf("   Estoque: [%d, %d, %d, %d]\n", estoque[0], estoque[1], estoque[2], estoque[3]);
    lastStatus = millis();
  }
  
  delay(100);
}SLOT 1
SLOT 2
SLOT 3
SLOT 4
LCD I2C