#pragma once

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

#include <Arduino.h>
#include <BLEAdvertisedDevice.h>
#include <BLEClient.h>
#include <BLEDevice.h>
#include <BLERemoteCharacteristic.h>
#include <BLEScan.h>
#include <BLEServer.h>

#include <deque>

class TBeamSupremeBleInterface : public RNS::InterfaceImpl {
public:
  explicit TBeamSupremeBleInterface(const String& node_label,
                                    const char* name = "TBeamSupremeBLE");
  ~TBeamSupremeBleInterface() override;

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

  bool connected() const { return _connected; }
  const char* role_name() const { return "dual-role"; }

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

  void start_peripheral();
  void start_central_scan();
  bool connect_to_advertised_device(BLEAdvertisedDevice* device);
  bool should_connect_to_peer(const String& peer_address) const;
  String advertised_node_label(BLEAdvertisedDevice& device) const;

  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();
  void print_queue_stats(bool force);
  RNS::Bytes local_identity_hash() const;

  static void client_notify_callback(BLERemoteCharacteristic* characteristic,
                                     uint8_t* data,
                                     size_t length,
                                     bool is_notify);

  class ServerCallbacks;
  class RxCallbacks;
  class AdvertisedDeviceCallbacks;

  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 uint32_t REASSEMBLY_TIMEOUT_MS = 30000;
  static constexpr uint32_t SCAN_RETRY_MS = 5000;
  static constexpr size_t MAX_INCOMING_QUEUE_DEPTH = 32;

  String _node_label;
  String _local_address;
  bool _started = false;
  bool _connected = false;
  bool _do_connect = false;
  bool _scanning = false;
  bool _server_handshake_received = false;
  uint8_t _scan_report_count = 0;
  uint32_t _next_scan_ms = 0;

  BLEServer* _server = nullptr;
  BLECharacteristic* _tx_characteristic = nullptr;
  BLECharacteristic* _identity_characteristic = nullptr;
  BLEAdvertisedDevice* _advertised_device = nullptr;
  BLEClient* _client = nullptr;
  BLERemoteCharacteristic* _remote_rx_characteristic = nullptr;
  BLERemoteCharacteristic* _remote_tx_characteristic = nullptr;

  RNS::Bytes _reassembly_buffer;
  uint16_t _expected_total = 0;
  uint16_t _received_fragments = 0;
  uint32_t _expected_message_len = 0;
  uint32_t _reassembly_started_ms = 0;
  uint32_t _rx_message_id = 0;
  uint32_t _current_rx_message_id = 0;
  uint32_t _tx_message_id = 0;
  uint32_t _ble_frag_tx_seq = 0;
  uint32_t _ble_frag_rx_seq = 0;
  uint32_t _packet_tx_seq = 0;
  uint32_t _packet_rx_seq = 0;
  uint32_t _reassembly_gaps = 0;
  uint32_t _reassembly_duplicates = 0;
  uint32_t _reassembly_out_of_order = 0;
  uint32_t _queue_pushes = 0;
  uint32_t _queue_pops = 0;
  uint32_t _queue_drops = 0;
  uint32_t _queue_overwrites = 0;
  uint32_t _queue_alloc_failures = 0;
  size_t _queue_highwater = 0;
  uint32_t _next_queue_log_ms = 0;

  std::deque<RNS::Bytes> _incoming_packets;
  portMUX_TYPE _queue_mux = portMUX_INITIALIZER_UNLOCKED;
};
