// ============================================================================
// uav_pairing.cpp — see uav_pairing.h for design notes and explicitly
// out-of-scope pieces.
// ============================================================================

#include "uav_pairing.h"
#include "uav_relay.h"   // MSG_TYPE_UAV_PAIR_REQ/RSP

#include <cstring>
#include <cstdio>

#ifndef UNIT_TEST
#  include "nvs_flash.h"
#  include "nvs.h"
#else
#  include <cstdlib>
#endif

#define UAV_PAIR_NVS_NS              "pdl_uavpair"
#define UAV_PAIR_NVS_KEY_ROOT        "root"
#define UAV_PAIR_NVS_KEY_CERT_HASH   "cert"
#define UAV_PAIR_NVS_KEY_DRONE_ID    "drone_id"
#define UAV_PAIR_NVS_KEY_CALLSIGN    "callsign"
#define UAV_PAIR_NVS_KEY_POLICY_HASH "policy"
#define UAV_PAIR_NVS_KEY_ALLOW_TERM  "allow_term"
#define UAV_PAIR_NVS_KEY_PROFILE     "profile"

// ---------------------------------------------------------------------------
// Shared in-RAM state (mirrors NVS; real builds write-through, native
// tests keep this as the only backing store, same split as auth_gate.cpp).
// ---------------------------------------------------------------------------
namespace {
bool     g_paired = false;
uint8_t  g_hmac_root[32] = {0};
uint8_t  g_operator_cert_hash[8] = {0};
char     g_drone_id[24] = {0};
char     g_operator_callsign[24] = {0};
uint8_t  g_policy_hash[32] = {0};
bool     g_allow_terminate = false;
UavDroneProfile g_profile = UavDroneProfile::ARDUPILOT_COPTER;

bool     g_pair_window_open = false;
uint32_t g_pair_window_deadline_ms = 0;
}  // namespace

// ---------------------------------------------------------------------------
// Pure frame parse/build — no NVS, no radio.
// ---------------------------------------------------------------------------

bool uav_pairing_parse_request(const uint8_t* data, size_t len, UavPairingData& out) {
    if (!data || len != UAV_PAIR_REQ_FRAME_SIZE) return false;
    if (data[0] != MSG_TYPE_UAV_PAIR_REQ) return false;

    memcpy(out.hmac_root, data + 1, 32);
    memcpy(out.operator_cert_hash, data + 33, 8);
    memcpy(out.drone_id, data + 41, 24);
    out.drone_id[sizeof(out.drone_id) - 1] = '\0';
    memcpy(out.operator_callsign, data + 65, 24);
    out.operator_callsign[sizeof(out.operator_callsign) - 1] = '\0';
    memcpy(out.policy_manifest_hash, data + 89, 32);
    out.allow_terminate = (data[121] != 0);
    out.profile = (UavDroneProfile)data[122];
    return true;
}

size_t uav_pairing_build_response(const UavPairResponseFrame& r, uint8_t* out, size_t out_len) {
    if (!out || out_len < UAV_PAIR_RSP_FRAME_SIZE) return 0;
    out[0] = MSG_TYPE_UAV_PAIR_RSP;
    memcpy(out + 1, r.vehicle_hash, 8);
    out[9]  = r.fw_major;
    out[10] = r.fw_minor;
    out[11] = r.fw_patch;
    out[12] = (uint8_t)r.status;
    out[13] = 0;   // reserved
    // Same CRC-16-CCITT (poly 0x1021, init 0, no reflect) as
    // drone-companion's ack_crc16() -- this frame crosses codebases so the
    // framing must byte-for-byte match on both ends.
    uint16_t crc = 0;
    for (int i = 0; i < 14; i++) {
        crc ^= (uint16_t)out[i] << 8;
        for (int b = 0; b < 8; b++) {
            crc = (crc & 0x8000) ? ((crc << 1) ^ 0x1021) : (crc << 1);
        }
    }
    out[14] = (uint8_t)((crc >> 8) & 0xFF);
    out[15] = (uint8_t)(crc & 0xFF);
    return UAV_PAIR_RSP_FRAME_SIZE;
}

// ---------------------------------------------------------------------------
// NVS persistence
// ---------------------------------------------------------------------------

static void persist_pairing() {
#ifndef UNIT_TEST
    nvs_handle_t h;
    if (nvs_open(UAV_PAIR_NVS_NS, NVS_READWRITE, &h) != ESP_OK) return;
    nvs_set_blob(h, UAV_PAIR_NVS_KEY_ROOT, g_hmac_root, sizeof(g_hmac_root));
    nvs_set_blob(h, UAV_PAIR_NVS_KEY_CERT_HASH, g_operator_cert_hash, sizeof(g_operator_cert_hash));
    nvs_set_str(h, UAV_PAIR_NVS_KEY_DRONE_ID, g_drone_id);
    nvs_set_str(h, UAV_PAIR_NVS_KEY_CALLSIGN, g_operator_callsign);
    nvs_set_blob(h, UAV_PAIR_NVS_KEY_POLICY_HASH, g_policy_hash, sizeof(g_policy_hash));
    nvs_set_u8(h, UAV_PAIR_NVS_KEY_ALLOW_TERM, g_allow_terminate ? 1 : 0);
    nvs_set_u8(h, UAV_PAIR_NVS_KEY_PROFILE, (uint8_t)g_profile);
    nvs_commit(h);
    nvs_close(h);
#endif
}

static bool load_persisted_pairing() {
#ifndef UNIT_TEST
    nvs_handle_t h;
    if (nvs_open(UAV_PAIR_NVS_NS, NVS_READONLY, &h) != ESP_OK) return false;
    size_t root_len = sizeof(g_hmac_root);
    esp_err_t err = nvs_get_blob(h, UAV_PAIR_NVS_KEY_ROOT, g_hmac_root, &root_len);
    if (err != ESP_OK || root_len != sizeof(g_hmac_root)) {
        nvs_close(h);
        return false;
    }
    size_t cert_len = sizeof(g_operator_cert_hash);
    nvs_get_blob(h, UAV_PAIR_NVS_KEY_CERT_HASH, g_operator_cert_hash, &cert_len);
    size_t did_len = sizeof(g_drone_id);
    nvs_get_str(h, UAV_PAIR_NVS_KEY_DRONE_ID, g_drone_id, &did_len);
    size_t cs_len = sizeof(g_operator_callsign);
    nvs_get_str(h, UAV_PAIR_NVS_KEY_CALLSIGN, g_operator_callsign, &cs_len);
    size_t pol_len = sizeof(g_policy_hash);
    nvs_get_blob(h, UAV_PAIR_NVS_KEY_POLICY_HASH, g_policy_hash, &pol_len);
    uint8_t allow_term = 0;
    nvs_get_u8(h, UAV_PAIR_NVS_KEY_ALLOW_TERM, &allow_term);
    g_allow_terminate = (allow_term != 0);
    uint8_t profile = 0;
    nvs_get_u8(h, UAV_PAIR_NVS_KEY_PROFILE, &profile);
    g_profile = (UavDroneProfile)profile;
    nvs_close(h);
    return true;
#else
    return false;   // native tests never have a prior-boot NVS to load
#endif
}

// ---------------------------------------------------------------------------
// State machine
// ---------------------------------------------------------------------------

void uav_pairing_init(uint32_t now_ms) {
    g_paired = load_persisted_pairing();
    if (!g_paired) {
        g_pair_window_open = true;
        g_pair_window_deadline_ms = now_ms + UAV_PAIR_WINDOW_MS;
    }
}

void uav_pairing_tick(uint32_t now_ms, uint32_t button_held_ms) {
    if (g_pair_window_open && now_ms >= g_pair_window_deadline_ms) {
        g_pair_window_open = false;
    }
    if (button_held_ms >= UAV_PAIR_BTN_HOLD_MS && !g_pair_window_open) {
        g_pair_window_open = true;
        g_pair_window_deadline_ms = now_ms + UAV_PAIR_WINDOW_MS;
    }
}

bool uav_pairing_is_paired() { return g_paired; }

bool uav_pairing_get_hmac_root(uint8_t out[32]) {
    if (!g_paired) return false;
    memcpy(out, g_hmac_root, 32);
    return true;
}

bool uav_pairing_window_open() { return g_pair_window_open; }

bool uav_pairing_process_request(const uint8_t* data, size_t len, uint32_t now_ms,
                                 const uint8_t vehicle_hash[8],
                                 uint8_t out_response[UAV_PAIR_RSP_FRAME_SIZE],
                                 size_t* out_response_len) {
    if (out_response_len) *out_response_len = 0;

    UavPairingData pd{};
    if (!uav_pairing_parse_request(data, len, pd)) return false;

    UavPairResponseFrame resp{};
    memcpy(resp.vehicle_hash, vehicle_hash, 8);
    unsigned major = 0, minor = 0, patch = 0;
#ifdef SENSOR_FW_VERSION
    sscanf(SENSOR_FW_VERSION, "%u.%u.%u", &major, &minor, &patch);
#endif
    resp.fw_major = (uint8_t)major;
    resp.fw_minor = (uint8_t)minor;
    resp.fw_patch = (uint8_t)patch;

    if (!g_pair_window_open) {
        resp.status = UavPairRspStatus::PAIR_MODE_REQUIRED;
        size_t n = uav_pairing_build_response(resp, out_response, UAV_PAIR_RSP_FRAME_SIZE);
        if (out_response_len) *out_response_len = n;
        return true;
    }

    memcpy(g_hmac_root, pd.hmac_root, 32);
    memcpy(g_operator_cert_hash, pd.operator_cert_hash, 8);
    strncpy(g_drone_id, pd.drone_id, sizeof(g_drone_id) - 1);
    g_drone_id[sizeof(g_drone_id) - 1] = '\0';
    strncpy(g_operator_callsign, pd.operator_callsign, sizeof(g_operator_callsign) - 1);
    g_operator_callsign[sizeof(g_operator_callsign) - 1] = '\0';
    memcpy(g_policy_hash, pd.policy_manifest_hash, 32);
    g_allow_terminate = pd.allow_terminate;
    g_profile = pd.profile;
    g_paired = true;
    persist_pairing();

    g_pair_window_open = false;
    resp.status = UavPairRspStatus::OK;
    size_t n = uav_pairing_build_response(resp, out_response, UAV_PAIR_RSP_FRAME_SIZE);
    if (out_response_len) *out_response_len = n;
    return true;
}

#ifdef UNIT_TEST
void uav_pairing_test_reset() {
    g_paired = false;
    memset(g_hmac_root, 0, sizeof(g_hmac_root));
    memset(g_operator_cert_hash, 0, sizeof(g_operator_cert_hash));
    memset(g_drone_id, 0, sizeof(g_drone_id));
    memset(g_operator_callsign, 0, sizeof(g_operator_callsign));
    memset(g_policy_hash, 0, sizeof(g_policy_hash));
    g_allow_terminate = false;
    g_profile = UavDroneProfile::ARDUPILOT_COPTER;
    g_pair_window_open = false;
    g_pair_window_deadline_ms = 0;
}
#endif
