#ifndef BLE_COMPANION_H
#define BLE_COMPANION_H

// ============================================================================
// ble_companion.h — Direct phone-to-sensor BLE peripheral (NimBLE backend)
//
// Exposes the T1 RID sensor over a custom GATT service so a Samsung / Android
// phone can pair with a specific named sensor (e.g. "Eric01", "Spencer01")
// over Bluetooth LE without going through the cloud or T2 gateway.
//
// Coexistence:
//   - The ESP32-S3 has BLE 5.0 in silicon, separate from WiFi (2.4 GHz radio
//     time-shared) and from the SX1262 LoRa module (separate SPI bus). The BLE
//     peripheral therefore runs in parallel with LoRa unconditionally and with
//     the WiFi promiscuous scan modulo Espressif's controller arbitration.
//   - All NimBLE callbacks run on NimBLE's own FreeRTOS task; we never call
//     into RadioLib / SX1262 from inside a callback. Cmd writes are queued and
//     drained from ble_companion_tick() on the main loop core.
//
// Build gating:
//   - All code in this module is compiled only when PDL_BLE_COMPANION_ENABLED
//     is defined (set in the _interop env). The standard env is unaffected.
//
// GATT service:
//   12345678-pdl0-4001-8001-pdl000000001
//
//   0x2A29 Manufacturer Name (read)         "PDL Consulting LLC"
//   0x2A24 Model Number      (read)         "T1 RID Sensor"
//   0x2A25 Serial Number     (read)         SENSOR_NAME (e.g. "Eric01")
//   pdl0001 Status        (read + notify)   JSON status @ 1 Hz, ≤244 bytes
//   pdl0002 Networks      (read)            JSON array of observed nets
//   pdl0003 Detections    (read + notify)   newest detection JSON
//   pdl0004 AuthChallenge (read)            32-byte random nonce
//   pdl0005 AuthResponse  (write)           {callsign, hmac_sha256(pin, nonce)}
//   pdl0006 Cmd           (write)           32-byte CMD_PACKET wire format
//   pdl0007 CmdAck        (notify)          16-byte CMD_ACK wire format
//
// Pairing:
//   NimBLE secure pairing with passkey display = last 6 hex digits of the
//   sensor_id (so the phone shows "Pair with PDL-Eric01? code 8DD13B" and the
//   operator can verify against the OLED).
//
// Authentication flow (operator pin HMAC):
//   1. On every connect, the sensor refreshes a 32-byte random nonce in
//      AuthChallenge.
//   2. Phone reads AuthChallenge, computes
//        resp = HMAC_SHA256(operator_pin_hmac, nonce)
//      and writes {callsign[16], resp[32]} to AuthResponse.
//   3. Sensor compares resp against an allowlist of authorized operator
//      pin_hmacs (provisioned via OTA or hardcoded fingerprint list for v1).
//      On match the Cmd characteristic is unlocked for 8 hours; AuthResponse
//      write returns 0x00 (NimBLE write callback rc=0). Mismatch returns 0x01.
//   4. Cmd writes outside the auth window are rejected with rc=0x02.
//
// Audit:
//   Every Cmd write is recorded via authgate_audit_append() with
//   origin = "ble:" + callsign (encoded into the audit entry's reserved area).
// ============================================================================

#include <cstdint>
#include <cstddef>
#include "config.h"

#ifdef __cplusplus
extern "C" {
#endif

// ---------------------------------------------------------------------------
// GATT UUIDs (string form — 128-bit). The native impl converts to NimBLE
// NimBLEUUID at registration time.
// ---------------------------------------------------------------------------
// FIXED 2026-07-26: these were "12345678-pdl0-4001-8001-pdl0000001XX" — the
// literal ASCII "pdl0"/"pdl" placeholders are not valid hex digits (p, l).
// NimBLE-Arduino's UUID parser silently mangled them, and ALL TEN of these
// (service + 9 characteristics) collapsed to the SAME runtime UUID
// (confirmed via live bleak scan: every custom characteristic reported
// 12345678-0000-000d-0000-000000004001, indistinguishable by UUID). Fixed
// to valid hex matching what the PDLIcarusV2 Android app already assumed.
#define BLE_PDL_SERVICE_UUID        "12345678-0000-4001-8001-000000000001"
#define BLE_PDL_CHAR_STATUS_UUID    "12345678-0000-4001-8001-000000000101"  // pdl0001
#define BLE_PDL_CHAR_NETWORKS_UUID  "12345678-0000-4001-8001-000000000102"  // pdl0002
#define BLE_PDL_CHAR_DETECT_UUID    "12345678-0000-4001-8001-000000000103"  // pdl0003
#define BLE_PDL_CHAR_CHALLENGE_UUID "12345678-0000-4001-8001-000000000104"  // pdl0004
#define BLE_PDL_CHAR_AUTHRESP_UUID  "12345678-0000-4001-8001-000000000105"  // pdl0005
#define BLE_PDL_CHAR_CMD_UUID       "12345678-0000-4001-8001-000000000106"  // pdl0006
#define BLE_PDL_CHAR_CMDACK_UUID    "12345678-0000-4001-8001-000000000107"  // pdl0007
// pdl0008/pdl0009 (ProxyTx/ProxyRx) are registered separately by
// ble_proxy_chars.cpp via ble_companion_get_service() -- see that module.
#define BLE_PDL_CHAR_VQC_UUID       "12345678-0000-4001-8001-000000000110"  // pdl0010
#define BLE_PDL_CHAR_WIFI_UUID      "12345678-0000-4001-8001-000000000111"  // pdl0011

// Standard 16-bit UUIDs (Bluetooth SIG)
#define BLE_STD_CHAR_MFR_NAME      0x2A29
#define BLE_STD_CHAR_MODEL_NUMBER  0x2A24
#define BLE_STD_CHAR_SERIAL_NUMBER 0x2A25

// Wire constants
#define BLE_PDL_CHALLENGE_BYTES         32
#define BLE_PDL_AUTHRESP_HMAC_BYTES     32
#define BLE_PDL_AUTHRESP_CALLSIGN_BYTES 16
#define BLE_PDL_AUTHRESP_TOTAL_BYTES   (BLE_PDL_AUTHRESP_CALLSIGN_BYTES + \
                                        BLE_PDL_AUTHRESP_HMAC_BYTES)  // 48
#define BLE_PDL_AUTH_WINDOW_MS          (8u * 60u * 60u * 1000u)  // 8 h
#define BLE_PDL_STATUS_JSON_MAX         244   // BLE 5 default ATT MTU - 3
#define BLE_PDL_NOTIFY_QUEUE_DEPTH      8     // pending detection notifications
#define BLE_PDL_AUTHORIZED_HMAC_SLOTS   8     // allowlist length

// Authentication-result bytes returned via NimBLE write status code.
#define BLE_PDL_OK                  0x00
#define BLE_PDL_ERR_AUTH_FAIL       0x01
#define BLE_PDL_ERR_NOT_AUTH        0x02
#define BLE_PDL_ERR_BAD_LEN         0x03
#define BLE_PDL_ERR_BAD_CMD         0x04

// ---------------------------------------------------------------------------
// Stats snapshot returned by ble_companion_get_stats().
// ---------------------------------------------------------------------------
struct BleCompanionStats {
    bool     advertising;
    bool     connected;
    bool     authenticated;
    uint32_t auth_expires_at_ms;   // 0 if not authenticated
    uint32_t connect_count;
    uint32_t auth_success_count;
    uint32_t auth_fail_count;
    uint32_t cmd_accepted_count;
    uint32_t cmd_rejected_count;
    uint32_t notify_dropped_count;
    char     last_callsign[BLE_PDL_AUTHRESP_CALLSIGN_BYTES + 1];
};

// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------

// Initialize BLE peripheral. Call once from setup() AFTER enrollment_init().
// Creates the NimBLE server/service/characteristics but does NOT start the
// service or advertising yet — call ble_companion_start_advertising() for
// that, AFTER any other module (e.g. ble_proxy_chars) has had a chance to
// add more characteristics via ble_companion_get_service(). NimBLE rejects
// new characteristics on an already-started service. Safe to call multiple
// times (subsequent calls are no-ops).
void ble_companion_init();

// Returns the NimBLEService* (as an opaque void* so this header stays
// NimBLE-dependency-free) that ble_companion_init() created, or nullptr if
// not yet initialized / BLE not compiled into this build. Callers that need
// the real type (e.g. ble_proxy_chars.cpp) cast back to NimBLEService*.
void* ble_companion_get_service();

// Starts the GATT service and begins advertising. Call once, AFTER
// ble_companion_init() and after any other module has finished adding
// characteristics via ble_companion_get_service().
void ble_companion_start_advertising();

// Drive periodic work: refresh status JSON @ 1 Hz, drain any pending Cmd
// writes from the NimBLE-task queue and dispatch them on the main core, push
// queued notifications. Call once per main-loop cycle (existing 1 Hz cadence).
void ble_companion_tick();

// Push a detection event; if a phone is connected with notifications enabled,
// it will be serialized to JSON and emitted via the Detections characteristic.
// If no phone is connected the call is a no-op (events are not buffered).
void ble_companion_publish_detection(const DetectionEvent& evt);

// Shared JSON encoder for a DetectionEvent (sensor_id/ts/class/conf/rssi/
// flags/lat/lon/sig) — also used by c2_wifi_bridge.cpp so a direct WiFi POST
// to OpenForge C2 carries byte-identical fields to the BLE-notify path.
size_t build_detection_json(char* buf, size_t cap, const DetectionEvent& e);

// Shared JSON encoder for a threat_vqc sample (sensor_id/gateway_id/
// timestamp/feats/vqc_score) — same field shape as main.cpp's MQTT vqc
// payload. Public (not static) so host tests can verify the 244-byte BLE
// budget directly, same as build_detection_json above.
size_t build_vqc_json(char* buf, size_t cap, const char* sensor_name,
                      const float feats[5], float vqc_score, uint32_t ts_utc_s);

// Push the current Markov NLL value into the status JSON (caller calls this
// at whatever rate they want; the Status notify happens on the 1 Hz tick).
void ble_companion_publish_nll(float nll);

// Push a threat_vqc sample (real live 5-feature input + score) for real-data
// eval collection (see threat_vqc.h / tools/eval_threat_vqc.cpp). Serialized
// to JSON and emitted via the VqcSample characteristic on the next tick, same
// as ble_companion_publish_detection(). This is the "phone-as-T2" path: an
// alternative to the MQTT publish in main.cpp's PDL_INTEROP_ENABLED block --
// use this one when no WiFi/MQTT broker is available (e.g. no T2 gateway
// assessed yet), by connecting the PDL Tactical app directly over BLE. The
// phone fills in gateway_id (T1 doesn't know it) and forwards to
// POST /api/sensor/vqc-sample on OpenForge C2. Score itself is still
// observational-only -- this does not change what threat_vqc gates (nothing).
void ble_companion_publish_vqc_sample(const float feats[5], float vqc_score,
                                      uint32_t ts_utc_s);

// True if a phone is currently connected (paired or not). Useful for OLED
// state indication.
bool ble_companion_is_connected();

// Snapshot module stats. out must be non-null.
void ble_companion_get_stats(BleCompanionStats* out);

// ---------------------------------------------------------------------------
// Audit-origin helper. Emits an audit entry for a BLE-origin Cmd write.
// Public so command_handler integration tests can synthesize entries.
// origin_str is "ble:" + callsign and gets folded into the audit token-hash
// area (the audit format reserves bytes 23..30 for the token hash).
// ---------------------------------------------------------------------------
void ble_companion_audit_cmd(const uint8_t* cmd_buf,
                             size_t         cmd_len,
                             const char*    callsign,
                             uint8_t        result);

// ---------------------------------------------------------------------------
// Test seam (UNIT_TEST only): direct injection of an authorized operator
// pin_hmac (32 bytes). Up to BLE_PDL_AUTHORIZED_HMAC_SLOTS slots; returns
// false if the table is full.
// ---------------------------------------------------------------------------
bool ble_companion_add_authorized_pin_hmac(const uint8_t pin_hmac[32]);

// Authorize a plaintext operator PIN (e.g. from a provisioning UI or
// deployment config) by hashing it with SHA-256 and adding the result to the
// allowlist. ble_companion_init() calls this once at boot with the factory
// default PIN ("000000") so command auth works out of the box; call again
// with a real per-deployment PIN to add it alongside (does not clear
// existing entries). No-op if pin is null/empty.
void ble_companion_set_operator_pin(const char* pin);

// Force-set the current challenge (UNIT_TEST or post-pair refresh).
void ble_companion_set_challenge(const uint8_t nonce[BLE_PDL_CHALLENGE_BYTES]);

// Compute the HMAC-SHA256 used by the auth handshake. Exposed for tests so
// the host-side expectations can be matched against device behaviour without
// mocking NimBLE.
void ble_companion_hmac_sha256(const uint8_t* key, size_t key_len,
                               const uint8_t* msg, size_t msg_len,
                               uint8_t out[32]);

// Process an AuthResponse write payload (callsign[16] + hmac[32]). Returns
// true if the HMAC matches an authorized operator and the auth window opens.
// Public so the NimBLE write callback can call it from inside its task and so
// host tests can drive auth without touching NimBLE.
bool ble_companion_process_auth_response(const uint8_t* payload, size_t len,
                                         uint32_t now_ms);

// Process a Cmd write (caller must have already verified the auth window).
// Returns one of BLE_PDL_OK / BLE_PDL_ERR_NOT_AUTH / BLE_PDL_ERR_BAD_LEN.
// The 16-byte CMD_ACK that command_handler emits is captured into out_ack
// (must be at least 16 bytes); *out_ack_len is set to the actual length.
uint8_t ble_companion_process_cmd(const uint8_t* cmd_buf,
                                  size_t         cmd_len,
                                  uint32_t       now_ms,
                                  uint8_t*       out_ack,
                                  size_t*        out_ack_len);

// True if the auth window is currently open as of now_ms.
bool ble_companion_is_authenticated(uint32_t now_ms);

#ifdef UNIT_TEST
// Reset module to a clean state — host-test only.
void ble_companion_test_reset();
#endif

#ifdef __cplusplus
}
#endif

#endif // BLE_COMPANION_H
