/**
 * @file qdx_tcp_logic.c
 * @brief TCP Network Logic Implementation for MCU
 */

#include "qdx_tcp_logic.h"
#include "qdx_port.h"
#include <stddef.h>
#include <stdio.h>
#include <string.h>

/* ============================================================
 * Internal State & Configuration Cache
 * ============================================================ */

#define RECV_BUF_SIZE 2048
#define MAX_FRAGMENT_PAYLOAD 1400
#define HEARTBEAT_INTERVAL_MS 2000
#define SERVER_TIMEOUT_MS 6000
#define RECONNECT_DELAY_MS 3000

typedef struct {
  qdx_socket_t sock;
  uint32_t last_activity_ms;
  uint32_t last_heartbeat_ms;
  uint32_t last_reconnect_ms;
  uint16_t sequence;
  uint8_t is_connected;
  uint8_t is_running;
  const char *label;
  uint8_t recv_buffer[RECV_BUF_SIZE * 2];
  uint32_t recv_len;
} TcpStreamCtx_t;

static struct {
  uint8_t uuid[16];
  uint8_t auth_token[16];
  uint16_t dev_id;
  int32_t pending_new_dev_id;
  uint32_t frame_count;

  TcpStreamCtx_t control_stream;
  TcpStreamCtx_t data_stream;

  qdx_mutex_t config_mutex;
  uint8_t has_valid_config;
  ConfigCommon_t cached_common;
  Config2D_t cached_cfg2d;
  Config1D_t cached_cfg1d;

  ConfigUpdateCallback_t config_cb;
  DetectionResultCallback_t detect_cb;
  TempFrameRequestCallback_t temp_req_cb;
} g_TcpLogic;

/* Server endpoint prototype - user would configure these, but we map to demo
 * defaults */
static const char *SERVER_IP = "192.168.7.50";
static const uint16_t CONTROL_PORT = 5511;
static const uint16_t DATA_PORT = 5512;

/* ============================================================
 * Internal Helpers
 * ============================================================ */

static void tcp_stream_init(TcpStreamCtx_t *ctx, const char *label) {
  memset(ctx, 0, sizeof(TcpStreamCtx_t));
  ctx->label = label;
}

static void tcp_stream_disconnect(TcpStreamCtx_t *ctx) {
  DBG_NET("[%s] Disconnected\r\n", ctx->label);
  ctx->is_connected = 0;
  if (ctx->sock) {
    qdx_port_tcp_close(ctx->sock);
    ctx->sock = NULL;
  }
}

static int8_t tcp_stream_connect(TcpStreamCtx_t *ctx, const char *ip,
                                 uint16_t port) {
  DBG_NET("[%s] connecting %s:%d...\r\n", ctx->label, ip, port);
  ctx->sock = qdx_port_tcp_connect(ip, port);
  if (ctx->sock == NULL) {
    DBG_ERR("[%s] connect FAILED\r\n", ctx->label);
    return -1;
  }

  ctx->is_connected = 1;
  ctx->last_activity_ms = qdx_port_get_tick_ms();
  ctx->last_heartbeat_ms = ctx->last_activity_ms;
  ctx->recv_len = 0;
  DBG_NET("[%s] Connected to %s:%d\r\n", ctx->label, ip, port);
  return 0;
}

static int32_t tcp_send_frame(TcpStreamCtx_t *ctx, uint8_t msg_class,
                              uint8_t tlv_type, const uint8_t *payload,
                              uint16_t payload_len, uint8_t flags) {
  if (!ctx->is_connected || ctx->sock == NULL)
    return -1;

  /* Max frame buffer for generic small control/heartbeat messages.
     Note: Images use BuildAndSendTemperatureFrame instead of this. */
  uint8_t buffer[1024];
  if (HEADER_SIZE + TLV_HEADER_SIZE + payload_len + CRC_SIZE > sizeof(buffer)) {
    return -1; /* Too large for generic send */
  }

  uint16_t seq = ctx->sequence++;
  int frame_len =
      qdx_build_frame(buffer, msg_class, tlv_type, payload, payload_len,
                      g_TcpLogic.dev_id, seq, qdx_port_get_tick_ms(), flags);

  int32_t sent = qdx_port_tcp_send(ctx->sock, buffer, frame_len);
  if (sent < 0) {
    tcp_stream_disconnect(ctx);
    return -1;
  }
  return sent;
}

static void tcp_send_handshake(TcpStreamCtx_t *ctx) {
  DBG_NET("[%s] Handshake (DevID=%d)\r\n", ctx->label, (int)g_TcpLogic.dev_id);
  uint8_t payload[54];
  memset(payload, 0, sizeof(payload));
  qdx_write_u16_le(payload + 0, 0x0200);
  memcpy(payload + 2, g_TcpLogic.uuid, 16);
  memcpy(payload + 18, g_TcpLogic.auth_token, 16);
  /* Safe string copy without relying on strncpy platform behavior */
  const char *hw = "V1.0";
  const char *fw = "V2.0";
  for (int i = 0; i < 8 && hw[i]; i++)
    payload[34 + i] = hw[i];
  for (int i = 0; i < 8 && fw[i]; i++)
    payload[42 + i] = fw[i];
  qdx_write_u32_le(payload + 50, 0x07);

  tcp_send_frame(ctx, CLASS_SYSTEM, TYPE_HANDSHAKE, payload, sizeof(payload),
                 FLAG_ACK_REQ);
}

static void tcp_send_heartbeat(TcpStreamCtx_t *ctx) {
  uint8_t payload[6];
  qdx_write_u32_le(payload + 0, qdx_port_get_tick_ms());
  payload[4] = 10; /* Placeholder CpuLoad */
  payload[5] = 20; /* Placeholder MemUsage */

  tcp_send_frame(ctx, CLASS_SYSTEM, TYPE_HEARTBEAT, payload, sizeof(payload),
                 0);
}

static void tcp_send_ack(TcpStreamCtx_t *ctx, uint16_t ack_seq, uint8_t status,
                         uint16_t error_code) {
  uint8_t payload[5];
  qdx_write_u16_le(payload + 0, ack_seq);
  payload[2] = status;
  qdx_write_u16_le(payload + 3, error_code);

  tcp_send_frame(ctx, CLASS_RESPONSE, TYPE_ACK_PAYLOAD, payload,
                 sizeof(payload), 0);
}

/* ============================================================
 * Receiving and Parsing
 * ============================================================ */

static void qdx_deserialize_config_common(ConfigCommon_t *cfg,
                                          const uint8_t *val) {
  /* PipelineId: 16 字节字符数组，逐字节拷贝 */
  for (int i = 0; i < 16; i++)
    cfg->PipelineId[i] = (char)val[i];
  cfg->PipelineType = val[16];
  cfg->WorkMode = val[17];
  cfg->ConfigTag = val[18];
  cfg->StrictnessLevel = val[19];
  cfg->IsCustomMode = val[20];
  cfg->Reserved[0] = val[21];
  cfg->Reserved[1] = val[22];
}

static void qdx_deserialize_config2d(Config2D_t *cfg, const uint8_t *val) {
  cfg->Enabled = val[0];
  cfg->IsLive = val[1];
  cfg->DeviceId = qdx_read_u16_le(val + 2);
  cfg->Width = qdx_read_u16_le(val + 4);
  cfg->Height = qdx_read_u16_le(val + 6);
  cfg->Fps = val[8];
  cfg->Exposure = qdx_read_u32_le(val + 9);
  cfg->AutoExposure = val[13];
  cfg->MaskEnabled = val[14];
  cfg->MaskThreshold = (int16_t)qdx_read_u16_le(val + 15);
  cfg->MaskWidth = qdx_read_u16_le(val + 17);
  cfg->MaskHeight = qdx_read_u16_le(val + 19);
  cfg->Angle = (int16_t)qdx_read_u16_le(val + 21);
  cfg->TargetWidth = qdx_read_u16_le(val + 23);
  cfg->TargetHeight = qdx_read_u16_le(val + 25);
  cfg->TriggerMode = val[27];
  cfg->TriggerGpioLine = val[28];
  cfg->TriggerDelayMs = qdx_read_u16_le(val + 29);
  cfg->TriggerBurstCount = val[31];
  cfg->TriggerInternalIntervalMs = qdx_read_u16_le(val + 32);
  cfg->TriggerTemperatureThreshold = (int16_t)qdx_read_u16_le(val + 34);
  cfg->TriggerDebounceIntervalMs = qdx_read_u16_le(val + 36);
  cfg->TriggerCondition = val[38];
  cfg->TriggerRoiX = qdx_read_u16_le(val + 39);
  cfg->TriggerRoiY = qdx_read_u16_le(val + 41);
  cfg->TriggerRoiW = qdx_read_u16_le(val + 43);
  cfg->TriggerRoiH = qdx_read_u16_le(val + 45);
  cfg->NGioDelay = qdx_read_u16_le(val + 47);
  cfg->OutputGpioLine = val[49];
  cfg->AlarmGpioLine = val[50];
  cfg->AlarmHoldMs = qdx_read_u16_le(val + 51);
  cfg->StoreNgImagesOnly = val[53];
  cfg->TrainingEnabled = val[54];
  cfg->TrainingSampleThreshold = qdx_read_u16_le(val + 55);
  cfg->ProcessingTimeoutMs = qdx_read_u16_le(val + 57);
  cfg->MaxProcessingQueueSize = val[59];
  cfg->Reserved = val[60];
}

static void qdx_deserialize_config1d(Config1D_t *cfg, const uint8_t *val) {
  cfg->Enabled = val[0];
  cfg->RunMode = val[1];
  cfg->TriggerType = val[2];
  cfg->BufferSize = qdx_read_u16_le(val + 3);
  cfg->TriggerTempLimit = (int16_t)qdx_read_u16_le(val + 5);
  cfg->StartPointsToRemove = qdx_read_u16_le(val + 7);
  cfg->ReferenceLength = qdx_read_u16_le(val + 9);
  cfg->HighTimerLimit = qdx_read_u16_le(val + 11);
  cfg->TimerCLimit = qdx_read_u16_le(val + 13);
  cfg->NgCountLimit = val[15];
  cfg->LSizeStart = qdx_read_u16_le(val + 16);
  cfg->RSizeStart = qdx_read_u16_le(val + 18);
  cfg->NGioDelay = qdx_read_u16_le(val + 20);
  cfg->OutputGpioLine = val[22];
  cfg->AlarmGpioLine = val[23];
  cfg->AlarmHoldMs = qdx_read_u16_le(val + 24);
}

static void parse_and_dispatch_tlv(TcpStreamCtx_t *ctx, const uint8_t *packet,
                                   uint16_t pkt_len) {
  uint16_t hdr_seq = qdx_read_u16_le(packet + 5);
  uint8_t hdr_flags = packet[15];

  /* TLV Data starts after header (16 bytes) */
  int offset = HEADER_SIZE;
  int payload_len = pkt_len - HEADER_SIZE - CRC_SIZE;
  int parsed_len = 0;

  uint8_t cfg_updated = 0;

  DBG_HB("[%s] TLV pkt: Seq=%d Len=%d\r\n", ctx->label,
         hdr_seq, payload_len);

  while (parsed_len <= payload_len - 3) {
    uint8_t type = packet[offset];
    uint16_t len = qdx_read_u16_le(packet + offset + 1);

    DBG_HB("[%s]  TLV: Type=0x%02X Len=%d\r\n", ctx->label, type, len);

    if (parsed_len + 3 + len > payload_len) {
      DBG_ERR("[%s] TLV truncated (need %d, have %d)\r\n",
               ctx->label, len, payload_len - parsed_len - 3);
      break; /* Malformed */
    }

    const uint8_t *value = packet + offset + 3;

    switch (type) {
    case TYPE_HEARTBEAT: {
      /* Server heartbeat response — update activity timestamp */
      ctx->last_activity_ms = qdx_port_get_tick_ms();
      break;
    }
    case TYPE_ACK_PAYLOAD: {
      if (len >= 3) {
        uint16_t ack_seq = qdx_read_u16_le(value);
        uint8_t status = value[2];
        uint16_t err_code = (len >= 5) ? qdx_read_u16_le(value + 3) : 0;
        DBG_CFG("<< ACK: seq=%d status=%d err=0x%04x\r\n",
                  (int)ack_seq, (int)status, (int)err_code);
      }
      break;
    }
    case TYPE_DEVID_ASSIGN: {
      if (len >= sizeof(DevIDAssignment_t)) {
        uint16_t new_id = qdx_read_u16_le(value);
        DBG_CFG("<< DevID assigned: %d -> %d\r\n",
                  (int)g_TcpLogic.dev_id, (int)new_id);
        g_TcpLogic.pending_new_dev_id = new_id;
        tcp_send_ack(ctx, hdr_seq, 0, 0);
      }
      break;
    }
    case TYPE_CONFIG_COMMON: {
      if (len >= sizeof(ConfigCommon_t)) {
        qdx_port_mutex_lock(g_TcpLogic.config_mutex);
        qdx_deserialize_config_common(&g_TcpLogic.cached_common, value);
        g_TcpLogic.has_valid_config = 1;
        cfg_updated = 1;
        qdx_port_mutex_unlock(g_TcpLogic.config_mutex);
        DBG_CFG("<< ConfigCommon: Mode=%d Tag=%d Strict=%d\r\n",
                  (int)g_TcpLogic.cached_common.WorkMode,
                  (int)g_TcpLogic.cached_common.ConfigTag,
                  (int)g_TcpLogic.cached_common.StrictnessLevel);
      }
      break;
    }
    case TYPE_CONFIG_2D: {
      if (len >= sizeof(Config2D_t)) {
        qdx_port_mutex_lock(g_TcpLogic.config_mutex);
        qdx_deserialize_config2d(&g_TcpLogic.cached_cfg2d, value);
        g_TcpLogic.has_valid_config = 1;
        cfg_updated = 1;
        qdx_port_mutex_unlock(g_TcpLogic.config_mutex);
        DBG_CFG("<< Config2D: En=%d %dx%d Tgt=%dx%d Fps=%d "
                  "Trig=%d Burst=%d Intv=%d Thresh=%d "
                  "ROI(%d,%d,%d,%d) NGio=%d\r\n",
                  (int)g_TcpLogic.cached_cfg2d.Enabled,
                  (int)g_TcpLogic.cached_cfg2d.Width,
                  (int)g_TcpLogic.cached_cfg2d.Height,
                  (int)g_TcpLogic.cached_cfg2d.TargetWidth,
                  (int)g_TcpLogic.cached_cfg2d.TargetHeight,
                  (int)g_TcpLogic.cached_cfg2d.Fps,
                  (int)g_TcpLogic.cached_cfg2d.TriggerMode,
                  (int)g_TcpLogic.cached_cfg2d.TriggerBurstCount,
                  (int)g_TcpLogic.cached_cfg2d.TriggerInternalIntervalMs,
                  (int)g_TcpLogic.cached_cfg2d.TriggerTemperatureThreshold,
                  (int)g_TcpLogic.cached_cfg2d.TriggerRoiX,
                  (int)g_TcpLogic.cached_cfg2d.TriggerRoiY,
                  (int)g_TcpLogic.cached_cfg2d.TriggerRoiW,
                  (int)g_TcpLogic.cached_cfg2d.TriggerRoiH,
                  (int)g_TcpLogic.cached_cfg2d.NGioDelay);
      } else {
        DBG_ERR("<< Config2D bad len=%d (need %d)\r\n",
                  len, (int)sizeof(Config2D_t));
      }
      break;
    }
    case TYPE_CONFIG_1D: {
      if (len >= sizeof(Config1D_t)) {
        qdx_port_mutex_lock(g_TcpLogic.config_mutex);
        qdx_deserialize_config1d(&g_TcpLogic.cached_cfg1d, value);
        g_TcpLogic.has_valid_config = 1;
        cfg_updated = 1;
        qdx_port_mutex_unlock(g_TcpLogic.config_mutex);
        DBG_CFG("<< Config1D: En=%d RunMode=%d TrigType=%d "
                  "BufSz=%d TempLim=%d L=%d R=%d NGio=%d\r\n",
                  (int)g_TcpLogic.cached_cfg1d.Enabled,
                  (int)g_TcpLogic.cached_cfg1d.RunMode,
                  (int)g_TcpLogic.cached_cfg1d.TriggerType,
                  (int)g_TcpLogic.cached_cfg1d.BufferSize,
                  (int)g_TcpLogic.cached_cfg1d.TriggerTempLimit,
                  (int)g_TcpLogic.cached_cfg1d.LSizeStart,
                  (int)g_TcpLogic.cached_cfg1d.RSizeStart,
                  (int)g_TcpLogic.cached_cfg1d.NGioDelay);
      }
      break;
    }
    case TYPE_TEMP_FRAME: {
      DBG_CFG("<< TempFrame request (len=%d)\r\n", (int)len);
      if (g_TcpLogic.temp_req_cb) {
        uint8_t is2d = 0;
        if (len >= 18) {
          is2d = value[18];
        }
        DBG_CFG("   -> callback: is2D=%d\r\n", (int)is2d);
        g_TcpLogic.temp_req_cb(is2d);
      }
      break;
    }
    case TYPE_DETECTION_RESULT: {
      if (len >= sizeof(DetectionResult_t)) {
        uint32_t frame_num = qdx_read_u32_le(value);
        uint8_t result_status = value[4];
        DBG_CFG("<< DetectionResult: frm=%d result=%d\r\n",
                  (int)frame_num, (int)result_status);
        if (g_TcpLogic.detect_cb)
          g_TcpLogic.detect_cb(frame_num, result_status);
      }
      break;
    }
    case TYPE_HANDSHAKE:
      /* Server echoes handshake info — safe to ignore */
      break;
    default:
      DBG_ERR("Unknown TLV type=0x%02x len=%d\r\n", (int)type, (int)len);
      break;
    }

    offset += (3 + len);
    parsed_len += (3 + len);
  }

  if (cfg_updated && g_TcpLogic.config_cb && g_TcpLogic.has_valid_config) {
    DBG_CFG("Config updated -> notify app\r\n");
    qdx_port_mutex_lock(g_TcpLogic.config_mutex);
    g_TcpLogic.config_cb(&g_TcpLogic.cached_common, &g_TcpLogic.cached_cfg2d,
                         &g_TcpLogic.cached_cfg1d);
    qdx_port_mutex_unlock(g_TcpLogic.config_mutex);
  }

  if (hdr_flags & FLAG_ACK_REQ) {
    tcp_send_ack(ctx, hdr_seq, 0, 0);
  }
}

static void tcp_process_rx_buffer(TcpStreamCtx_t *ctx) {
  while (ctx->recv_len >= HEADER_SIZE) {
    /* 1. Search for Magic 0x55AA */
    int start_idx = -1;
    for (uint32_t i = 0; i <= ctx->recv_len - 2; i++) {
      if (ctx->recv_buffer[i] == 0xAA && ctx->recv_buffer[i + 1] == 0x55) {
        start_idx = i;
        break;
      }
    }

    if (start_idx == -1) {
      ctx->recv_buffer[0] = ctx->recv_buffer[ctx->recv_len - 1];
      ctx->recv_len = 1;
      break;
    }

    if (start_idx > 0) {
      /* 缓冲区内部左移，源与目标重叠，必须使用 memmove */
      memmove(ctx->recv_buffer, ctx->recv_buffer + start_idx,
              ctx->recv_len - start_idx);
      ctx->recv_len -= start_idx;
      if (ctx->recv_len < HEADER_SIZE)
        break;
    }

    uint8_t version = ctx->recv_buffer[2];
    uint16_t length = qdx_read_u16_le(ctx->recv_buffer + 3);

    if (version != PROTO_VERSION || length < HEADER_SIZE + CRC_SIZE) {
      DBG_ERR("[%s] bad header: ver=0x%02X(exp 0x%02X) len=%d\r\n",
               ctx->label, version, PROTO_VERSION, length);
      memmove(ctx->recv_buffer, ctx->recv_buffer + 2, ctx->recv_len - 2);
      ctx->recv_len -= 2;
      continue;
    }

    if (length > sizeof(ctx->recv_buffer)) {
      DBG_ERR("[%s] frame too large: %d > %d\r\n", ctx->label,
               length, (int)sizeof(ctx->recv_buffer));
      ctx->recv_len = 0;
      break;
    }

    if (ctx->recv_len < length) {
      break; /* Need more data */
    }

    /* 2. Validate CRC */
    uint16_t received_crc = qdx_read_u16_le(ctx->recv_buffer + length - 2);
    uint16_t calculated_crc = qdx_crc16_modbus(ctx->recv_buffer, length - 2);

    if (received_crc == calculated_crc) {
      /* 3. Dispatch */
      parse_and_dispatch_tlv(ctx, ctx->recv_buffer, length);
    } else {
      DBG_ERR("[%s] CRC fail: calc=0x%04X recv=0x%04X len=%d\r\n",
               ctx->label, calculated_crc, received_crc, length);
    }

    /* 4. 移除已处理帧（缓冲区内部左移，必须 memmove） */
    memmove(ctx->recv_buffer, ctx->recv_buffer + length,
            ctx->recv_len - length);
    ctx->recv_len -= length;
  }
}

static void recv_thread_entry(void *arg) {
  TcpStreamCtx_t *ctx = (TcpStreamCtx_t *)arg;

  while (ctx->is_running) {
    if (!ctx->is_connected) {
      qdx_port_delay_ms(100);
      continue;
    }

    /* Leave space for maximum TCP MTU read */
    if (sizeof(ctx->recv_buffer) - ctx->recv_len > 0) {
      int32_t bytes =
          qdx_port_tcp_recv(ctx->sock, ctx->recv_buffer + ctx->recv_len,
                            sizeof(ctx->recv_buffer) - ctx->recv_len);

      if (bytes > 0) {
        ctx->recv_len += bytes;
        ctx->last_activity_ms = qdx_port_get_tick_ms();
        tcp_process_rx_buffer(ctx);
      } else if (bytes < 0) {
        /* Disconnected / Error */
        tcp_stream_disconnect(ctx);
      }
    } else {
      /* Buffer completely full but no valid packet found. Prevent overflow
       * lock. */
      ctx->recv_len = 0;
    }

    qdx_port_delay_ms(10);
  }
}

/* ============================================================
 * Main Background Manager
 * ============================================================ */

static void manager_thread_entry(void *arg) {
  while (1) {
    uint32_t now = qdx_port_get_tick_ms();

    /* DevID Reassignment Handling */
    if (g_TcpLogic.pending_new_dev_id >= 0) {
      g_TcpLogic.dev_id = (uint16_t)g_TcpLogic.pending_new_dev_id;
      g_TcpLogic.pending_new_dev_id = -1;

      tcp_stream_disconnect(&g_TcpLogic.control_stream);
      tcp_stream_disconnect(&g_TcpLogic.data_stream);

      g_TcpLogic.control_stream.last_reconnect_ms = 0;
      g_TcpLogic.data_stream.last_reconnect_ms = 0;
      qdx_port_delay_ms(500);
      continue;
    }

    /* Connection Management: Control Stream */
    if (!g_TcpLogic.control_stream.is_connected) {
      if (now - g_TcpLogic.control_stream.last_reconnect_ms >
          RECONNECT_DELAY_MS) {
        if (tcp_stream_connect(&g_TcpLogic.control_stream, SERVER_IP,
                               CONTROL_PORT) == 0) {
          tcp_send_handshake(&g_TcpLogic.control_stream);
        }
        g_TcpLogic.control_stream.last_reconnect_ms = qdx_port_get_tick_ms();
      }
    }

    /* Connection Management: Data Stream */
    if (!g_TcpLogic.data_stream.is_connected) {
      if (now - g_TcpLogic.data_stream.last_reconnect_ms > RECONNECT_DELAY_MS) {
        if (tcp_stream_connect(&g_TcpLogic.data_stream, SERVER_IP, DATA_PORT) ==
            0) {
          tcp_send_handshake(&g_TcpLogic.data_stream);
        }
        g_TcpLogic.data_stream.last_reconnect_ms = qdx_port_get_tick_ms();
      }
    }

    /* Heartbeat & Timeout checks */
    if (g_TcpLogic.control_stream.is_connected) {
      if (now - g_TcpLogic.control_stream.last_heartbeat_ms >
          HEARTBEAT_INTERVAL_MS) {
        tcp_send_heartbeat(&g_TcpLogic.control_stream);
        g_TcpLogic.control_stream.last_heartbeat_ms = now;
      }
    }

    if (g_TcpLogic.data_stream.is_connected) {
      if (now - g_TcpLogic.data_stream.last_heartbeat_ms >
          HEARTBEAT_INTERVAL_MS) {
        tcp_send_heartbeat(&g_TcpLogic.data_stream);
        g_TcpLogic.data_stream.last_heartbeat_ms = now;
      }
    }

    qdx_port_delay_ms(100);
  }
}

/* ============================================================
 * Public API Implementations
 * ============================================================ */

int8_t TcpLogic_Init(const uint8_t *deviceUUID, const uint8_t *authToken) {
  memset(&g_TcpLogic, 0, sizeof(g_TcpLogic));

  if (deviceUUID)
    memcpy(g_TcpLogic.uuid, deviceUUID, 16);
  if (authToken)
    memcpy(g_TcpLogic.auth_token, authToken, 16);

  /* Default DevID = 101 */
  g_TcpLogic.dev_id = 101;
  g_TcpLogic.pending_new_dev_id = -1;

  g_TcpLogic.config_mutex = qdx_port_mutex_create();
  if (g_TcpLogic.config_mutex == NULL)
    return -1;

  tcp_stream_init(&g_TcpLogic.control_stream, "Control");
  tcp_stream_init(&g_TcpLogic.data_stream, "Data");

  return 0;
}

void TcpLogic_Start(void) {
  g_TcpLogic.control_stream.is_running = 1;
  g_TcpLogic.data_stream.is_running = 1;

  qdx_port_thread_create("tcp_mgr", manager_thread_entry, NULL, 2048, 3);
  qdx_port_thread_create("tcp_rx_c", recv_thread_entry,
                         &g_TcpLogic.control_stream, 2048, 4);
  qdx_port_thread_create("tcp_rx_d", recv_thread_entry, &g_TcpLogic.data_stream,
                         2048, 4);
}

int8_t TcpLogic_GetLatestConfig(ConfigCommon_t *out_common,
                                Config2D_t *out_cfg2d, Config1D_t *out_cfg1d) {
  if (!out_common || !out_cfg2d || !out_cfg1d)
    return -2;

  qdx_port_mutex_lock(g_TcpLogic.config_mutex);
  if (!g_TcpLogic.has_valid_config) {
    qdx_port_mutex_unlock(g_TcpLogic.config_mutex);
    return -1;
  }

  memcpy(out_common, &g_TcpLogic.cached_common, sizeof(ConfigCommon_t));
  memcpy(out_cfg2d, &g_TcpLogic.cached_cfg2d, sizeof(Config2D_t));
  memcpy(out_cfg1d, &g_TcpLogic.cached_cfg1d, sizeof(Config1D_t));
  qdx_port_mutex_unlock(g_TcpLogic.config_mutex);

  return 0;
}

void TcpLogic_RegisterConfigCallback(ConfigUpdateCallback_t cb) {
  g_TcpLogic.config_cb = cb;
}

void TcpLogic_RegisterDetectionCallback(DetectionResultCallback_t cb) {
  g_TcpLogic.detect_cb = cb;
}

void TcpLogic_RegisterTempFrameRequestCallback(TempFrameRequestCallback_t cb) {
  g_TcpLogic.temp_req_cb = cb;
}

void TcpLogic_InjectTestConfig(const ConfigCommon_t *common,
                               const Config2D_t *cfg2d,
                               const Config1D_t *cfg1d) {
  qdx_port_mutex_lock(g_TcpLogic.config_mutex);
  if (common) memcpy(&g_TcpLogic.cached_common, common, sizeof(*common));
  if (cfg2d)  memcpy(&g_TcpLogic.cached_cfg2d,  cfg2d,  sizeof(*cfg2d));
  if (cfg1d)  memcpy(&g_TcpLogic.cached_cfg1d,  cfg1d,  sizeof(*cfg1d));
  g_TcpLogic.has_valid_config = 1;
  qdx_port_mutex_unlock(g_TcpLogic.config_mutex);
  if (g_TcpLogic.config_cb) {
    g_TcpLogic.config_cb(&g_TcpLogic.cached_common,
                          &g_TcpLogic.cached_cfg2d,
                          &g_TcpLogic.cached_cfg1d);
  }
}

/* ============================================================
 * Zero-Copy Frame Building & Fragmentation for Temperature Data
 * ============================================================ */

int8_t
TcpLogic_BuildAndSendTemperatureFrame(TcpTxBuffer_t *io_buffer,
                                      const PreprocessResult_t *processMeta,
                                      uint8_t frameType, uint8_t is2D) {

  if (!g_TcpLogic.data_stream.is_connected || !io_buffer || !processMeta)
    return -1;
  if (io_buffer->ValidPayloadLen == 0)
    return -2;

  g_TcpLogic.frame_count++;

  DBG_DATA(">> TempFrame #%d: %dx%d type=%d is2D=%d payload=%d\r\n",
            (int)processMeta->FrameNumber,
            (int)processMeta->ValidWidth, (int)processMeta->ValidHeight,
            (int)frameType, (int)is2D, (int)io_buffer->ValidPayloadLen);

  /* We need to prepend: TLV Header (3) + TemperatureFrameHeader_t (18) */
  uint32_t tlv_wrapper_len = TLV_HEADER_SIZE + sizeof(TemperatureFrameHeader_t);

  /* Ensure application left enough head room */
  if (io_buffer->HeadOffset < HEADER_SIZE + tlv_wrapper_len) {
    return -3; /* Not enough offset space allocated by user memory pool */
  }

  /* Start writing right before the application payload */
  uint8_t *tlv_start =
      io_buffer->pBuffer + io_buffer->HeadOffset - tlv_wrapper_len;

  /* 1. Fill TLV Header manually via shift */
  uint32_t tlv_value_len =
      sizeof(TemperatureFrameHeader_t) + io_buffer->ValidPayloadLen;
  tlv_start[0] = TYPE_TEMP_FRAME;
  qdx_write_u16_le(tlv_start + 1, (uint16_t)tlv_value_len);

  /* 2. Fill TemperatureFrameHeader manually via shift to avoid alignment
   * faults
   */
  uint8_t *temp_hdr = tlv_start + TLV_HEADER_SIZE;
  qdx_write_u32_le(temp_hdr + 0, processMeta->FrameNumber);
  qdx_write_u16_le(temp_hdr + 4, processMeta->ValidWidth);
  qdx_write_u16_le(temp_hdr + 6, processMeta->ValidHeight);
  qdx_write_u16_le(temp_hdr + 8, (uint16_t)processMeta->MinTemp);
  qdx_write_u16_le(temp_hdr + 10, (uint16_t)processMeta->MaxTemp);
  qdx_write_u16_le(temp_hdr + 12, (uint16_t)processMeta->AvgTemp);
  qdx_write_u16_le(temp_hdr + 14, (uint16_t)processMeta->RoiTemp);
  temp_hdr[16] = frameType;
  temp_hdr[17] = processMeta->Status;
  temp_hdr[18] = is2D;
  temp_hdr[19] = 0; /* Reserved */

  /* Total payload length is the entire TLV block */
  uint32_t total_tlv_len = TLV_HEADER_SIZE + tlv_value_len;

  /* 3. Handle Fragmentation if necessary */
  if (total_tlv_len <= MAX_FRAGMENT_PAYLOAD) {
    /* No fragmentation needed, build frame in place at the front */
    uint8_t *frame_start = tlv_start - HEADER_SIZE;
    uint16_t seq = g_TcpLogic.data_stream.sequence++;

    int final_len = qdx_build_frame_inplace(
        frame_start, CLASS_DATA, (uint16_t)total_tlv_len, g_TcpLogic.dev_id,
        seq, qdx_port_get_tick_ms(), 0);

    int32_t sent =
        qdx_port_tcp_send(g_TcpLogic.data_stream.sock, frame_start, final_len);
    return (sent >= 0) ? 0 : -1;
  }

  /* Fragmentation required.
     Note: For zero-copy fragmentation, we send piece by piece.
     We need an external buffer for each piece's frame header + CRC.
     We can't easily prepend headers to later fragments inline. */

  uint32_t offset = 0;
  static uint8_t frag_buf[HEADER_SIZE + MAX_FRAGMENT_PAYLOAD + CRC_SIZE];
  uint32_t frag_count =
      (total_tlv_len + MAX_FRAGMENT_PAYLOAD - 1) / MAX_FRAGMENT_PAYLOAD;

  DBG_DATA(">> Fragmented: %d frags, total=%d\r\n",
            (int)frag_count, (int)total_tlv_len);

  for (uint32_t i = 0; i < frag_count; i++) {
    uint32_t chunk_len = total_tlv_len - offset;
    if (chunk_len > MAX_FRAGMENT_PAYLOAD)
      chunk_len = MAX_FRAGMENT_PAYLOAD;

    uint8_t flags = (i == frag_count - 1) ? FLAG_LAST_FRAGMENT : 0;
    uint16_t seq = g_TcpLogic.data_stream.sequence++;

    /* We copy the chunk into frag_buf to append Header/CRC.
       This involves ONE copy of the chunk, but it's small (1400 bytes at a
       time), and ensures we don't need additional memory pools. */
    int frame_len = qdx_build_fragment_frame(
        frag_buf, CLASS_DATA, tlv_start + offset, (uint16_t)chunk_len,
        g_TcpLogic.dev_id, seq, qdx_port_get_tick_ms(), flags);

    int32_t sent =
        qdx_port_tcp_send(g_TcpLogic.data_stream.sock, frag_buf, frame_len);
    if (sent < 0) {
      tcp_stream_disconnect(&g_TcpLogic.data_stream);
      return -1;
    }

    offset += chunk_len;
  }

  return 0;
}
