/* SPDX-FileCopyrightText: 2024 Google LLC */
/* SPDX-License-Identifier: Apache-2.0 */

#include <string.h>
#include <inttypes.h>

#include "services/common/analytics/analytics.h"
#include "services/common/analytics/analytics_event.h"
#include "services/common/analytics/analytics_logging.h"
#include "services/common/analytics/analytics_storage.h"

#include "apps/system_apps/launcher/launcher_app.h"
#include "comm/ble/gap_le_connection.h"
#include "comm/bt_lock.h"
#include "comm/ble/gap_le_connection.h"
#include "kernel/pbl_malloc.h"
#include "services/common/comm_session/session_internal.h"
#include "services/normal/alarms/alarm.h"
#include "services/normal/timeline/timeline.h"
#include "syscall/syscall.h"
#include "syscall/syscall_internal.h"
#include "system/logging.h"
#include "system/passert.h"
#include "util/size.h"
#include "util/time/time.h"

_Static_assert(sizeof(AnalyticsEventBlob) == 36,
      "When the blob format or size changes, be sure to bump up ANALYTICS_EVENT_BLOB_VERSION");

// ------------------------------------------------------------------------------------------
// Log an app launch event
static bool prv_send_uuid(AnalyticsEvent event_enum, const Uuid *uuid) {
  if (uuid_is_invalid(uuid) || uuid_is_system(uuid)) {
    // No need to log apps with invalid uuids. This is typically built-in test apps like "Light
    // config" that we don't bother to declare a UUID for
    return false;
  }

  // FIXME: The sdkshell doesn't have a launcher menu so this causes a linker error. Maybe the
  // mapping of events to analytics should also be shell-specific?
#ifndef SHELL_SDK
  // No need to log the launcher menu app
  if (uuid_equal(uuid, &launcher_menu_app_get_app_info()->uuid)) {
    return false;
  }
#endif

  return true;
}

// ------------------------------------------------------------------------------------------
// Log an out-of-memory situation for an app.

void analytics_event_app_oom(AnalyticsEvent type,
                             uint32_t requested_size, uint32_t total_size, uint32_t total_free,
                             uint32_t largest_free_block) {
  PBL_ASSERTN(type == AnalyticsEvent_AppOOMNative || type == AnalyticsEvent_AppOOMRocky);

  AnalyticsEventBlob event_blob = {
    .event = type,
    .app_oom = {
      .requested_size = requested_size,
      .total_size = total_size,
      .total_free = MIN(total_free, UINT16_MAX),
      .largest_free_block = MIN(largest_free_block, UINT16_MAX),
    },
  };
  if (!sys_process_manager_get_current_process_uuid(&event_blob.app_oom.app_uuid)) {
    // Process has no UUID
    return;
  }

#if LOG_DOMAIN_ANALYTICS
  ANALYTICS_LOG_DEBUG("app oom: is_rocky=%u, req_sz=%"PRIu32" tot_sz=%"PRIu32" free=%"PRIu32
                      " max_free=%"PRIu32,
                      (type == AnalyticsEvent_AppOOMRocky),
                      requested_size, total_size, total_free, largest_free_block);
#endif

  sys_analytics_logging_log_event(&event_blob);
}

// ------------------------------------------------------------------------------------------
// Log a generic app launch event
void analytics_event_app_launch(const Uuid *uuid) {
  if (!prv_send_uuid(AnalyticsEvent_AppLaunch, uuid)) {
    return;
  }

  // Format the event specifc info in the blob. The analytics_logging_log_event() method will fill
  // in the common fields
  AnalyticsEventBlob event_blob = {
    .event = AnalyticsEvent_AppLaunch,
    .app_launch.uuid = *uuid,
  };

#if LOG_DOMAIN_ANALYTICS
  char uuid_string[UUID_STRING_BUFFER_LENGTH];
  uuid_to_string(uuid, uuid_string);
  ANALYTICS_LOG_DEBUG("app launch event: uuid %s", uuid_string);
#endif

  analytics_logging_log_event(&event_blob);
}


// ------------------------------------------------------------------------------------------
// Log a pin open/create/update event.
static void prv_simple_pin_event(time_t timestamp, const Uuid *parent_id,
                                 AnalyticsEvent event_enum, const char *verb) {
  // Format the event specifc info in the blob. The analytics_logging_log_event() method will fill
  // in the common fields
  AnalyticsEventBlob event_blob = {
    .event = event_enum,
    .pin_open_create_update.time_utc = timestamp,
    .pin_open_create_update.parent_id = *parent_id,
  };

#if LOG_DOMAIN_ANALYTICS
  char uuid_string[UUID_STRING_BUFFER_LENGTH];
  uuid_to_string(&event_blob.pin_open_create_update.parent_id, uuid_string);
  ANALYTICS_LOG_DEBUG("pin %s event: timestamp: %"PRIu32", uuid:%s", verb,
                      event_blob.pin_open_create_update.time_utc, uuid_string);
#endif

  analytics_logging_log_event(&event_blob);
}


// ------------------------------------------------------------------------------------------
// Log a pin open event.
void analytics_event_pin_open(time_t timestamp, const Uuid *parent_id) {
  prv_simple_pin_event(timestamp, parent_id, AnalyticsEvent_PinOpen, "open");
}


// ------------------------------------------------------------------------------------------
// Log a pin created event.
void analytics_event_pin_created(time_t timestamp, const Uuid *parent_id) {
  prv_simple_pin_event(timestamp, parent_id, AnalyticsEvent_PinCreated, "created");
}


// ------------------------------------------------------------------------------------------
// Log a pin updated event.
void analytics_event_pin_updated(time_t timestamp, const Uuid *parent_id) {
  prv_simple_pin_event(timestamp, parent_id, AnalyticsEvent_PinUpdated, "updated");
}


// ------------------------------------------------------------------------------------------
// Log a pin action event.
void analytics_event_pin_action(time_t timestamp, const Uuid *parent_id,
                                TimelineItemActionType action_type) {
  // Format the event specifc info in the blob. The analytics_logging_log_event() method will fill
  // in the common fields
  AnalyticsEventBlob event_blob = {
    .event = AnalyticsEvent_PinAction,
    .pin_action.time_utc = timestamp,
    .pin_action.parent_id = *parent_id,
    .pin_action.type = action_type,
  };

#if LOG_DOMAIN_ANALYTICS
  char uuid_string[UUID_STRING_BUFFER_LENGTH];
  uuid_to_string(&event_blob.pin_action.parent_id, uuid_string);
  ANALYTICS_LOG_DEBUG("pin action event: timestamp: %"PRIu32", uuid:%s, action:%"PRIu8,
                      event_blob.pin_action.time_utc, uuid_string, action_type);
#endif

  analytics_logging_log_event(&event_blob);
}


// ------------------------------------------------------------------------------------------
// Log a pin app launch event.
void analytics_event_pin_app_launch(time_t timestamp, const Uuid *parent_id) {
  if (!prv_send_uuid(AnalyticsEvent_PinAppLaunch, parent_id)) {
    return;
  }

  // Format the event specifc info in the blob. The analytics_logging_log_event() method will fill
  // in the common fields
  AnalyticsEventBlob event_blob = {
    .event = AnalyticsEvent_PinAppLaunch,
    .pin_app_launch.time_utc = timestamp,
    .pin_app_launch.parent_id = *parent_id,
  };

#if LOG_DOMAIN_ANALYTICS
  char uuid_string[UUID_STRING_BUFFER_LENGTH];
  uuid_to_string(&event_blob.pin_app_launch.parent_id, uuid_string);
  ANALYTICS_LOG_DEBUG("pin app launch event: timestamp: %"PRIu32", uuid:%s",
                      event_blob.pin_app_launch.time_utc, uuid_string);
#endif

  analytics_logging_log_event(&event_blob);
}


// ------------------------------------------------------------------------------------------
// Log a canned response event
void analytics_event_canned_response(const char *response, bool successfully_sent) {
  // Format the event specific info in the blob. The analytics_logging_log_event() method will fill
  // in the common fields
  AnalyticsEventBlob event_blob = {
    .event = successfully_sent ? AnalyticsEvent_CannedReponseSent
                               : AnalyticsEvent_CannedReponseFailed,
  };

  if (!response) {
    event_blob.canned_response.response_size_bytes = 0;
  } else {
    event_blob.canned_response.response_size_bytes = strlen(response);
  }

  if (successfully_sent) {
    ANALYTICS_LOG_DEBUG("canned response sent event: response_size_bytes:%d",
                        event_blob.canned_response.response_size_bytes);
  } else {
    ANALYTICS_LOG_DEBUG("canned response failed event: response_size_bytes:%d",
                        event_blob.canned_response.response_size_bytes);
  }
  analytics_logging_log_event(&event_blob);
}


// ------------------------------------------------------------------------------------------
// Log a voice response event
void analytics_event_voice_response(AnalyticsEvent event_type, uint16_t response_size_bytes,
                                    uint16_t response_len_chars, uint32_t response_len_ms,
                                    uint8_t error_count, uint8_t num_sessions, Uuid *app_uuid) {

  PBL_ASSERTN((event_type >= AnalyticsEvent_VoiceTranscriptionAccepted) &&
      (event_type <= AnalyticsEvent_VoiceTranscriptionAutomaticallyAccepted));

  // Format the event specific info in the blob. The analytics_logging_log_event() method will fill
  // in the common fields

  AnalyticsEventBlob event_blob = {
    .event = event_type,
  };

  event_blob.voice_response = (AnalyticsEventVoiceResponse) {
      .response_size_bytes = response_size_bytes,
      .response_len_chars = response_len_chars,
      .response_len_ms = response_len_ms,
      .num_sessions = num_sessions,
      .error_count = error_count,
      .app_uuid = *app_uuid,
  };

  const char *msg = "Other";
  switch (event_type) {
    case AnalyticsEvent_VoiceTranscriptionAccepted:
      msg = "Accepted";
      break;
    case AnalyticsEvent_VoiceTranscriptionRejected:
      msg = "Rejected";
      break;
    case AnalyticsEvent_VoiceTranscriptionAutomaticallyAccepted:
      msg = "Automatically accepted";
      break;
    default:
      break;
  }

    ANALYTICS_LOG_DEBUG("voice response %s event: size: %"PRIu16"; length (chars): %"PRIu16
        "; length (ms): %"PRIu32"; Errors: %"PRIu8"; Sessions: %"PRIu8, msg,
        event_blob.voice_response.response_size_bytes, event_blob.voice_response.response_len_chars,
        event_blob.voice_response.response_len_ms, event_blob.voice_response.error_count,
        event_blob.voice_response.num_sessions);
  // Use syscall because this is called by voice_window
  analytics_logging_log_event(&event_blob);
}

// ------------------------------------------------------------------------------------------
// Log a BLE HRM event
void analytics_event_ble_hrm(BleHrmEventSubtype subtype) {
  AnalyticsEventBlob event_blob = {
    .event = AnalyticsEvent_BleHrmEvent,
    .ble_hrm = {
      .subtype = subtype,
    },
  };

  ANALYTICS_LOG_DEBUG("BLE HRM Event %u", subtype);

  analytics_logging_log_event(&event_blob);
}

// ------------------------------------------------------------------------------------------
// Log a bluetooth disconnection event
void analytics_event_bt_connection_or_disconnection(AnalyticsEvent type, uint8_t reason) {
  AnalyticsEventBlob event_blob = {
    .event = type,
  };

  event_blob.bt_connection_disconnection.reason = reason;

  ANALYTICS_LOG_DEBUG("Event %d - BT (dis)connection: Reason: %"PRIu8,
                      event_blob.event,
                      event_blob.bt_connection_disconnection.reason);

  analytics_logging_log_event(&event_blob);
}

void analytics_event_bt_le_disconnection(uint8_t reason, uint8_t remote_bt_version,
                                         uint16_t remote_bt_company_id,
                                         uint16_t remote_bt_subversion) {
  AnalyticsEventBlob event_blob = {
    .event = AnalyticsEvent_BtLeDisconnect,
    .ble_disconnection = {
      .reason = reason,
      .remote_bt_version = remote_bt_version,
      .remote_bt_company_id = remote_bt_company_id,
      .remote_bt_subversion_number = remote_bt_subversion,
    }
  };

  ANALYTICS_LOG_DEBUG("Event %d - BT disconnection: Reason: %"PRIu8, event_blob.event,
                      event_blob.bt_connection_disconnection.reason);

  analytics_logging_log_event(&event_blob);
}


// ------------------------------------------------------------------------------------------
// Log a bluetooth error
void analytics_event_bt_error(AnalyticsEvent type, uint32_t error) {
  AnalyticsEventBlob event_blob = {};
  event_blob.event = type,
  event_blob.bt_error.error_code = error;

  ANALYTICS_LOG_DEBUG("bluetooth event %d - error: %"PRIu32,
                      event_blob.event,
                      event_blob.bt_error.error_code);

  analytics_logging_log_event(&event_blob);
}

// ------------------------------------------------------------------------------------------
//! Log when app_launch trigger failed.
void analytics_event_bt_app_launch_error(uint8_t gatt_error) {
  analytics_event_bt_error(AnalyticsEvent_BtAppLaunchError, gatt_error);
}

// ------------------------------------------------------------------------------------------
//! Log when a Pebble Protocol session is closed.
void analytics_event_session_close(bool is_system_session, const Uuid *optional_app_uuid,
                                   CommSessionCloseReason reason, uint16_t session_duration_mins) {
  AnalyticsEventBlob event_blob = {};
  event_blob.event = (is_system_session ? AnalyticsEvent_PebbleProtocolSystemSessionEnd :
                                          AnalyticsEvent_PebbleProtocolAppSessionEnd);
  event_blob.pp_common_session_close.close_reason = reason;
  event_blob.pp_common_session_close.duration_minutes = session_duration_mins;

  if (!is_system_session && optional_app_uuid) {
    memcpy(&event_blob.pp_app_session_close.app_uuid, optional_app_uuid, sizeof(Uuid));
  }

#if LOG_DOMAIN_ANALYTICS
  char uuid_str[UUID_STRING_BUFFER_LENGTH];
  uuid_to_string(optional_app_uuid, uuid_str);
  ANALYTICS_LOG_DEBUG("Session close event. is_system_session=%u, uuid=%s, "
                      "reason=%u, duration_mins=%"PRIu16,
                      is_system_session, uuid_str, reason, session_duration_mins);
#endif

  analytics_logging_log_event(&event_blob);
}

// ------------------------------------------------------------------------------------------
//! Log when the CC2564x BT chip becomes unresponsive
void analytics_event_bt_cc2564x_lockup_error(void) {
  AnalyticsEventBlob event_blob = {
    .event = AnalyticsEvent_BtLockupError,
  };

  ANALYTICS_LOG_DEBUG("CC2564x lockup event");

  analytics_logging_log_event(&event_blob);
}

// ------------------------------------------------------------------------------------------
// Log a crash event
void analytics_event_crash(uint8_t crash_code, uint32_t link_register) {
  AnalyticsEventBlob event_blob = {
    .event = AnalyticsEvent_Crash,
    .crash_report.crash_code = crash_code,
    .crash_report.link_register = link_register
  };

  ANALYTICS_LOG_DEBUG("Crash occured: Code %"PRIu8" / LR: %"PRIu32,
    event_blob.crash_report.crash_code, event_blob.crash_report.link_register);

  analytics_logging_log_event(&event_blob);
}

// ------------------------------------------------------------------------------------------
// Log local bluetooth disconnection reason
void analytics_event_local_bt_disconnect(uint16_t conn_handle, uint32_t lr) {
  AnalyticsEventBlob event_blob = {
    .event = AnalyticsEvent_LocalBtDisconnect,
  };

  event_blob.local_bt_disconnect.lr = lr;
  event_blob.local_bt_disconnect.conn_handle = conn_handle;

  ANALYTICS_LOG_DEBUG("Event %d - BT Disconnect: Handle:%"PRIu16" LR: %"PRIu32,
                      event_blob.event,
                      conn_handle,
                      lr);
  analytics_logging_log_event(&event_blob);
}

// ------------------------------------------------------------------------------------------
// Log an Apple Media Service event.
void analytics_event_ams(uint8_t type, int32_t aux_info) {
  AnalyticsEventBlob event_blob = {
    .event = AnalyticsEvent_BtLeAMS,
    .ams = {
      .type = type,
      .aux_info = aux_info,
    },
  };

  ANALYTICS_LOG_DEBUG("Event %d - AMS: type:%d aux_info: %"PRId32, event_blob.event, type, aux_info);
  analytics_logging_log_event(&event_blob);
}

// ------------------------------------------------------------------------------------------
// Log stationary mode events
void analytics_event_stationary_state_change(time_t timestamp, uint8_t state_change) {
  AnalyticsEventBlob event_blob = {
    .event = AnalyticsEvent_StationaryModeSwitch,
    .sd = {
      .timestamp = timestamp,
      .state_change = state_change,
    }
  };

  analytics_logging_log_event(&event_blob);
}

// ------------------------------------------------------------------------------------------
// Log a health insight created event.
void analytics_event_health_insight_created(time_t timestamp,
                                            ActivityInsightType insight_type,
                                            PercentTier pct_tier) {
  // Format the event specifc info in the blob. The analytics_logging_log_event() method will fill
  // in the common fields
  AnalyticsEventBlob event_blob = {
    .event = AnalyticsEvent_HealthInsightCreated,
    .health_insight_created = {
      .time_utc = timestamp,
      .insight_type = insight_type,
      .percent_tier = pct_tier,
    }
  };

#if LOG_DOMAIN_ANALYTICS
  ANALYTICS_LOG_DEBUG("health insight created event: timestamp: %"PRIu32", type:%"PRIu8,
                      timestamp, insight_type);
#endif

  analytics_logging_log_event(&event_blob);
}


// ------------------------------------------------------------------------------------------
// Log a health insight response event.
void analytics_event_health_insight_response(time_t timestamp, ActivityInsightType insight_type,
                                             ActivitySessionType activity_type,
                                             ActivityInsightResponseType response_id) {
  // Format the event specifc info in the blob. The analytics_logging_log_event() method will fill
  // in the common fields
  AnalyticsEventBlob event_blob = {
    .event = AnalyticsEvent_HealthInsightResponse,
    .health_insight_response = {
      .time_utc = timestamp,
      .insight_type = insight_type,
      .activity_type = activity_type,
      .response_id = response_id,
    }
  };

#if LOG_DOMAIN_ANALYTICS
  ANALYTICS_LOG_DEBUG("health insight response event: timestamp: %"PRIu32", type:%"PRIu8 \
                      ", response:%"PRIu8, timestamp, insight_type, response_id);
#endif

  analytics_logging_log_event(&event_blob);
}

// ------------------------------------------------------------------------------------------
// Log an App Crash event
void analytics_event_app_crash(const Uuid *uuid, uint32_t pc, uint32_t lr,
                               const uint8_t *build_id, bool is_rocky_app) {
  AnalyticsEventBlob event_blob = {
    .event = (is_rocky_app ? AnalyticsEvent_RockyAppCrash : AnalyticsEvent_AppCrash),
    .app_crash_report = {
      .uuid = *uuid,
      .pc = pc,
      .lr = lr,
    },
  };

  if (build_id) {
    memcpy(event_blob.app_crash_report.build_id_slice, build_id,
           sizeof(event_blob.app_crash_report.build_id_slice));
  }

#if LOG_DOMAIN_ANALYTICS
  char uuid_string[UUID_STRING_BUFFER_LENGTH];
  uuid_to_string(uuid, uuid_string);
  ANALYTICS_LOG_DEBUG("App Crash event: uuid:%s, pc: %p, lr: %p",
                      uuid_string, (void *)pc, (void *)lr);
#endif

  analytics_logging_log_event(&event_blob);
}

extern bool comm_session_is_valid(const CommSession *session);

// ------------------------------------------------------------------------------------------
static bool prv_get_connection_details(CommSession *session, bool *is_ppogatt,
                                       uint16_t *conn_interval) {
  bt_lock();

  if (!session || !comm_session_is_valid(session)) {
    bt_unlock();
    return false;
  }

  const bool tmp_is_ppogatt =
      (comm_session_analytics_get_transport_type(session) == CommSessionTransportType_PPoGATT);

  uint16_t tmp_conn_interval = 0;
  if (tmp_is_ppogatt) {
    GAPLEConnection *conn = gap_le_connection_get_gateway();
    if (conn) {
      tmp_conn_interval = conn->conn_params.conn_interval_1_25ms;
    }
  }

  bt_unlock();

  if (is_ppogatt) {
    *is_ppogatt = tmp_is_ppogatt;
  }
  if (conn_interval) {
    *conn_interval = tmp_conn_interval;
  }
  return true;
}

void analytics_event_put_byte_stats(
    CommSession *session, bool crc_good, uint8_t type,
    uint32_t bytes_transferred, uint32_t elapsed_time_ms,
    uint32_t conn_events, uint32_t sync_errors, uint32_t skip_errors, uint32_t other_errors) {

  bool is_ppogatt = false;
  uint16_t conn_interval = 0;
  if (!prv_get_connection_details(session, &is_ppogatt, &conn_interval)) {
    return;
  }

  AnalyticsEventBlob event_blob = {
    .event = AnalyticsEvent_PutByteTime,
    .pb_time = {
      .ppogatt = is_ppogatt,
      .conn_intvl_1_25ms = MIN(conn_interval, UINT8_MAX),
      .crc_good = crc_good,
      .type = type,
      .bytes_transferred = bytes_transferred,
      .elapsed_time_ms = elapsed_time_ms,
      .conn_events = MIN(conn_events, UINT32_MAX),
      .sync_errors = MIN(sync_errors, UINT16_MAX),
      .skip_errors = MIN(skip_errors, UINT16_MAX),
      .other_errors = MIN(other_errors, UINT16_MAX),
    },
  };

  ANALYTICS_LOG_DEBUG("PutBytes event: is_ppogatt: %d, bytes: %d, time ms: %d",
                      (int)event_blob.pb_time.ppogatt,
                      (int)event_blob.pb_time.bytes_transferred,
                      (int)event_blob.pb_time.elapsed_time_ms);

  analytics_logging_log_event(&event_blob);
}

// ------------------------------------------------------------------------------------------
#if !PLATFORM_TINTIN
void analytics_event_vibe_access(VibePatternFeature vibe_feature, VibeScoreId pattern_id) {
  AnalyticsEventBlob event_blob = {
    .event = AnalyticsEvent_VibeAccess,
    . vibe_access_data = {
      .feature = (uint8_t) vibe_feature,
      .vibe_pattern_id = (uint8_t) pattern_id
    }
  };

  analytics_logging_log_event(&event_blob);
}
#endif

// ------------------------------------------------------------------------------------------
void analytics_event_alarm(AnalyticsEvent event_type, const AlarmInfo *info) {
  AnalyticsEventBlob event_blob = {
    .event = event_type,
    .alarm = {
      .hour = info->hour,
      .minute = info->minute,
      .is_smart = info->is_smart,
      .kind = info->kind,
    },
  };

  memcpy(event_blob.alarm.scheduled_days, info->scheduled_days,
         sizeof(event_blob.alarm.scheduled_days));

  analytics_logging_log_event(&event_blob);
}

// ------------------------------------------------------------------------------------------
void analytics_event_bt_chip_boot(uint8_t build_id[BUILD_ID_EXPECTED_LEN],
                                  uint32_t crash_lr, uint32_t reboot_reason_code) {
  AnalyticsEventBlob event_blob = {
    .event = AnalyticsEvent_BtChipBoot,
    .bt_chip_boot = {
      .crash_lr = crash_lr,
      .reboot_reason = reboot_reason_code,
    },
  };

  memcpy(event_blob.bt_chip_boot.build_id, build_id, sizeof(BUILD_ID_EXPECTED_LEN));

  ANALYTICS_LOG_DEBUG("BtChipBoot event: crash_lr: 0x%x, reboot_reason: %"PRIu32,
                      (int)event_blob.bt_chip_boot.crash_lr,
                      event_blob.bt_chip_boot.reboot_reason);

  analytics_logging_log_event(&event_blob);
}

// ------------------------------------------------------------------------------------------
void analytics_event_PPoGATT_disconnect(time_t timestamp, bool successful_reconnect) {
  AnalyticsEventBlob event_blob = {
    .event = AnalyticsEvent_PPoGATTDisconnect,
    .ppogatt_disconnect = {
      .successful_reconnect = successful_reconnect,
      .time_utc = timestamp,
    },
  };
  analytics_logging_log_event(&event_blob);
}


void analytics_event_get_bytes_stats(CommSession *session, uint8_t type,
                                     uint32_t bytes_transferred, uint32_t elapsed_time_ms,
                                     uint32_t conn_events, uint32_t sync_errors,
                                     uint32_t skip_errors, uint32_t other_errors) {
  bool is_ppogatt = false;
  uint16_t conn_interval = 0;
  if (!prv_get_connection_details(session, &is_ppogatt, &conn_interval)) {
    return;
  }

  AnalyticsEventBlob event_blob = {
    .event = AnalyticsEvent_GetBytesStats,
    .get_bytes_stats = {
      .ppogatt = is_ppogatt,
      .conn_intvl_1_25ms = MIN(conn_interval, UINT8_MAX),
      .type = type,
      .bytes_transferred = bytes_transferred,
      .elapsed_time_ms = elapsed_time_ms,
      .conn_events = conn_events,
      .sync_errors = MIN(sync_errors, UINT16_MAX),
      .skip_errors = MIN(skip_errors, UINT16_MAX),
      .other_errors = MIN(other_errors, UINT16_MAX),
    },
  };

  ANALYTICS_LOG_DEBUG("GetBytesStats event: type: 0x%x, num_bytes: %"PRIu32", elapsed_ms: %"PRIu32,
                      type, bytes_transferred, elapsed_time_ms);

  analytics_logging_log_event(&event_blob);
}
