#pragma once

#include <Bytes.h>
#include <Interface.h>

#include <gio/gio.h>

#include <deque>
#include <string>
#include <vector>

class HostBluezPeripheralInterface : public RNS::InterfaceImpl {
public:
  explicit HostBluezPeripheralInterface(const std::string& node_label,
                                        bool frame_log_enabled = false,
                                        const char* name = "HostBluezPeripheral");
  ~HostBluezPeripheralInterface() override;

  bool start() override;
  void stop() override;
  void loop() override;

  bool connected() const { return connected_; }
  const char* role_name() const { return "linux-peripheral"; }
  const std::string& peer_path() const { return peer_path_; }

private:
  void send_outgoing(const RNS::Bytes& data) override;

  bool connect_bus();
  bool find_adapter();
  bool adapter_supports_peripheral() const;
  bool register_gatt_application();
  bool register_advertisement();
  void unregister_bluez_objects();
  void emit_tx_value_changed();
  void send_fragment(const uint8_t* data, size_t len);
  void handle_fragment(const uint8_t* data, size_t len);
  void enqueue_packet(const RNS::Bytes& packet);
  bool dequeue_packet(RNS::Bytes& packet);
  void reset_reassembly();
  RNS::Bytes local_identity_hash() const;

  GVariant* get_managed_objects() const;
  GVariant* get_property(const char* object_path, const char* interface_name, const char* property_name) const;
  void handle_method_call(const char* object_path,
                          const char* interface_name,
                          const char* method_name,
                          const char* sender,
                          GVariant* parameters,
                          GDBusMethodInvocation* invocation);

  static void method_call(GDBusConnection* connection,
                          const gchar* sender,
                          const gchar* object_path,
                          const gchar* interface_name,
                          const gchar* method_name,
                          GVariant* parameters,
                          GDBusMethodInvocation* invocation,
                          gpointer user_data);
  static GVariant* property_get(GDBusConnection* connection,
                                const gchar* sender,
                                const gchar* object_path,
                                const gchar* interface_name,
                                const gchar* property_name,
                                GError** error,
                                gpointer user_data);
  static void register_application_done(GObject* source_object, GAsyncResult* result, gpointer user_data);
  static void register_advertisement_done(GObject* source_object, GAsyncResult* result, gpointer user_data);

  static constexpr const char* BLUEZ_BUS = "org.bluez";
  static constexpr const char* APP_PATH = "/com/microreticulum/ex306";
  static constexpr const char* SERVICE_PATH = "/com/microreticulum/ex306/service0";
  static constexpr const char* TX_PATH = "/com/microreticulum/ex306/service0/tx";
  static constexpr const char* RX_PATH = "/com/microreticulum/ex306/service0/rx";
  static constexpr const char* IDENTITY_PATH = "/com/microreticulum/ex306/service0/identity";
  static constexpr const char* ADV_PATH = "/com/microreticulum/ex306/advertisement0";
  static constexpr const char* SERVICE_UUID = "37145b00-442d-4a94-917f-8f42c5da28e3";
  static constexpr const char* TX_UUID = "37145b00-442d-4a94-917f-8f42c5da28e4";
  static constexpr const char* RX_UUID = "37145b00-442d-4a94-917f-8f42c5da28e5";
  static constexpr const char* IDENTITY_UUID = "37145b00-442d-4a94-917f-8f42c5da28e6";

  static constexpr uint8_t FRAG_START = 0x01;
  static constexpr uint8_t FRAG_CONTINUE = 0x02;
  static constexpr uint8_t FRAG_END = 0x03;
  static constexpr uint8_t FRAG_HEADER_VERSION = 0x02;
  static constexpr size_t FRAG_HEADER_SIZE = 14;
  static constexpr size_t BLE_ATT_MTU = 185;
  static constexpr size_t BLE_VALUE_SIZE = BLE_ATT_MTU - 3;
  static constexpr size_t BLE_PAYLOAD_SIZE = BLE_VALUE_SIZE - FRAG_HEADER_SIZE;
  static constexpr uint64_t REASSEMBLY_TIMEOUT_MS = 30000;

  std::string node_label_;
  bool frame_log_enabled_ = false;
  GDBusConnection* bus_ = nullptr;
  std::string adapter_path_;
  GDBusNodeInfo* object_manager_node_ = nullptr;
  GDBusNodeInfo* gatt_service_node_ = nullptr;
  GDBusNodeInfo* gatt_characteristic_node_ = nullptr;
  GDBusNodeInfo* advertisement_node_ = nullptr;
  std::vector<guint> object_registration_ids_;
  bool online_ = false;
  bool connected_ = false;
  bool notifying_ = false;
  bool gatt_registered_ = false;
  bool advertisement_registered_ = false;
  bool has_gatt_manager_ = false;
  bool has_advertising_manager_ = false;
  std::string peer_path_;
  uint32_t tx_message_id_ = 0;
  RNS::Bytes tx_value_;
  RNS::Bytes identity_value_;

  RNS::Bytes reassembly_buffer_;
  uint16_t expected_total_ = 0;
  uint16_t received_fragments_ = 0;
  uint32_t expected_message_len_ = 0;
  uint64_t reassembly_started_ms_ = 0;
  uint32_t current_rx_message_id_ = 0;
  std::deque<RNS::Bytes> incoming_packets_;
};
