// main/main.c
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include "cJSON.h"
#include "driver/gpio.h"
#include "driver/spi_master.h"
#include "esp_eth.h"
#include "esp_eth_mac_spi.h"
#include "esp_event.h"
#include "esp_http_client.h"
#include "esp_http_server.h"
#include "esp_log.h"
#include "esp_netif.h"
#include "esp_netif_net_stack.h"
#include "esp_system.h"
#include "ethernet_init.h"
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#include "freertos/task.h"
#include "led_strip.h"
#include "led_strip_types.h"
#include "lwip/dns.h"
#include "lwip/inet.h"
#include "lwip/netdb.h"

static const char* TAG = "app";

// ─────── User‐configurable defines ───────
#define STATIC_IP_ADDR "192.168.0.20"
#define STATIC_NETMASK "255.255.255.0"
#define STATIC_GW      "192.168.0.1"

#define PIN_W5500_MISO 11
#define PIN_W5500_MOSI 13
#define PIN_W5500_SCLK 12
#define PIN_W5500_CS   10
#define PIN_W5500_INT  9
#define PIN_W5500_RST  14

#define RMT_LED_GPIO   48

// ─────────────────────────────────────────

// for passing JSON send requests to the HTTP‐POST task
typedef struct {
    char host[256];
    char json[256];
} json_req_t;

static QueueHandle_t json_queue;

// parameters received from the web UI
static struct {
    char host[256];
    char key[64];
    char value[64];
    int interval; // seconds
    bool auto_mode;
} json_params = {.interval = 0, .auto_mode = false};

// handle to our WS2812 strip (1 LED)
static led_strip_handle_t strip = NULL;

// ─── Helper: set the single WS2812 to one of six named colors ───
static void
set_led_color(const char* color) {
    uint8_t r = 0, g = 0, b = 0;
    if (strcmp(color, "orange") == 0) {
        r = 255;
        g = 165;
        b = 0;
    } else if (strcmp(color, "blue") == 0) {
        r = 0;
        g = 0;
        b = 255;
    } else if (strcmp(color, "red") == 0) {
        r = 255;
        g = 0;
        b = 0;
    } else if (strcmp(color, "green") == 0) {
        r = 0;
        g = 255;
        b = 0;
    } else if (strcmp(color, "violet") == 0) {
        r = 238;
        g = 130;
        b = 238;
    } else if (strcmp(color, "yellow") == 0) {
        r = 255;
        g = 255;
        b = 0;
    }
    // clear & set & refresh
    led_strip_clear(strip);
    led_strip_set_pixel(strip, 0, r, g, b);
    led_strip_refresh(strip);

    ESP_LOGI(TAG, "LED color set to %s", color);
}

// ─── HTTP‐server handlers ──────────────────────────────────────────────

// Serve the control page
static const char index_html[] = R"rawliteral(
<!DOCTYPE html>
<html><head><meta charset="UTF-8"><title>ESP32 Control</title>
<script>
function setColor(){
  let c = document.getElementById('color').value;
  fetch('/set_color?color='+c)
    .then(r=>r.json())
    .then(j=>{ document.getElementById('status').innerText = 'LED → '+j.color; });
}
function updateParams(){
  let p = {
    interval: parseInt(document.getElementById('interval').value)||0,
    key:      document.getElementById('key').value,
    value:    document.getElementById('value').value,
    host:     document.getElementById('host').value,
    auto:     document.getElementById('auto').checked
  };
  fetch('/update_params',{
    method:'POST', headers:{'Content-Type':'application/json'},
    body: JSON.stringify(p)
  })
  .then(r=>r.json())
  .then(j=>{ document.getElementById('param_status').innerText = j.status; });
}
function manualSend(){
  fetch('/manual_send',{method:'POST'})
    .then(r=>r.json())
    .then(j=>{ document.getElementById('param_status').innerText = j.status; });
}
function onAutoChange(cb){
  let btn = document.getElementById('sendBtn');
  btn.disabled = cb.checked;
  btn.style.backgroundColor = cb.checked?'gray':'blue';
}
</script>
</head><body>
  <h1>ESP32‑S3 Control</h1>
  <label>Color:
    <select id="color" onchange="setColor()">
      <option>orange</option><option>blue</option><option>red</option>
      <option>green</option><option>violet</option><option>yellow</option>
    </select>
  </label>
  <p id="status"></p>
  <hr>
  <label>Interval (sec 0–9999): <input type="number" id="interval" min="0" max="9999" onchange="updateParams()"></label><br>
  <label>Key:   <input type="text" id="key"    onchange="updateParams()"></label><br>
  <label>Value: <input type="text" id="value"  onchange="updateParams()"></label><br>
  <label>Host:  <input type="text" id="host"   onchange="updateParams()"></label><br>
  <label>Automatic:
    <input type="checkbox" id="auto"
           onchange="updateParams(); onAutoChange(this)">
  </label><br>
  <button id="sendBtn" onclick="manualSend()" style="background:blue;color:white;" disabled>Send</button>
  <p id="param_status"></p>
</body></html>
)rawliteral";

static esp_err_t
root_get_handler(httpd_req_t* req) {
    httpd_resp_set_type(req, "text/html");
    httpd_resp_sendstr(req, index_html);
    return ESP_OK;
}

static esp_err_t
set_color_get_handler(httpd_req_t* req) {
    char color[16];
    size_t buf_len = httpd_req_get_url_query_len(req) + 1;
    char* buf = malloc(buf_len);
    if (httpd_req_get_url_query_str(req, buf, buf_len) == ESP_OK
        && httpd_query_key_value(buf, "color", color, sizeof(color)) == ESP_OK) {
        set_led_color(color);
        cJSON* j = cJSON_CreateObject();
        cJSON_AddStringToObject(j, "status", "OK");
        cJSON_AddStringToObject(j, "color", color);
        char* s = cJSON_PrintUnformatted(j);
        httpd_resp_set_type(req, "application/json");
        httpd_resp_sendstr(req, s);
        cJSON_Delete(j);
        free(s);
    }
    free(buf);
    return ESP_OK;
}

// update json_params from the page
static esp_err_t
update_params_post_handler(httpd_req_t* req) {
    int len = req->content_len;
    char* buf = malloc(len + 1);
    int ret = httpd_req_recv(req, buf, len);
    buf[ret] = 0;
    cJSON* root = cJSON_Parse(buf);
    if (root) {
        cJSON* j;
        if ((j = cJSON_GetObjectItem(root, "interval")) && cJSON_IsNumber(j)) {
            json_params.interval = j->valueint;
        }
        if ((j = cJSON_GetObjectItem(root, "auto")) && cJSON_IsBool(j)) {
            json_params.auto_mode = cJSON_IsTrue(j);
        }
        if ((j = cJSON_GetObjectItem(root, "key")) && cJSON_IsString(j)) {
            strncpy(json_params.key, j->valuestring, sizeof(json_params.key) - 1);
        }
        if ((j = cJSON_GetObjectItem(root, "value")) && cJSON_IsString(j)) {
            strncpy(json_params.value, j->valuestring, sizeof(json_params.value) - 1);
        }
        if ((j = cJSON_GetObjectItem(root, "host")) && cJSON_IsString(j)) {
            strncpy(json_params.host, j->valuestring, sizeof(json_params.host) - 1);
            ESP_LOGI(TAG, "Host: %s", json_params.host);
        }
        cJSON_Delete(root);
    }
    free(buf);
    // ack
    httpd_resp_set_type(req, "application/json");
    httpd_resp_sendstr(req, "{\"status\":\"Params updated\"}");
    return ESP_OK;
}

// enqueue a manual‐send
static esp_err_t
manual_send_post_handler(httpd_req_t* req) {
    json_req_t job;
    // build the minimal JSON to send
    cJSON* j = cJSON_CreateObject();
    cJSON_AddStringToObject(j, json_params.key, json_params.value);
    char* s = cJSON_PrintUnformatted(j);
    ESP_LOGI(TAG, "JSON: %s", s);
    snprintf(job.json, sizeof(job.json), "%s", s);
    snprintf(job.host, sizeof(job.host), "%s", json_params.host);
    ESP_LOGI(TAG, "To: %s", job.host);
    xQueueSend(json_queue, &job, portMAX_DELAY);
    cJSON_Delete(j);
    free(s);
    httpd_resp_set_type(req, "application/json");
    httpd_resp_sendstr(req, "{\"status\":\"queued\"}");
    return ESP_OK;
}

// kick off the HTTP server and register URIs
static httpd_handle_t
start_webserver(void) {
    httpd_handle_t server = NULL;
    httpd_config_t config = HTTPD_DEFAULT_CONFIG();
    config.max_uri_handlers = 40;
    config.max_resp_headers = 4096;
    config.stack_size = 8192;
    ESP_ERROR_CHECK(httpd_start(&server, &config));
    httpd_uri_t u;

    u.uri = "/";
    u.method = HTTP_GET;
    u.handler = root_get_handler;
    httpd_register_uri_handler(server, &u);

    u.uri = "/set_color";
    u.method = HTTP_GET;
    u.handler = set_color_get_handler;
    httpd_register_uri_handler(server, &u);

    u.uri = "/update_params";
    u.method = HTTP_POST;
    u.handler = update_params_post_handler;
    httpd_register_uri_handler(server, &u);

    u.uri = "/manual_send";
    u.method = HTTP_POST;
    u.handler = manual_send_post_handler;
    httpd_register_uri_handler(server, &u);

    return server;
}

// ────────── JSON‐POST task ──────────
static esp_err_t
_http_event_cb(esp_http_client_event_t* evt) {
    switch (evt->event_id) {
        case HTTP_EVENT_ERROR: ESP_LOGE(TAG, "HTTP_EVENT_ERROR"); break;
        case HTTP_EVENT_ON_CONNECTED: ESP_LOGI(TAG, "HTTP_EVENT_ON_CONNECTED"); break;
        case HTTP_EVENT_HEADER_SENT: break;
        case HTTP_EVENT_ON_HEADER: ESP_LOGI(TAG, "H: %.*s", evt->data_len, (char*)evt->data); break;
        case HTTP_EVENT_ON_DATA: ESP_LOGI(TAG, "D: %.*s", evt->data_len, (char*)evt->data); break;
        case HTTP_EVENT_ON_FINISH: ESP_LOGI(TAG, "HTTP_EVENT_ON_FINISH"); break;
        case HTTP_EVENT_DISCONNECTED: ESP_LOGI(TAG, "HTTP_EVENT_DISCONNECTED"); break;
        case HTTP_EVENT_REDIRECT: ESP_LOGI(TAG, "HTTP_EVENT_REDIRECT"); break;
    }
    return ESP_OK;
}

static void
json_client_task(void* arg) {
    json_req_t req;
    while (1) {
        if (xQueueReceive(json_queue, &req, portMAX_DELAY) == pdTRUE) {
            esp_http_client_config_t cfg = {
                .url = req.host,
                .event_handler = _http_event_cb,
                .transport_type = HTTP_TRANSPORT_OVER_SSL,
                .skip_cert_common_name_check = true,
            };
            esp_http_client_handle_t c = esp_http_client_init(&cfg);
            esp_http_client_set_method(c, HTTP_METHOD_POST);
            esp_http_client_set_header(c, "Content-Type", "application/json");
            esp_http_client_set_post_field(c, req.json, strlen(req.json));
            esp_err_t err = esp_http_client_perform(c);
            if (err == ESP_OK) {
                int sc = esp_http_client_get_status_code(c);
                ESP_LOGI(TAG, "POST → %s  (HTTP %d)", req.json, sc);
            } else {
                ESP_LOGE(TAG, "POST failed: %s", esp_err_to_name(err));
            }
            esp_http_client_cleanup(c);
        }
    }
}

// ────── Auto‐POST task ──────
static void
json_auto_task(void* arg) {
    while (1) {
        if (json_params.auto_mode && json_params.interval > 0) {
            vTaskDelay(pdMS_TO_TICKS(json_params.interval * 1000));
            // enqueue same struct as manual
            json_req_t job;
            cJSON* j = cJSON_CreateObject();
            cJSON_AddStringToObject(j, json_params.key, json_params.value);
            char* s = cJSON_PrintUnformatted(j);
            ESP_LOGI(TAG, "Auto‐POST: %s", s);
            snprintf(job.json, sizeof(job.json), "%s", s);
            snprintf(job.host, sizeof(job.host), "%s", json_params.host);
            ESP_LOGI(TAG, "To: %s", job.host);
            xQueueSend(json_queue, &job, portMAX_DELAY);
            cJSON_Delete(j);
            free(s);
        } else {
            vTaskDelay(pdMS_TO_TICKS(500));
        }
    }
}

// ───── Ethernet event handlers ───────────────────────────────────────────
static void
eth_event_handler(void* arg, esp_event_base_t eb, int32_t ei, void* ev) {
    if (ei == ETHERNET_EVENT_CONNECTED) {
        ESP_LOGI(TAG, "Ethernet Link Up");
    } else if (ei == ETHERNET_EVENT_DISCONNECTED) {
        ESP_LOGI(TAG, "Ethernet Link Down");
    } else if (ei == ETHERNET_EVENT_START) {
        ESP_LOGI(TAG, "Ethernet Started");
    } else if (ei == ETHERNET_EVENT_STOP) {
        ESP_LOGI(TAG, "Ethernet Stopped");
    }
}

static void
got_ip_event_handler(void* arg, esp_event_base_t eb, int32_t ei, void* ev) {
    ip_event_got_ip_t* e = ev;
    const esp_netif_ip_info_t* ip = &e->ip_info;
    ESP_LOGI(TAG, "Got IP:" IPSTR, IP2STR(&ip->ip));
    ESP_LOGI(TAG, "Mask :" IPSTR, IP2STR(&ip->netmask));
    ESP_LOGI(TAG, "GW   :" IPSTR, IP2STR(&ip->gw));

    // compare to STATIC_IP_ADDR
    esp_netif_ip_info_t user = *ip;
    ip4_addr_t want;
    ip4addr_aton(STATIC_IP_ADDR, &want);
    if (!ip4_addr_cmp(&user.ip, &want)) {
        ESP_LOGW(TAG, "WARNING: static %s ≠ assigned " IPSTR, STATIC_IP_ADDR, IP2STR(&user.ip));
    }

    vTaskDelay(pdMS_TO_TICKS(2000)); // Wait 2 seconds

    ESP_LOGI(TAG, "Testing DNS resolution for 'www.utr-control.com'...");
    struct addrinfo hints = {.ai_family = AF_INET, .ai_socktype = SOCK_STREAM};
    struct addrinfo* res;
    int err = getaddrinfo("www.google.com", NULL, &hints, &res);
    if (err == 0) {
        struct sockaddr_in* ipv4 = (struct sockaddr_in*)res->ai_addr;
        ESP_LOGI(TAG, "DNS resolved");
        freeaddrinfo(res);
    } else {
        ESP_LOGE(TAG, "DNS resolution failed: %s, error code: %d", lwip_strerr(err), err);
    }
}

// ────────── app_main ─────────────────────────────────────────────────────
void
app_main(void) {
    // hard-reset the W5500
    gpio_reset_pin(PIN_W5500_RST);
    gpio_set_direction(PIN_W5500_RST, GPIO_MODE_OUTPUT);
    gpio_set_level(PIN_W5500_RST, 0);
    vTaskDelay(pdMS_TO_TICKS(500));
    gpio_set_level(PIN_W5500_RST, 1);
    vTaskDelay(pdMS_TO_TICKS(500));
    ESP_LOGI(TAG, "W5500 reset done");

    // 1) SPI bus init for W5500
    spi_bus_config_t buscfg = {
        .miso_io_num = PIN_W5500_MISO,
        .mosi_io_num = PIN_W5500_MOSI,
        .sclk_io_num = PIN_W5500_SCLK,
        .quadwp_io_num = -1,
        .quadhd_io_num = -1,
        .max_transfer_sz = 1536,
    };
    ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO));

    // 2) W5500 MAC+PHY init
    spi_device_interface_config_t w5500_devcfg = {
        .command_bits = 16,
        .address_bits = 8,
        .dummy_bits = 0,
        .mode = 0,
        .spics_io_num = PIN_W5500_CS,
        .clock_speed_hz = 1 * 1000 * 1000,
        .queue_size = 20,
    };
    eth_w5500_config_t w5500_cfg = ETH_W5500_DEFAULT_CONFIG(SPI2_HOST, &w5500_devcfg);
    w5500_cfg.int_gpio_num = PIN_W5500_INT;
    ESP_ERROR_CHECK(gpio_install_isr_service(0));

    eth_mac_config_t mac_cfg = ETH_MAC_DEFAULT_CONFIG();
    mac_cfg.flags |= ETH_MAC_FLAG_PIN_TO_CORE;
    esp_eth_mac_t* mac = esp_eth_mac_new_w5500(&w5500_cfg, &mac_cfg);

    eth_phy_config_t phy_cfg = ETH_PHY_DEFAULT_CONFIG();
    phy_cfg.reset_gpio_num = PIN_W5500_RST;

    esp_eth_phy_t* phy = esp_eth_phy_new_w5500(&phy_cfg);

    esp_eth_config_t eth_config = ETH_DEFAULT_CONFIG(mac, phy);
    esp_eth_handle_t eth_handle = NULL;
    ESP_ERROR_CHECK(esp_eth_driver_install(&eth_config, &eth_handle));

    uint8_t mac_addr[6];
    ESP_ERROR_CHECK(esp_eth_ioctl(eth_handle, ETH_CMD_G_MAC_ADDR, mac_addr));
    ESP_LOGI(TAG, "W5500 MAC address: %02x:%02x:%02x:%02x:%02x:%02x", mac_addr[0], mac_addr[1], mac_addr[2],
             mac_addr[3], mac_addr[4], mac_addr[5]);

    // 3) esp-netif + attach
    ESP_ERROR_CHECK(esp_netif_init());
    ESP_ERROR_CHECK(esp_event_loop_create_default());
    esp_netif_config_t cfg = ESP_NETIF_DEFAULT_ETH();
    esp_netif_t* eth_netif = esp_netif_new(&cfg);
    ESP_ERROR_CHECK(esp_netif_attach(eth_netif, esp_eth_new_netif_glue(eth_handle)));
    struct netif* lwip_netif = esp_netif_get_netif_impl(eth_netif);
    netif_set_default(lwip_netif);

    // 4) register events
    ESP_ERROR_CHECK(esp_event_handler_register(ETH_EVENT, ESP_EVENT_ANY_ID, eth_event_handler, NULL));
    ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_ETH_GOT_IP, got_ip_event_handler, NULL));

    // 5) Static IP?
    ESP_ERROR_CHECK(esp_netif_dhcpc_stop(eth_netif));
    esp_netif_ip_info_t info = {};
    ESP_ERROR_CHECK(esp_netif_str_to_ip4(STATIC_IP_ADDR, &info.ip));
    ESP_ERROR_CHECK(esp_netif_str_to_ip4(STATIC_NETMASK, &info.netmask));
    ESP_ERROR_CHECK(esp_netif_str_to_ip4(STATIC_GW, &info.gw));
    ESP_ERROR_CHECK(esp_netif_set_ip_info(eth_netif, &info));

    esp_netif_dns_info_t dns_info = {
        .ip.type = IPADDR_TYPE_V4,
        .ip.u_addr.ip4.addr = ipaddr_addr("1.1.1.1"),
    };
    ESP_ERROR_CHECK(esp_netif_set_dns_info(eth_netif, ESP_NETIF_DNS_MAIN, &dns_info));

    esp_netif_dns_info_t dns_info_fallback = {
        .ip.type = IPADDR_TYPE_V4,
        .ip.u_addr.ip4.addr = ipaddr_addr("8.8.8.8"),
    };
    ESP_ERROR_CHECK(esp_netif_set_dns_info(eth_netif, ESP_NETIF_DNS_FALLBACK, &dns_info_fallback));

    // 6) start
    // ESP_ERROR_CHECK(ethernet_init_all(eth_handle, NULL));
    ESP_ERROR_CHECK(esp_eth_start(eth_handle));

    // 7) init WS2812 on GPIO48
    led_strip_config_t strip_cfg = {
        .strip_gpio_num = RMT_LED_GPIO,
        .max_leds = 1,
        .led_model = LED_MODEL_WS2812,
        .color_component_format = LED_STRIP_COLOR_COMPONENT_FMT_GRB,
        .flags = {.invert_out = false},
    };

    led_strip_rmt_config_t rmt_cfg = {
        .clk_src = RMT_CLK_SRC_DEFAULT,
        .resolution_hz = 10 * 1000 * 1000,
        .flags = {.with_dma = false},
    };
    ESP_ERROR_CHECK(led_strip_new_rmt_device(&strip_cfg, &rmt_cfg, &strip));
    led_strip_clear(strip);
    led_strip_refresh(strip);

    // 8) start HTTP server
    start_webserver();

    // 9) JSON send queue and tasks
    json_queue = xQueueCreate(10, sizeof(json_req_t));
    xTaskCreate(json_client_task, "json_manual", 8 * 1024, NULL, 5, NULL);
    xTaskCreate(json_auto_task, "json_auto", 8 * 1024, NULL, 5, NULL);
}
