#include "src/Helpers/_CPlugin_Helper.h"
#ifdef USES_C005

# include "src/Commands/ExecuteCommand.h"
# include "src/Globals/EventQueue.h"
# if FEATURE_MQTT_DISCOVER
#  include "src/Globals/MQTT.h"
# endif // if FEATURE_MQTT_DISCOVER
# include "src/Helpers/_CPlugin_Helper_mqtt.h"
# include "src/Helpers/PeriodicalActions.h"
# include "src/Helpers/StringParser.h"
# include "_Plugin_Helper.h"

// #######################################################################################################
// ################### Controller Plugin 005: Home Assistant (openHAB) MQTT ##############################
// #######################################################################################################

/** Changelog:
 * 2025-08-23 tonhuisman: Add 10/sec call to poll background connection process while not connected
 * 2025-06-17 tonhuisman: Enable sending Derived values when available
 * 2024-11-29 tonhuisman: Add Discovery trigger setting
 * 2024-11-11 tonhuisman: Add AutoDiscovery options
 *                        Home Assistant suggested discovery topic: "homeassistant/%devclass%/%unique_id%" (updated)
 * 2023-08-18 tonhuisman: Clean up source for pull request
 * 2023-03-15 tonhuisman: Add processing of topic endpoint /set to issue a TaskValueSet,taskname,taskvalue,payload command for
 *                        topic %sysname%/#/taskname/valuename/set
 *                        Move /cmd and /set handling to helper file for generic MQTT use
 *                        Reformatted source using Uncrustify
 * 2023-03 Changelog started
 */

# define CPLUGIN_005
# define CPLUGIN_ID_005         5
# define CPLUGIN_NAME_005       "Home Assistant (openHAB) MQTT"

String CPlugin_005_pubname;
bool   CPlugin_005_mqtt_retainFlag = false;

bool C005_parse_command(struct EventStruct *event);

bool CPlugin_005(CPlugin::Function function, struct EventStruct *event, String& string)
{
  bool success = false;

  switch (function)
  {
    case CPlugin::Function::CPLUGIN_PROTOCOL_ADD:
    {
      ProtocolStruct& proto = getProtocolStruct(event->idx); //      = CPLUGIN_ID_005;
      proto.usesMQTT     = true;
      proto.usesTemplate = true;
      proto.usesAccount  = true;
      proto.usesPassword = true;
      proto.usesExtCreds = true;
      proto.defaultPort  = 1883;
      proto.usesID       = false;
      # if FEATURE_MQTT_TLS
      proto.usesTLS = true;
      # endif // if FEATURE_MQTT_TLS
      # if FEATURE_MQTT_DISCOVER
      proto.mqttAutoDiscover = true;
      # endif // if FEATURE_MQTT_DISCOVER
      # if FEATURE_STRING_VARIABLES
      proto.allowSendDerived = true;
      # endif // if FEATURE_STRING_VARIABLES
      break;
    }

    case CPlugin::Function::CPLUGIN_GET_DEVICENAME:
    {
      string = F(CPLUGIN_NAME_005);
      break;
    }

    case CPlugin::Function::CPLUGIN_INIT:
    {
      success = init_mqtt_delay_queue(event->ControllerIndex, CPlugin_005_pubname, CPlugin_005_mqtt_retainFlag);
      # if FEATURE_MQTT_DISCOVER
      mqttDiscoveryController = INVALID_CONTROLLER_INDEX;

      if (success) {
        MakeControllerSettings(ControllerSettings); // -V522

        if (!AllocatedControllerSettings()) {
          return false;
        }
        LoadControllerSettings(event->ControllerIndex, *ControllerSettings);

        if (ControllerSettings->mqtt_autoDiscovery() &&
            (ControllerSettings->MqttAutoDiscoveryTopic[0] != 0) &&
            (0 == mqttDiscoveryTimeout)) {
          mqttDiscoveryController = event->ControllerIndex;
          mqttDiscoveryTimeout    = random(10, MQTT_DISCOVERY_MAX_DELAY_0_1_SECONDS);

          if (loglevelActiveFor(LOG_LEVEL_INFO)) {
            addLog(LOG_LEVEL_INFO, strformat(F("C005 : INIT: AutoDiscovery for Controller %d in %.1f sec."),
                                             event->ControllerIndex + 1,
                                             mqttDiscoveryTimeout / 10.0f));
          }
        }
      }
      # endif // if FEATURE_MQTT_DISCOVER
      break;
    }

    case CPlugin::Function::CPLUGIN_TEN_PER_SECOND:
    {
      # if FEATURE_MQTT_DISCOVER

      if ((mqttDiscoveryTimeout > 0) && MQTTclient_connected) {
        mqttDiscoveryTimeout--;

        if (0 == mqttDiscoveryTimeout) {
          if (loglevelActiveFor(LOG_LEVEL_INFO)) {
            addLog(LOG_LEVEL_INFO, F("C005 : AutoDiscovery delay expired, starting now..."));
          }
          success = MQTT_SendAutoDiscovery(event->ControllerIndex, CPLUGIN_ID_005);
        }
      }
      # endif // if FEATURE_MQTT_DISCOVER
      # if FEATURE_MQTT_CONNECT_BACKGROUND
      MQTTConnectInBackground(CONTROLLER_MAX, true); // Report state
      # endif // if FEATURE_MQTT_CONNECT_BACKGROUND
      break;
    }

    case CPlugin::Function::CPLUGIN_EXIT:
    {
      exit_mqtt_delay_queue();
      break;
    }

    case CPlugin::Function::CPLUGIN_PROTOCOL_TEMPLATE:
    {
      event->String1 = F("%sysname%/#");
      event->String2 = F("%sysname%/%tskname%/%valname%");
      # if FEATURE_MQTT_DISCOVER

      if (event->Data != nullptr) {
        ControllerSettingsStruct*controllerSettings = (ControllerSettingsStruct *)(event->Data);
        safe_strncpy(controllerSettings->MqttAutoDiscoveryTrigger,
                     FsP(F("homeassistant/status")),
                     sizeof(controllerSettings->MqttAutoDiscoveryTrigger));
        safe_strncpy(controllerSettings->MqttAutoDiscoveryTopic,
                     FsP(F("homeassistant/%devclass%/%unique_id%")),
                     sizeof(controllerSettings->MqttAutoDiscoveryTopic));
      }
      # endif // if FEATURE_MQTT_DISCOVER
      break;
    }

    case CPlugin::Function::CPLUGIN_PROTOCOL_RECV:
    {
      controllerIndex_t ControllerID = findFirstEnabledControllerWithId(CPLUGIN_ID_005);

      if (validControllerIndex(ControllerID) && event) {
        # if FEATURE_MQTT_DISCOVER
        MakeControllerSettings(ControllerSettings); // -V522

        if (!AllocatedControllerSettings()) {
          return false;
        }
        LoadControllerSettings(event->ControllerIndex, *ControllerSettings);

        const String online = parseStringKeepCase(ControllerSettings->MqttAutoDiscoveryConfig, 2, '|');

        // AutoDiscovery enabled?
        if (ControllerSettings->mqtt_autoDiscovery()
            && (ControllerSettings->MqttAutoDiscoveryTrigger[0] != '\0')
            && event->String1.equals(ControllerSettings->MqttAutoDiscoveryTrigger)
            && event->String2.equals(online.isEmpty() ? String(F("online")) : online)
            ) {
          // We have received the Discovery request topic
          // Generate random time-offset in 0.1 sec, range 1..30 seconds
          mqttDiscoveryTimeout = random(10, MQTT_DISCOVERY_MAX_DELAY_0_1_SECONDS);

          if (loglevelActiveFor(LOG_LEVEL_INFO)) {
            addLog(LOG_LEVEL_INFO, strformat(F("C005 : Request for AutoDiscovery received. %.1f sec."),
                                             mqttDiscoveryTimeout / 10.0f));
          }

          // FIXME Generate event when request received?
        } else
        # endif // if FEATURE_MQTT_DISCOVER
        {
          C005_parse_command(event);
        }
      }
      break;
    }

    case CPlugin::Function::CPLUGIN_PROTOCOL_SEND:
    {
      if (MQTT_queueFull(event->ControllerIndex)) {
        break;
      }

      const bool taskRetained = Settings.SendRetainedTaskValues(event->TaskIndex, event->ControllerIndex);

      success = MQTT_protocol_send(event, CPlugin_005_pubname, CPlugin_005_mqtt_retainFlag || taskRetained);

      break;
    }

    case CPlugin::Function::CPLUGIN_FLUSH:
    {
      processMQTTdelayQueue();
      delay(0);
      break;
    }

    default:
      break;
  }

  return success;
}

bool C005_parse_command(struct EventStruct *event) {
  // FIXME TD-er: Command is not parsed for template arguments.

  // Topic  : event->String1
  // Message: event->String2
  bool validTopic = MQTT_handle_topic_commands(event); // default handling of /cmd and /set topics

  if (!validTopic) {
    String cmd;
    const int lastindex            = event->String1.lastIndexOf('/');
    const String lastPartTopic     = event->String1.substring(lastindex + 1);
    const bool   has_cmd_arg_index = event->String1.lastIndexOf(F("cmd_arg")) != -1;

    if (has_cmd_arg_index) {
      // Example:
      // Topic: ESP_Easy/Bathroom_pir_env/cmd_arg1/GPIO/0
      // Message: 14
      // Full command: gpio,14,0

      uint8_t topic_index  = 1;
      String  topic_folder = parseStringKeepCase(event->String1, topic_index, '/');

      while (!topic_folder.startsWith(F("cmd_arg")) && !topic_folder.isEmpty()) {
        ++topic_index;
        topic_folder = parseStringKeepCase(event->String1, topic_index, '/');
      }

      if (!topic_folder.isEmpty()) {
        int32_t cmd_arg_nr = -1;

        if (validIntFromString(topic_folder.substring(7), cmd_arg_nr)) {
          int constructed_cmd_arg_nr = 0;
          ++topic_index;
          topic_folder = parseStringKeepCase(event->String1, topic_index, '/');
          bool msg_added = false;

          while (!topic_folder.isEmpty()) {
            if (constructed_cmd_arg_nr != 0) {
              cmd += ',';
            }

            if (constructed_cmd_arg_nr == cmd_arg_nr) {
              cmd      += event->String2;
              msg_added = true;
            } else {
              cmd += topic_folder;
              ++topic_index;
              topic_folder = parseStringKeepCase(event->String1, topic_index, '/');
            }
            ++constructed_cmd_arg_nr;
          }

          if (!msg_added) {
            cmd += ',';
            cmd += event->String2;
          }

          // addLog(LOG_LEVEL_INFO, String(F("MQTT cmd: ")) + cmd);

          validTopic = true;
        }
      }
    } else {
      // Example:
      // Topic: ESP_Easy/Bathroom_pir_env/GPIO/14
      // Message: 0 or 1
      // Full command:  gpio,14,0
      if (lastindex > 0) {
        // Topic has at least one separator
        int32_t lastPartTopic_int;
        float   value_f;

        if (validFloatFromString(event->String2, value_f) &&
            validIntFromString(lastPartTopic, lastPartTopic_int)) {
          const int prevLastindex = event->String1.lastIndexOf('/', lastindex - 1);
          cmd = strformat(F("%s,%d,%s"),
                          event->String1.substring(prevLastindex + 1, lastindex).c_str(),
                          lastPartTopic_int,
                          event->String2.c_str()); // Just use the original format
          validTopic = true;
        }
      }
    }

    if (validTopic) {
      MQTT_execute_command(cmd);
    }
  }
  return validTopic;
}

#endif // ifdef USES_C005
