#include "ota_offer_protocol.h"
#include "ota_binary_stream.h"
#include "ota_uart_tune.h"
#include "ota_frame_protocol.h"
#include "device_nvs.h"

#include "sdkconfig.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static volatile bool s_query_sent_boot_s;
static volatile bool s_ota_armed_s;
static ota_offer_done_cb_t s_done_cb_s;

void ota_offer_reset_boot(void)
{
    s_query_sent_boot_s = false;
    s_ota_armed_s = false;
}

bool ota_offer_is_armed(void)
{
    return s_ota_armed_s;
}

bool ota_offer_session_busy(void)
{
    return s_ota_armed_s || ota_binary_stream_is_active();
}

void ota_offer_disarm(void)
{
    s_ota_armed_s = false;
}

static void ota_offer_format_device_id(char *out, size_t cap, const char *device_id)
{
    snprintf(out, cap, "%s", device_id_plain(device_id));
}

static void ota_offer_body_add_ota_tune(cJSON *body)
{
#if CONFIG_APP_LINK_UART
    int baud = CONFIG_APP_UART_LINK_BAUDRATE;
#else
    int baud = 0;
#endif
    uint16_t chunk = ota_uart_tune_chunk_for_baud(baud);
    uint32_t ack_ms = ota_uart_tune_ack_timeout_ms(baud > 0 ? baud : 115200, chunk);

    cJSON_AddNumberToObject(body, "ota_chunk", chunk);
    cJSON_AddNumberToObject(body, "ota_chunk_max", OTA_FRAME_MAX_PAYLOAD);
    cJSON_AddNumberToObject(body, "ota_ack_timeout_ms", (double)ack_ms);
#if CONFIG_APP_LINK_UART
    cJSON_AddNumberToObject(body, "uart_baud", baud);
#endif
}

static char *ota_offer_build(int message_type, cJSON *body)
{
    if (!body) {
        return NULL;
    }

    cJSON *root = cJSON_CreateObject();
    cJSON *head = cJSON_CreateObject();
    if (!root || !head) {
        cJSON_Delete(root);
        cJSON_Delete(head);
        cJSON_Delete(body);
        return NULL;
    }

    cJSON_AddNumberToObject(head, "message_type", message_type);
    cJSON_AddItemToObject(root, "head", head);
    cJSON_AddItemToObject(root, "body", body);

    char *out = cJSON_PrintUnformatted(root);
    cJSON_Delete(root);
    return out;
}

char *ota_offer_query_json_malloc(const char *device_id)
{
    cJSON *body = cJSON_CreateObject();
    if (!body) {
        return NULL;
    }

    char id_full[64];
    ota_offer_format_device_id(id_full, sizeof(id_full), device_id);

    cJSON_AddStringToObject(body, "phase", "query");
    cJSON_AddStringToObject(body, "device_ID", id_full);
    cJSON_AddStringToObject(body, "version", CONFIG_MY_APP_VERSION);
    ota_offer_body_add_ota_tune(body);

    return ota_offer_build(OTA_MSG_DEVICE_OFFER, body);
}

char *ota_offer_active_json_malloc(const char *device_id)
{
    cJSON *body = cJSON_CreateObject();
    if (!body) {
        return NULL;
    }

    char id_full[64];
    ota_offer_format_device_id(id_full, sizeof(id_full), device_id);

    cJSON_AddStringToObject(body, "phase", "active");
    cJSON_AddStringToObject(body, "device_ID", id_full);
    cJSON_AddStringToObject(body, "version", CONFIG_MY_APP_VERSION);
    ota_offer_body_add_ota_tune(body);

    return ota_offer_build(OTA_MSG_DEVICE_OFFER, body);
}

char *ota_offer_result_json_malloc(const char *device_id,
                                   bool success,
                                   uint32_t bytes_written,
                                   const char *err_step)
{
    cJSON *body = cJSON_CreateObject();
    if (!body) {
        return NULL;
    }

    char id_full[64];
    ota_offer_format_device_id(id_full, sizeof(id_full), device_id);

    cJSON_AddStringToObject(body, "phase", "done");
    cJSON_AddNumberToObject(body, "ok", success ? 1 : 0);
    cJSON_AddStringToObject(body, "device_ID", id_full);
    cJSON_AddStringToObject(body, "version", CONFIG_MY_APP_VERSION);
    cJSON_AddNumberToObject(body, "written", (double)bytes_written);
    if (!success && err_step != NULL && err_step[0] != '\0') {
        cJSON_AddStringToObject(body, "err", err_step);
    } else {
        cJSON_AddStringToObject(body, "err", "");
    }

    return ota_offer_build(OTA_MSG_DEVICE_OFFER, body);
}

bool ota_offer_parse_phone_response(cJSON *root)
{
    if (!root) {
        return false;
    }

    cJSON *head = cJSON_GetObjectItem(root, "head");
    if (!head || !cJSON_IsObject(head)) {
        return false;
    }

    cJSON *m_type = cJSON_GetObjectItem(head, "message_type");
    if (!m_type || !cJSON_IsNumber(m_type)) {
        return false;
    }

    if ((int)m_type->valuedouble == OTA_MSG_PHONE_OTA_START) {
        s_ota_armed_s = true;
        return true;
    }

    return false;
}

bool ota_offer_mark_query_sent(void)
{
    if (s_query_sent_boot_s) {
        return false;
    }
    s_query_sent_boot_s = true;
    return true;
}

void ota_offer_reset_query_sent(void)
{
    s_query_sent_boot_s = false;
}

void ota_offer_set_done_callback(ota_offer_done_cb_t cb)
{
    s_done_cb_s = cb;
}

void ota_offer_notify_done(bool success, uint32_t bytes_written, const char *err_step)
{
    s_ota_armed_s = false;
    if (s_done_cb_s) {
        s_done_cb_s(success, bytes_written, err_step);
    }
}

void ota_offer_report_abort(const char *err_step)
{
    if (!ota_offer_session_busy()) {
        return;
    }

    uint32_t written = ota_binary_stream_get_written_bytes();
    if (ota_binary_stream_is_active()) {
        ota_binary_stream_abort();
    }
    ota_offer_notify_done(false, written, err_step);
}
