/* =============================================================================
 * reporter.c - 이벤트 보고 헬퍼 구현 (본문 생성 -> 서명 -> TLS POST + 재시도)
 *
 * SHT30 온습도 보드가 사용한다. RPi 의 post_reading() 의
 * "본문 구성 -> requests.post -> 실패 시 재시도" 흐름을 그대로 옮긴다.
 *
 * 동작 개요(report_sht30_event):
 *   1) jsonbody.c 로 JSON 본문 생성 (BOARD_* 식별값 + timesync_now() timestamp)
 *   2) tls_transport_init() 로 TLS 전송(transport_t) 획득
 *   3) httpapi.c::api_post_once() 로 1회 POST (서명/연결/송수신/상태파싱/종료 포함)
 *   4) HTTP 200 이면 성공(0), 아니면 APP_HTTP_RETRY_DELAY_MS 대기 후 재시도
 *      (총 시도 = 1 + APP_HTTP_RETRY_COUNT)
 *
 * 동시성:
 *   현재 보고는 측정 태스크 단독에서 호출되지만, 향후 다중 호출자에 대비해
 *   mbedTLS 컨텍스트/LwIP 소켓 세션을 정적 뮤텍스로 직렬화한다(한 번에 하나의
 *   TLS 세션만 수립).
 * ===========================================================================*/
#include "reporter.h"

#include "app_config.h"     /* APP_API_HOST/PORT/PATH, APP_HTTP_*, APP_VERSION */
#include "board_config.h"   /* BOARD_DEVICE_ID/LOCATION/SENSOR_ID/SENSOR_NAME */
#include "jsonbody.h"       /* jb_sht30_event */
#include "httpapi.h"        /* api_post_once */
#include "tls.h"            /* tls_transport_init */
#include "timesync.h"       /* timesync_now / timesync_is_set */
#include "applog.h"         /* LOGI / LOGW / LOGE */

#include "FreeRTOS.h"
#include "task.h"           /* vTaskDelay, xTaskGetSchedulerState */
#include "semphr.h"         /* xSemaphoreCreateMutexStatic 등 */

/* ── 상수 ───────────────────────────────────────────────────────────────── */
#define REPORT_BODY_BUFSZ      512u   /* 본문 버퍼 (SHT30 본문에 충분) */
#define HTTP_STATUS_OK         200    /* 성공으로 간주하는 HTTP 상태 코드 */

/* ── TLS 세션 직렬화용 정적 뮤텍스 ─────────────────────────────────────────
 * 정적 할당(xSemaphoreCreateMutexStatic)을 사용해 힙/초기화 순서 의존성을
 * 없앤다. report_init() 호출 없이도 첫 사용 시 lazy 생성한다. */
static StaticSemaphore_t s_tls_mutex_storage;
static SemaphoreHandle_t s_tls_mutex = NULL;

/* 뮤텍스 lazy 초기화. 스케줄러 시작 전 호출되어도 안전하도록 임계구역으로
 * 보호한다(동시 생성 방지). 생성 실패는 NULL 로 남으며, 이후 락 단계에서
 * "락 없이 진행"으로 폴백한다. */
static SemaphoreHandle_t reporter_mutex(void)
{
    if (s_tls_mutex == NULL) {
        taskENTER_CRITICAL();
        if (s_tls_mutex == NULL) {
            s_tls_mutex = xSemaphoreCreateMutexStatic(&s_tls_mutex_storage);
        }
        taskEXIT_CRITICAL();
    }
    return s_tls_mutex;
}

/* 보고 절차 진입: 뮤텍스 확보. 스케줄러가 동작 중일 때만 블로킹 take 한다.
 * (스케줄러 시작 전 부팅 단계 보고에서는 동시성이 없으므로 락을 건너뛴다.) */
static void reporter_lock(void)
{
    SemaphoreHandle_t m = reporter_mutex();
    if (m != NULL && xTaskGetSchedulerState() == taskSCHEDULER_RUNNING) {
        (void)xSemaphoreTake(m, portMAX_DELAY);
    }
}

static void reporter_unlock(void)
{
    SemaphoreHandle_t m = s_tls_mutex;
    if (m != NULL && xTaskGetSchedulerState() == taskSCHEDULER_RUNNING) {
        (void)xSemaphoreGive(m);
    }
}

/* ── 본문 1개를 재시도 포함하여 POST ───────────────────────────────────────
 * body 는 이미 완성된 JSON 본문. what 은 로그용 라벨(예: "sht30/periodic").
 * 반환: HTTP 200 이면 0, 그 외/통신 실패는 음수. */
static int report_post_body(const char *what, const char *body)
{
    /* 총 시도 횟수 = 최초 1회 + 재시도 APP_HTTP_RETRY_COUNT (RPi 재시도 루프와 동일) */
    const unsigned max_attempts = 1u + APP_HTTP_RETRY_COUNT;
    int last_rc = -1;

    reporter_lock();
    for (unsigned attempt = 1u; attempt <= max_attempts; ++attempt) {
        /* 매 시도마다 새 transport 를 바인딩한다(connect 시 핸드셰이크). */
        transport_t t;
        int init_rc = tls_transport_init(&t);
        if (init_rc != 0) {
            last_rc = init_rc;
            LOGE("report %s: TLS init 실패 rc=%d (시도 %u/%u)",
                 what, init_rc, attempt, max_attempts);
        } else {
            int code = api_post_once(&t, APP_API_HOST, APP_API_PORT, APP_API_PATH,
                                     BOARD_DEVICE_ID, APP_API_KEY, body,
                                     APP_HTTP_TIMEOUT_MS);
            if (code == HTTP_STATUS_OK) {
                LOGI("report %s: HTTP 200 OK (시도 %u/%u)", what, attempt, max_attempts);
                reporter_unlock();
                return 0;   /* 성공 */
            }

            if (code > 0) {
                /* 서버 응답은 받았으나 비-200 (예: 4xx/5xx) */
                last_rc = -code;
                LOGW("report %s: HTTP %d (시도 %u/%u)", what, code, attempt, max_attempts);
            } else {
                /* 전송 계층 오류(연결/송수신/응답없음) */
                last_rc = code;
                LOGW("report %s: 전송 오류 rc=%d (시도 %u/%u)",
                     what, code, attempt, max_attempts);
            }
        }

        /* 마지막 시도가 아니면 대기 후 재시도 */
        if (attempt < max_attempts) {
            vTaskDelay(pdMS_TO_TICKS(APP_HTTP_RETRY_DELAY_MS));
        }
    }
    reporter_unlock();

    LOGE("report %s: 모든 시도 실패 (last rc=%d)", what, last_rc);
    return (last_rc < 0) ? last_rc : -1;
}

/* 현재 timestamp 획득. 미동기 시 0 이며 경고를 남기되 그대로 전송한다
 * (서버가 수신 시각으로 보정할 수 있도록). */
static uint32_t reporter_timestamp(const char *what)
{
    if (!timesync_is_set()) {
        LOGW("report %s: 시간 미동기(timestamp=0) 상태로 전송", what);
    }
    return timesync_now();
}

/* ── 공개 API ───────────────────────────────────────────────────────────── */

int report_sht30_event(const char *event_type, double temp_c, double rh,
                       const char *metric_status)
{
    char body[REPORT_BODY_BUFSZ];
    uint32_t ts = reporter_timestamp("sht30");

    /* SHT30 본문에는 app_version(APP_VERSION) 을 포함한다. */
    int n = jb_sht30_event(body, sizeof(body),
                           BOARD_DEVICE_ID, BOARD_DEVICE_LOCATION,
                           BOARD_SENSOR_ID, BOARD_SENSOR_NAME,
                           event_type, ts, temp_c, rh,
                           metric_status, APP_VERSION);
    if (n < 0) {
        LOGE("report sht30: 본문 버퍼 부족(event=%s)", event_type ? event_type : "?");
        return -1;
    }

    LOGI("report sht30: event=%s status=%s ts=%lu (%d bytes)",
         event_type ? event_type : "?",
         metric_status ? metric_status : "?",
         (unsigned long)ts, n);

    return report_post_body(event_type ? event_type : "sht30", body);
}
