#pragma once

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

#include <gio/gio.h>

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

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

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

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

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

  bool connect_bus();
  bool find_adapter();
  bool start_discovery();
  bool stop_discovery();
  bool scan_for_peer();
  bool connect_device(const std::string& device_path);
  bool discover_characteristics();
  bool start_notify();
  bool write_characteristic(const std::string& path, const uint8_t* data, size_t len, bool with_response);
  bool 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();
  void mark_disconnected(const char* reason);
  void disconnect_peer();

  static void properties_changed(GDBusConnection* connection,
                                 const gchar* sender_name,
                                 const gchar* object_path,
                                 const gchar* interface_name,
                                 const gchar* signal_name,
                                 GVariant* parameters,
                                 gpointer user_data);

  static void device_properties_changed(GDBusConnection* connection,
                                        const gchar* sender_name,
                                        const gchar* object_path,
                                        const gchar* interface_name,
                                        const gchar* signal_name,
                                        GVariant* parameters,
                                        gpointer user_data);

  static constexpr const char* BLUEZ_BUS = "org.bluez";
  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;
  static constexpr uint64_t SCAN_RETRY_MS = 5000;

  std::string node_label_;
  bool frame_log_enabled_ = false;
  GDBusConnection* bus_ = nullptr;
  std::string adapter_path_;
  std::string device_path_;
  std::string tx_char_path_;
  std::string rx_char_path_;
  std::string identity_char_path_;
  guint notify_subscription_ = 0;
  guint device_subscription_ = 0;
  bool online_ = false;
  bool connected_ = false;
  bool discovering_ = false;
  uint64_t next_scan_ms_ = 0;
  uint32_t tx_message_id_ = 0;

  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_;
};
