/**
 * @file http_request.c
 * @brief 基于 FreeRTOS 队列的 HTTP/HTTPS 请求组件
 * @version 0.2
 * @date 2026-09-22
 */

#include "http_request.h"

#include <stdlib.h>
#include <string.h>

#include "esp_log.h"

#include "esp_crt_bundle.h"

#define TAG "http_request"

#define HTTP_REQUEST_TASK_NAME     "http_request_task"
#define HTTP_REQUEST_TASK_STACK    6144  //含 TLS 时 4096 偏小
#define HTTP_REQUEST_TASK_PRIORITY 5

static QueueHandle_t s_queue = NULL;
static TaskHandle_t s_task = NULL;
static esp_http_client_handle_t s_client = NULL;

/**
 * @brief 字符串拷贝到堆,入参为 NULL 时返回 NULL
 */
static char *http_request_strdup(const char *src)
{
  if (src == NULL) {
    return NULL;
  }
  size_t len = strlen(src) + 1;
  char *dst = malloc(len);
  if (dst != NULL) {
    memcpy(dst, src, len);
  }
  return dst;
}

/**
 * @brief 释放队列元素内申请的内存
 */
static void http_request_item_clear(http_request_item_t *item)
{
  free(item->url);
  free(item->post_data);
  free(item->content_type);
  item->url = NULL;
  item->post_data = NULL;
  item->content_type = NULL;
}

/**
 * @brief 循环接收响应体并分片回调
 *
 * @note 不依赖 Content-Length,服务器返回 chunked 编码(此时长度为 -1)也能正常接收
 */
static http_request_result_t http_request_read_body(esp_http_client_handle_t client,
                                                    const http_request_item_t *item)
{
  //多申请 1 字节用于补字符串结束符
  char *buffer = (char *)malloc(item->chunk_size + 1);
  if (buffer == NULL) {
    ESP_LOGW(TAG, "申请接收内存失败, 长度=%d", item->chunk_size);
    return HTTP_REQUEST_FAIL_NO_MEM;
  }

  int64_t total_len = esp_http_client_get_content_length(client);  //未知时为 -1
  int64_t read_total = 0;
  http_request_result_t result = HTTP_REQUEST_OK;

  while (1) {
    int read_len = esp_http_client_read(client, buffer, item->chunk_size);
    if (read_len < 0) {
      ESP_LOGW(TAG, "接收响应数据出错, ret=%d", read_len);
      result = HTTP_REQUEST_FAIL_RECV;
      break;
    }
    if (read_len == 0) {
      //返回 0 表示数据流结束,需再确认是否收完(对端提前断开时要判为失败)
      if (!esp_http_client_is_complete_data_received(client)) {
        ESP_LOGW(TAG, "响应数据不完整, 已接收 %lld 字节", (long long)read_total);
        result = HTTP_REQUEST_FAIL_RECV;
      }
      break;
    }
    buffer[read_len] = '\0';
    read_total += read_len;
    if (item->on_data != NULL) {
      item->on_data(buffer, read_len, total_len);
    }
  }

  free(buffer);
  ESP_LOGI(TAG, "响应接收完成, 共 %lld 字节", (long long)read_total);
  return result;
}

/**
 * @brief 处理一条队列消息,完成一次完整的 HTTP 请求
 */
static void http_request_handle(esp_http_client_handle_t client, http_request_item_t *item)
{
  http_request_result_t result = HTTP_REQUEST_OK;
  int status_code = 0;
  const char *content_type = item->content_type;
  int body_len = (item->post_data != NULL) ? (int)strlen(item->post_data) : 0;

  //1. 设置本次请求参数,句柄是常驻复用的,每次都必须重新设置
  esp_http_client_set_method(client, item->method);
  esp_http_client_set_timeout_ms(client, item->timeout_ms);
  if (esp_http_client_set_url(client, item->url) != ESP_OK) {
    ESP_LOGW(TAG, "请求链接非法: %s", item->url);
    result = HTTP_REQUEST_FAIL_PARAM;
    goto done;
  }

  //2. Content-Type 只在显式指定或带请求体时才发送
  if (content_type == NULL && body_len > 0) {
    content_type = HTTP_REQUEST_CONTENT_TYPE_DEFAULT;
  }
  if (content_type != NULL) {
    esp_http_client_set_header(client, "Content-Type", content_type);
  }

  //3. 建立连接,body_len 会作为 Content-Length 发送给服务器
  if (esp_http_client_open(client, body_len) != ESP_OK) {
    ESP_LOGW(TAG, "建立 http 连接失败: %s", item->url);
    result = HTTP_REQUEST_FAIL_OPEN;
    goto done;
  }

  //4. 发送请求体(esp_http_client_write 内部会写满,返回实际写入长度)
  if (body_len > 0) {
    int written = esp_http_client_write(client, item->post_data, body_len);
    if (written != body_len) {
      ESP_LOGW(TAG, "发送请求数据失败, 已发送 %d/%d 字节", written, body_len);
      result = HTTP_REQUEST_FAIL_WRITE;
      goto close;
    }
  }

  //5. 读取响应头
  if (esp_http_client_fetch_headers(client) < 0) {
    ESP_LOGW(TAG, "获取响应头失败: %s", item->url);
    result = HTTP_REQUEST_FAIL_HEADER;
    goto close;
  }
  status_code = esp_http_client_get_status_code(client);
  ESP_LOGI(TAG, "响应码 ResponseCode= %d", status_code);

  //6. 读取响应体
  result = http_request_read_body(client, item);

close:
  //每轮请求都主动断开,避免复用到已被服务器关闭的旧连接;因此不做 HTTP 长连接复用
  esp_http_client_close(client);
done:
  if (item->on_done != NULL) {
    item->on_done(result, status_code);
  }
  http_request_item_clear(item);
}

/**
 * @brief HTTP 请求任务,串行处理队列中的请求
 */
static void http_request_task(void *arg)
{
  QueueHandle_t queue = (QueueHandle_t)arg;

  //句柄常驻复用;url 不预置,每次请求前用 esp_http_client_set_url() 设置
  const esp_http_client_config_t config = {
      .timeout_ms = HTTP_REQUEST_TIMEOUT_DEFAULT,
      .buffer_size = HTTP_REQUEST_CHUNK_DEFAULT,
      .crt_bundle_attach = esp_crt_bundle_attach,  //https 使用内置根证书校验
  };

  s_client = esp_http_client_init(&config);
  if (s_client == NULL) {
    ESP_LOGE(TAG, "http 客户端初始化失败, 任务退出");
    s_task = NULL;
    vTaskDelete(NULL);
    return;
  }

  s_queue = queue;
  ESP_LOGI(TAG, "http 客户端初始化成功, 等待请求");

  http_request_item_t item;
  while (1) {
    if (xQueueReceive(queue, &item, portMAX_DELAY) == pdTRUE) {
      http_request_handle(s_client, &item);
    }
  }
}

/**
 * @brief 初始化 HTTP 客户端
 *
 * @param QueueHandle 传入 http 请求结构 队列
 */
void http_request_init(QueueHandle_t QueueHandle)
{
  if (QueueHandle == NULL) {
    ESP_LOGE(TAG, "请求队列为空");
    return;
  }
  if (s_task != NULL) {
    ESP_LOGW(TAG, "组件已初始化, 忽略本次调用");
    return;
  }
  if (xTaskCreate(http_request_task, HTTP_REQUEST_TASK_NAME, HTTP_REQUEST_TASK_STACK,
                  (void *)QueueHandle, HTTP_REQUEST_TASK_PRIORITY, &s_task) != pdPASS) {
    s_task = NULL;
    ESP_LOGE(TAG, "创建 http 任务失败");
  }
}

esp_err_t http_request_submit(const http_request_config_t *config)
{
  if (config == NULL || config->url == NULL || config->url[0] == '\0') {
    ESP_LOGW(TAG, "请求参数非法");
    return ESP_ERR_INVALID_ARG;
  }
  if (s_queue == NULL) {
    ESP_LOGW(TAG, "组件未初始化");
    return ESP_ERR_INVALID_STATE;
  }

  http_request_item_t item = {
      .method = config->method,
      .chunk_size = (config->chunk_size > 0) ? config->chunk_size : HTTP_REQUEST_CHUNK_DEFAULT,
      .timeout_ms = (config->timeout_ms > 0) ? config->timeout_ms : HTTP_REQUEST_TIMEOUT_DEFAULT,
      .on_data = config->on_data,
      .on_done = config->on_done,
  };
  item.url = http_request_strdup(config->url);
  item.post_data = http_request_strdup(config->post_data);
  item.content_type = http_request_strdup(config->content_type);

  if (item.url == NULL) {
    http_request_item_clear(&item);
    return ESP_ERR_NO_MEM;
  }
  if (xQueueSend(s_queue, &item, 0) != pdTRUE) {
    ESP_LOGW(TAG, "请求队列已满, 丢弃请求: %s", config->url);
    http_request_item_clear(&item);
    return ESP_ERR_TIMEOUT;
  }
  return ESP_OK;
}

esp_err_t http_request_get(const char *url, http_request_callback_t on_data,
                           http_request_done_callback_t on_done)
{
  const http_request_config_t config = {
      .url = url,
      .method = HTTP_METHOD_GET,
      .on_data = on_data,
      .on_done = on_done,
  };
  return http_request_submit(&config);
}

esp_err_t http_request_post(const char *url, const char *post_data, http_request_callback_t on_data,
                            http_request_done_callback_t on_done)
{
  const http_request_config_t config = {
      .url = url,
      .method = HTTP_METHOD_POST,
      .post_data = post_data,
      .on_data = on_data,
      .on_done = on_done,
  };
  return http_request_submit(&config);
}

void http_request_deinit(void)
{
  if (s_task != NULL) {
    vTaskDelete(s_task);
    s_task = NULL;
  }
  if (s_queue != NULL) {
    //释放队列中还没处理的请求占用的内存
    http_request_item_t item;
    while (xQueueReceive(s_queue, &item, 0) == pdTRUE) {
      http_request_item_clear(&item);
    }
    s_queue = NULL;
  }
  if (s_client != NULL) {
    esp_http_client_cleanup(s_client);
    s_client = NULL;
  }
}