#pragma once

#include "logWrapper.h"

#include <chrono>
#include <dBus/dBus.h>
#include <dBus/defs.h>
#include <expected>
#include <format>
#include <future>
#include <memory>
#include <string>
#include <type_traits>
#include <vector>

namespace dBus::api
{
// Define a type alias for the return status of post
// operations, using std::expected to represent success
// or failure with an error code
using PostReturnStatus = std::expected<void, dBus::ReturnStatus>;

//--------------------------------------------------------------
struct BaseDefaultData
{
};

template<HashID ID>
struct DefaultData : BaseDefaultData
{
  DefaultData()                                      = default;     // Default constructor
  virtual ~DefaultData()                             = default;     // Default destructor
  DefaultData(const DefaultData &obj)                = default;     // Copy constructor
  DefaultData(DefaultData &&obj) noexcept            = default;     // Move constructor
  DefaultData &operator=(const DefaultData &obj)     = default;     // Copy assignment operator
  DefaultData &operator=(DefaultData &&obj) noexcept = default;     // Move assignment operator

  constexpr HashID getID() const
  {
    return ID;
  }

  [[nodiscard]] virtual std::string toString() const = 0;     // Convert the data to a string representation for logging or debugging
};
//--------------------------------------------------------------

/* --- */

//--------------------------------------------------------------
// Generic message API template for simple data transfer (fire-and-forget)
template<class T>
class MessageApi : public EventMessage<T>
{
  public:
  MessageApi()                                     = delete;      // Default constructor
  virtual ~MessageApi()                            = default;     // Default destructor
  MessageApi(const MessageApi &obj)                = delete;      // Copy constructor
  MessageApi(MessageApi &&obj) noexcept            = delete;      // Move constructor
  MessageApi &operator=(const MessageApi &obj)     = delete;      // Copy assignment operator
  MessageApi &operator=(MessageApi &&obj) noexcept = delete;      // Move assignment operator

  explicit MessageApi(const HashID &messageTypeID, T data, const MessageCategory &category = MessageCategory::System)     // Constructor
    : EventMessage<T>(messageTypeID, category, std::move(data))
  {
  }

  // Serialize the message data to a string for logging or debugging
  [[nodiscard]] std::string serializeData() const override
  {
    if constexpr (std::is_base_of_v<BaseDefaultData, T>)
    {
      return this->value.toString();
    }
    else if constexpr (std::is_arithmetic_v<T>)
    {
      return std::to_string(this->value);
    }
    else
    {
      return "";
    }
  }
};
//--------------------------------------------------------------
// Request message API template for data transfer with response
template<class TRequest, class TResponse>
class RequestMessageApi : public RequestMessage<TRequest, TResponse>
{
  public:
  RequestMessageApi()                                            = delete;      // Default constructor
  virtual ~RequestMessageApi()                                   = default;     // Default destructor
  RequestMessageApi(const RequestMessageApi &obj)                = delete;      // Copy constructor
  RequestMessageApi(RequestMessageApi &&obj) noexcept            = delete;      // Move constructor
  RequestMessageApi &operator=(const RequestMessageApi &obj)     = delete;      // Copy assignment operator
  RequestMessageApi &operator=(RequestMessageApi &&obj) noexcept = delete;      // Move assignment operator

  explicit RequestMessageApi(const HashID &messageTypeID, TRequest request, const MessageCategory &category = MessageCategory::System)
    : RequestMessage<TRequest, TResponse>(messageTypeID, category, std::move(request), createResponse())
  {
  }

  // Serialize the message data to a string for logging or debugging
  [[nodiscard]] std::string serializeData() const override
  {
    if constexpr (std::is_base_of_v<BaseDefaultData, TRequest>)
    {
      return this->value.toString();
    }
    else if constexpr (std::is_arithmetic_v<TRequest>)
    {
      return std::to_string(this->value);
    }
    else
    {
      return "";
    }
  }

  private:
  static auto createResponse()
  {
    if constexpr (std::is_void_v<TResponse>)
      return nullptr;     // Use nullptr for void response type since there is no value to return
    else
      return std::make_shared<TResponse>();
  }
};
//--------------------------------------------------------------

/* --- */

//--------------------------------------------------------------
// Post a simple data message (fire-and-forget)
template<class T>
bool postData(Bus                   &bus,
              const HashID          &messageTypeID,
              T                      data,
              const MessageCategory &category)
{
  auto msg = std::make_shared<MessageApi<T>>(messageTypeID, std::move(data), category);
  return bus.post(msg);
}
//--------------------------------------------------------------
// Post a data message with subscriber check
template<class T>
std::expected<void, ReturnStatus> postDataChecked(Bus                   &bus,
                                                  const HashID          &messageTypeID,
                                                  T                      data,
                                                  const MessageCategory &category)
{
  auto       msg              = std::make_shared<MessageApi<T>>(messageTypeID, std::move(data), category);
  const auto subscribersFound = bus.post(msg);

  if (!subscribersFound)
  {
    return std::unexpected(ReturnStatus::NoSubscribers);
  }

  return {};
}
//--------------------------------------------------------------
template<class TRequest, class TResponse>
std::expected<TResponse, ReturnStatus> requestData(Bus                            &bus,
                                                   const HashID                   &messageTypeID,
                                                   TRequest                        request,
                                                   const MessageCategory          &category,
                                                   const std::chrono::milliseconds timeout = DEFAULT_TIMEOUT)
{
  auto msg    = std::make_shared<RequestMessageApi<TRequest, TResponse>>(messageTypeID, std::move(request), category);
  auto future = msg->getFuture();

  try
  {
    // Post the message to the bus and check if there are subscribers
    const auto subscribersFound = bus.post(msg);
    if (!subscribersFound)
    {
      ::api::log::postLogWarning(bus, std::format("No subscribers found for message type: 0x{:08x}", msg->getMessageTypeID()));
      return std::unexpected(ReturnStatus::NoSubscribers);
    }

    const auto status = future.wait_for(timeout);
    if (status == std::future_status::ready)
    {
      if constexpr (std::is_void_v<TResponse>)
      {
        // Wait for the future to be ready and check for exceptions,
        // but ignore the value since it's void
        future.get();

        return {};     // Return success without a value
      }
      else
      {
        // Wait for the future to be ready and return the value,
        // or propagate any exceptions that occurred during the
        // request handling
        return future.get();     // Return success with value
      }
    }
    else if (status == std::future_status::timeout)
    {
      ::api::log::postLogWarning(bus, std::format("Timeout while waiting for response to message type: 0x{:08x}", msg->getMessageTypeID()));
      return std::unexpected(ReturnStatus::Timeout);
    }
    else
    {
      ::api::log::postLogError(bus, std::format("Unexpected future status while waiting for response to message type: 0x{:08x}", msg->getMessageTypeID()));
      return std::unexpected(ReturnStatus::Error);
    }
  }
  catch (const std::exception &e)
  {
    ::api::log::postLogError(bus, std::format("Exception in requestData for message type: 0x{:08x}: {}", msg->getMessageTypeID(), e.what()));
    // return std::unexpected(ReturnStatus::Exception);
    throw;
  }
}
//--------------------------------------------------------------
}     // namespace dBus::api
