// {{name}} — tiny heartbeat node ({{board}}). Identity from NVS "tiny"/"cfg" (JSON written by tools/provision.sh).
#include <string.h>
#include "cJSON.h"
#include "esp_event.h"
#include "esp_http_client.h"
#include "esp_log.h"
#include "esp_netif.h"
#include "esp_wifi.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "nvs_flash.h"

static const char *TAG = "{{slug}}";
#define HEARTBEAT_MS 30000
#define CAPABILITIES "{{capabilities}}"

typedef struct { char device_id[64]; char token[96]; char api[96]; char name[32]; char ssid[33]; char key[65]; } tiny_cfg_t;

static bool load_cfg(tiny_cfg_t *c) {
  nvs_handle_t h; size_t len = 0;
  if (nvs_open("tiny", NVS_READONLY, &h) != ESP_OK) return false;
  if (nvs_get_blob(h, "cfg", NULL, &len) != ESP_OK || len == 0 || len > 4096) { nvs_close(h); return false; }
  char *buf = malloc(len + 1); nvs_get_blob(h, "cfg", buf, &len); buf[len] = 0; nvs_close(h);
  cJSON *j = cJSON_Parse(buf); free(buf);
  if (!j) return false;
  memset(c, 0, sizeof *c);
  #define S(field, key) do { cJSON *v = cJSON_GetObjectItem(j, key); if (cJSON_IsString(v)) strlcpy(c->field, v->valuestring, sizeof c->field); } while (0)
  S(device_id, "device_id"); S(token, "token"); S(api, "api"); S(name, "name");
  cJSON *nets = cJSON_GetObjectItem(j, "networks");
  if (cJSON_IsArray(nets) && cJSON_GetArraySize(nets) > 0) {
    cJSON *n0 = cJSON_GetArrayItem(nets, 0);
    cJSON *s = cJSON_GetObjectItem(n0, "ssid"), *k = cJSON_GetObjectItem(n0, "key");
    if (cJSON_IsString(s)) strlcpy(c->ssid, s->valuestring, sizeof c->ssid);
    if (cJSON_IsString(k)) strlcpy(c->key, k->valuestring, sizeof c->key);
  }
  cJSON_Delete(j);
  if (!c->api[0]) strlcpy(c->api, "https://tiny.technology", sizeof c->api);
  return c->device_id[0] && c->token[0] && c->ssid[0];
}

static void wifi_start(const tiny_cfg_t *c) {
  esp_netif_init(); esp_event_loop_create_default(); esp_netif_create_default_wifi_sta();
  wifi_init_config_t wcfg = WIFI_INIT_CONFIG_DEFAULT(); esp_wifi_init(&wcfg);
  wifi_config_t sta = {0};
  strlcpy((char *)sta.sta.ssid, c->ssid, sizeof sta.sta.ssid);
  strlcpy((char *)sta.sta.password, c->key, sizeof sta.sta.password);
  esp_wifi_set_mode(WIFI_MODE_STA); esp_wifi_set_config(WIFI_IF_STA, &sta); esp_wifi_start(); esp_wifi_connect();
}

static int heartbeat(const tiny_cfg_t *c) {
  char url[160]; snprintf(url, sizeof url, "%s/api/devices/heartbeat", c->api);
  char body[512]; snprintf(body, sizeof body, "{\"deviceId\":\"%s\",\"token\":\"%s\",\"capabilities\":%s}", c->device_id, c->token, CAPABILITIES);
  esp_http_client_config_t hc = { .url = url, .method = HTTP_METHOD_POST, .timeout_ms = 10000, .crt_bundle_attach = esp_crt_bundle_attach };
  esp_http_client_handle_t h = esp_http_client_init(&hc);
  esp_http_client_set_header(h, "Content-Type", "application/json");
  esp_http_client_set_post_field(h, body, strlen(body));
  int status = esp_http_client_perform(h) == ESP_OK ? esp_http_client_get_status_code(h) : -1;
  esp_http_client_cleanup(h);
  return status;
}

void app_main(void) {
  ESP_ERROR_CHECK(nvs_flash_init());
  tiny_cfg_t cfg;
  if (!load_cfg(&cfg)) { ESP_LOGW(TAG, "not provisioned — run tools/provision.sh"); return; }
  ESP_LOGI(TAG, "I am %s (%s)", cfg.name, cfg.device_id);
  wifi_start(&cfg);
  vTaskDelay(pdMS_TO_TICKS(8000));
  for (;;) {
    int s = heartbeat(&cfg);
    ESP_LOGI(TAG, "heartbeat %d", s);
    if (s == 401) { ESP_LOGE(TAG, "token revoked — re-provision"); return; }
    vTaskDelay(pdMS_TO_TICKS(HEARTBEAT_MS));
  }
}
