//
// Created by dongwenzhe on 2022/10/9.
//
#include <string>
#include <map>
using namespace std;

#include <sys/ioctl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <net/if.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <memory.h>
#include <cstdlib>
#include "opendhcpd.h"
#include "s2j/cJSON.h"
#include "haywire.h"
#include "misc.h"
#include <net/if_arp.h>
#include <libconfig.h>
#include <zlog.h>
#include "config.h"
#include "proto.h"

extern data2   cfig;
extern bool    kRunning;
extern dhcpMap dhcpCache;
extern time_t  t;

static void sendUserList(data19 *req, const char *pRequest) {
    char              logBuff[512];
    dhcpMap::iterator p;

    dzlog_debug("Input: %s\n", pRequest);

    if (pRequest == nullptr || strlen(pRequest) == 0) {
        sprintf(logBuff, "Requeset Json");
        logDHCPMess(logBuff, 1);
        return;
    }

    cJSON *pRoot = cJSON_Parse(pRequest);

    if (!pRoot) {
        return;
    }

    cJSON *pMsgContent = cJSON_GetObjectItem(pRoot, "msgContent");

    if (!pMsgContent) {
        cJSON_Delete(pRoot);
        return;
    }

    cJSON *pUserMac = cJSON_GetObjectItem(pMsgContent, "userMac");

    if (!pUserMac) {
        cJSON_Delete(pRoot);
        return;
    }

    req->memSize = (int)(2048 + (135 * dhcpCache.size()) + (cfig.dhcpSize * 26));
    req->dp      = (char *)calloc(1, req->memSize);

    if (!req->dp) {
        sprintf(logBuff, "Memory Error");
        logDHCPMess(logBuff, 1);
        return;
    }

    //    cJSON *pRspRoot = cJSON_CreateObject();
    //    cJSON_AddNumberToObject(pRspRoot, "version", 3);
    //    cJSON_AddNumberToObject(pRspRoot, "cryptoType", 0);
    //    cJSON_AddNumberToObject(pRspRoot, "timeStamp", (unsigned int)time(nullptr));

    cJSON *pRspMsg   = cJSON_CreateObject();
    cJSON *pMsgArray = cJSON_CreateArray();

    cJSON_AddItemToObject(pRspMsg, "userInfo", pMsgArray);

    for (int i = 0; i < cJSON_GetArraySize(pUserMac); i++) {
        char   tempbuff[512];
        cJSON *pItem    = cJSON_GetArrayItem(pUserMac, i);
        cJSON *pRspItem = cJSON_CreateObject();

        p = dhcpCache.find(pItem->valuestring);
        cJSON_AddStringToObject(pRspItem, "userMac", pItem->valuestring);

        if (p != dhcpCache.end()) {
            data7 *dhcpEntry = p->second;

            cJSON_AddStringToObject(pRspItem, "ip", IP2String(tempbuff, dhcpEntry->ip));
            cJSON_AddStringToObject(pRspItem, "hostname", dhcpEntry->hostname);
            if (dhcpEntry->display && dhcpEntry->expiry >= t) {
                tm *ttm = localtime(&dhcpEntry->expiry);
                strftime(tempbuff, sizeof(tempbuff), "%d-%b-%y %X", ttm);
                cJSON_AddStringToObject(pRspItem, "leaseExpiry", tempbuff);
            } else {
                cJSON_AddStringToObject(pRspItem, "leaseExpiry", "Expiry");
            }
        } else {
            cJSON_AddStringToObject(pRspItem, "ip", "");
            cJSON_AddStringToObject(pRspItem, "hostname", "");
        }

        cJSON_AddItemToArray(pMsgArray, pRspItem);
    }

    const char *pStrPro = proto_create_new(pRspMsg, 200);

    //cJSON_AddItemToObject(pRspRoot, "msgContent", pRspMsg);

    char *fp = req->dp;
    //char *maxData = req->dp + (req->memSize - 512);

    //fp += sprintf(fp, send200, strlen(rspBuf));
    fp += sprintf(fp, "%s", pStrPro);

    cJSON_Delete(pRoot);
    //cJSON_Delete(pRspMsg);

    req->bytes = (int)(fp - req->dp);
}

static void sendAllLists(data19 *req) {
    char              logBuff[512];
    data7            *dhcpEntry;
    dhcpMap::iterator p;

    req->memSize = (int)(2048 + (135 * dhcpCache.size()) + (cfig.dhcpSize * 26));
    req->dp      = (char *)calloc(1, req->memSize);

    if (!req->dp) {
        sprintf(logBuff, "Memory Error");
        logDHCPMess(logBuff, 1);
        return;
    }

    char *fp      = req->dp;
    char *maxData = req->dp + (req->memSize - 512);

    cJSON *pRspRoot = cJSON_CreateObject();
    cJSON_AddNumberToObject(pRspRoot, "version", 3);
    cJSON_AddNumberToObject(pRspRoot, "cryptoType", 0);
    cJSON_AddNumberToObject(pRspRoot, "timeStamp", (unsigned int)time(nullptr));

    cJSON *pRspMsg   = cJSON_CreateObject();
    cJSON *pMsgArray = cJSON_CreateArray();

    cJSON_AddItemToObject(pRspMsg, "users", pMsgArray);

    for (p = dhcpCache.begin(); kRunning && p != dhcpCache.end() && fp < maxData; p++) {
        //cJSON *pRspItem = cJSON_CreateObject();
        if ((dhcpEntry = p->second)) {
            cJSON_AddStringToObject(pMsgArray, "", dhcpEntry->mapname);
        }

        //cJSON_AddItemToArray(pMsgArray, pRspItem);
    }

    cJSON_AddItemToObject(pRspRoot, "msgContent", pRspMsg);

    fp += sprintf(fp, "%s", cJSON_Print(pRspRoot));

    cJSON_Delete(pRspRoot);

    req->bytes = (int)(fp - req->dp);
}

#define VALUE_TO_DHCP_TLV(buf, val, tag)                      \
    do {                                                      \
        int valSize;                                          \
        int numbytes = myTokenize((buf), (val), "/,.", true); \
        if (numbytes <= 255) {                                \
            char *ptr = (buf);                                \
            valSize   = 0;                                    \
            for (; *ptr; ptr = myGetToken(ptr, 1)) {          \
                if (isInt(ptr)) {                             \
                    hoption[valSize] = STR2INT(ptr);          \
                    valSize++;                                \
                } else {                                      \
                    break;                                    \
                }                                             \
            }                                                 \
            memcpy((val), hoption, valSize);                  \
            if (buffSize > valSize + 2) {                     \
                *dp = (tag);                                  \
                dp++;                                         \
                *dp = valSize;                                \
                dp++;                                         \
                memcpy(dp, (val), valSize);                   \
                dp += valSize;                                \
                buffSize -= (valSize + 2);                    \
            }                                                 \
        }                                                     \
    } while (0)

static void add_options(OBJ_DHCP_RNG pRange, data20 *optionData) {
    char    buff[1024];
    MYBYTE  hoption[256];
    MYBYTE *dp         = optionData->options;
    MYWORD  buffSize   = sizeof(dhcp_packet) - sizeof(dhcp_header);
    char    value[256] = {0};

    *dp = 0;
    dp++;

    //dhcp_range
    addDHCPRange(pRange.rangAddr);

    if (strlen(pRange.subnet) != 0) {
        strcpy(value, pRange.subnet);
        VALUE_TO_DHCP_TLV(buff, value, DHCP_OPTION_NETMASK);
        optionData->mask = (*((MYDWORD *)value));
    }

    if (strlen(pRange.dnsSvr) != 0) {
        strcpy(value, pRange.dnsSvr);
        VALUE_TO_DHCP_TLV(buff, value, DHCP_OPTION_DNS);
    }

    if (strlen(pRange.gateway) != 0) {
        strcpy(value, pRange.gateway);
        VALUE_TO_DHCP_TLV(buff, value, DHCP_OPTION_ROUTER);
    }

    //lease
    if (pRange.lease != 0) {
        MYDWORD j;
        j = pRange.lease;
        if (buffSize > 6) {
            *dp = DHCP_OPTION_IPADDRLEASE;
            dp++;
            *dp = 4;
            dp++;
            dp += pUInt(dp, j);
        }
    }

    *dp = DHCP_OPTION_END;
    dp++;
    optionData->optionSize = (dp - optionData->options);
}

static void expand_range_set(data19 *req, const char *pRequest) {
    char         logBuff[512];
    OBJ_DHCP_RNG pRange;
    char        *fp;
    cJSON       *pRspRoot;
    cJSON       *pExpandArray;

    dzlog_debug("Input: %s\n", pRequest);

    if (pRequest == nullptr || strlen(pRequest) == 0) {
        sprintf(logBuff, "Requeset Json");
        logDHCPMess(logBuff, 1);
        return;
    }

    cJSON *pRoot = cJSON_Parse(pRequest);
    if (!pRoot) {
        return;
    }

    cJSON *prange_set = cJSON_GetObjectItem(pRoot, "range_set");

    req->memSize = (int)(2048 + (135 * dhcpCache.size()) + (cfig.dhcpSize * 26));
    req->dp      = (char *)calloc(1, req->memSize);

    if (!req->dp) {
        sprintf(logBuff, "Memory Error");
        logDHCPMess(logBuff, 1);
        cJSON_Delete(pRoot);
        return;
    }

    fp           = req->dp;
    pRspRoot     = cJSON_CreateObject();
    pExpandArray = cJSON_CreateArray();
    cJSON_AddItemToObject(pRspRoot, "expansion", pExpandArray);

    for (int i = 0; i < cJSON_GetArraySize(prange_set); i++) {
        char   tempbuff[512];
        cJSON *pItem       = cJSON_GetArrayItem(prange_set, i);
        cJSON *pdhcp_range = cJSON_GetObjectItem(pItem, "dhcp_range");
        cJSON *pEx_range   = cJSON_CreateObject();

        if (!pdhcp_range) {
            cJSON_Delete(pRoot);
            return;
        }

        cJSON *psubnet_mask   = cJSON_GetObjectItem(pItem, "subnet_mask");
        cJSON *pdomain_server = cJSON_GetObjectItem(pItem, "domain_server");
        cJSON *pgateway       = cJSON_GetObjectItem(pItem, "gateway");
        cJSON *please_time    = cJSON_GetObjectItem(pItem, "lease_time");

        memset(&pRange, 0, sizeof(OBJ_DHCP_RNG));

        strcpy(pRange.rangAddr, pdhcp_range->valuestring);
        if (psubnet_mask) {
            strcpy(pRange.subnet, psubnet_mask->valuestring);
        }

        if (pdomain_server) {
            strcpy(pRange.dnsSvr, pdomain_server->valuestring);
        }

        if (pgateway) {
            strcpy(pRange.gateway, pgateway->valuestring);
        }

        if (please_time) {
            pRange.lease = STR2INT(please_time->valuestring);
        }

        //写入cfig
        MYBYTE m = cfig.rangeCount;
        data20 optionData {};
        optionData.rangeSetInd = m;

        MYBYTE *options                              = nullptr;
        cfig.rangeSet[optionData.rangeSetInd].active = true;

        add_options(pRange, &optionData);

        if (optionData.optionSize > 3) {
            options = (MYBYTE *)calloc(1, optionData.optionSize);
            memcpy(options, optionData.options, optionData.optionSize);
        }

        cfig.dhcpRanges[m].rangeSetInd = optionData.rangeSetInd;
        cfig.dhcpRanges[m].options     = options;
        cfig.dhcpRanges[m].mask        = optionData.mask;
        if (!cfig.dhcpRanges[m].mask) {
            cfig.dhcpRanges[m].mask = cfig.mask;
        }
        cfig.rangeCount = (char)(m + 1);

        cJSON_AddNumberToObject(pEx_range, "expand_start", cfig.dhcpRanges[m].rangeStart);
        cJSON_AddNumberToObject(pEx_range, "expand_end", cfig.dhcpRanges[m].rangeEnd);
        cJSON_AddItemToArray(pExpandArray, pEx_range);
    }

    fp += sprintf(fp, "%s", cJSON_Print(pRspRoot));

    cJSON_Delete(pRoot);
    cJSON_Delete(pRspRoot);

    req->bytes = (int)(fp - req->dp);
}

#pragma clang diagnostic push
#pragma ide diagnostic   ignored "cert-err34-c"
int                      getHwAddr(char *buff, char *mac) {
    if (buff == nullptr || mac == nullptr) {
        return -1;
    }

    int          i;
    unsigned int p[6];

    if (sscanf(mac, "%x:%x:%x:%x:%x:%x", &p[0], &p[1], &p[2], &p[3], &p[4], &p[5]) < 6) {
        return -1;
    }

    for (i = 0; i < 6; i++) {
        buff[i] = (char)p[i];
    }

    return 0;
}
#pragma clang diagnostic pop

int arpSet(const char *ifname, char *ipStr, char *mac) {
    if (ifname == nullptr || ipStr == nullptr || mac == nullptr) {
        dzlog_error("para is null.\n");
        return -1;
    }

    struct arpreq       req {};
    struct sockaddr_in *sin;
    int                 ret;
    int                 sock_fd;

    memset(&req, 0, sizeof(struct arpreq));
    sin                  = (struct sockaddr_in *)&req.arp_pa;
    sin->sin_family      = AF_INET;
    sin->sin_addr.s_addr = inet_addr(ipStr);
    //arp_dev长度为[16],注意越界
    strncpy(req.arp_dev, ifname, 15);
    req.arp_flags = ATF_PERM | ATF_COM;

    if (getHwAddr((char *)req.arp_ha.sa_data, mac) < 0) {
        dzlog_error("get mac error.\n");
        return -1;
    }

    sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock_fd < 0) {
        dzlog_error("get socket error.\n");
        return -1;
    }

    ret = ioctl(sock_fd, SIOCSARP, &req);
    if (ret < 0) {
        dzlog_error("ioctl error.\n");
        close(sock_fd);
        return -1;
    }

    close(sock_fd);
    return 0;
}

sockaddr_in get_cliAddr(char *nicif, char *tempbuff, data9 *req) {
    arpSet(nicif, IP2String(tempbuff, req->dhcpp.header.bp_yiaddr), req->chaddr);

    sockaddr_in cliAddr {};
    memcpy(&cliAddr, &req->remote, sizeof(sockaddr_in));
    cliAddr.sin_addr.s_addr = inet_addr(IP2String(tempbuff, req->dhcpp.header.bp_yiaddr));
    return cliAddr;
}

static void response_complete(void *user_data) {
    auto *req = (data19 *)user_data;
    if (req) {

        if (req->dp) {
            free(req->dp);
        }

        free(req);
    }
}

static void opendhcp_http_info(http_request *request, hw_http_response *response, void *UNUSED(user_data)) {
    hw_string status_code;
    hw_string content_type_name;
    hw_string content_type_value;
    hw_string body;
    hw_string keep_alive_name;
    hw_string keep_alive_value;

    auto *req = (data19 *)malloc(sizeof(struct data19));

    if (req == nullptr) {
        hw_http_response_send_error(response, HTTP_STATUS_500, "memory error");
        return;
    }

    if (request->method != HW_HTTP_GET) {
        hw_http_response_send_error(response, HTTP_STATUS_405, HTTP_STATUS_405);
        return;
    }

    memset(req, 0, sizeof(struct data19));

    SETSTRING(content_type_name, "Content-Type");
    SETSTRING(content_type_value, "text/html");
    hw_set_response_header(response, &content_type_name, &content_type_value);

    SETSTRING(status_code, HTTP_STATUS_200);
    prepareUserHtmlRespStatus(req);
    SETSTRING(body, req->dp);
    hw_set_body(response, &body);
    hw_set_response_status_code(response, &status_code);

    if (request->keep_alive) {
        SETSTRING(keep_alive_name, "Connection");
        SETSTRING(keep_alive_value, "close");
        hw_set_response_header(response, &keep_alive_name, &keep_alive_value);
    } else {
        hw_set_http_version(response, 1, 0);
    }

    hw_http_response_send(response, req, response_complete);
}

static void opendhcp_http_get_userinfo(http_request *request, hw_http_response *response, void *UNUSED(user_data)) {
    hw_string status_code;
    hw_string content_type_name;
    hw_string content_type_value;
    hw_string body;
    hw_string keep_alive_name;
    hw_string keep_alive_value;

    auto *req = (data19 *)malloc(sizeof(struct data19));

    if (req == nullptr) {
        hw_http_response_send_error(response, HTTP_STATUS_500, "memory error");
        return;
    }

    if (request->method != HW_HTTP_POST) {
        hw_http_response_send_error(response, HTTP_STATUS_405, HTTP_STATUS_405);
        return;
    }

    memset(req, 0, sizeof(struct data19));

    SETSTRING(content_type_name, "Content-Type");
    SETSTRING(content_type_value, "application/json");
    hw_set_response_header(response, &content_type_name, &content_type_value);

    SETSTRING(status_code, HTTP_STATUS_200);
    sendUserList(req, request->body->value);
    SETSTRING(body, req->dp);
    hw_set_body(response, &body);
    hw_set_response_status_code(response, &status_code);

    if (request->keep_alive) {
        SETSTRING(keep_alive_name, "Connection");
        SETSTRING(keep_alive_value, "close");
        hw_set_response_header(response, &keep_alive_name, &keep_alive_value);
    } else {
        hw_set_http_version(response, 1, 0);
    }

    hw_http_response_send(response, req, response_complete);
}

static void opendhcp_http_get_alluser(http_request *request, hw_http_response *response, void *UNUSED(user_data)) {
    hw_string status_code;
    hw_string content_type_name;
    hw_string content_type_value;
    hw_string body;
    hw_string keep_alive_name;
    hw_string keep_alive_value;

    auto *req = (data19 *)malloc(sizeof(struct data19));

    if (req == nullptr) {
        hw_http_response_send_error(response, HTTP_STATUS_500, "memory error");
        return;
    }

    if (request->method != HW_HTTP_GET) {
        hw_http_response_send_error(response, HTTP_STATUS_405, HTTP_STATUS_405);
        return;
    }

    memset(req, 0, sizeof(struct data19));

    SETSTRING(content_type_name, "Content-Type");
    SETSTRING(content_type_value, "text/html");
    hw_set_response_header(response, &content_type_name, &content_type_value);

    SETSTRING(status_code, HTTP_STATUS_200);
    sendAllLists(req);
    SETSTRING(body, req->dp);
    hw_set_body(response, &body);
    hw_set_response_status_code(response, &status_code);

    if (request->keep_alive) {
        SETSTRING(keep_alive_name, "Connection");
        SETSTRING(keep_alive_value, "close");
        hw_set_response_header(response, &keep_alive_name, &keep_alive_value);
    } else {
        hw_set_http_version(response, 1, 0);
    }

    hw_http_response_send(response, req, response_complete);
}

static void opendhcp_http_expand_rangeset(http_request *request, hw_http_response *response, void *UNUSED(user_data)) {
    hw_string status_code;
    hw_string content_type_name;
    hw_string content_type_value;
    hw_string body;
    hw_string keep_alive_name;
    hw_string keep_alive_value;

    auto *req = (data19 *)malloc(sizeof(struct data19));

    if (req == nullptr) {
        hw_http_response_send_error(response, HTTP_STATUS_500, "memory error");
        return;
    }

    if (request->method != HW_HTTP_POST) {
        hw_http_response_send_error(response, HTTP_STATUS_405, HTTP_STATUS_405);
        return;
    }

    memset(req, 0, sizeof(struct data19));
    SETSTRING(content_type_name, "Content-Type");
    SETSTRING(content_type_value, "text/html");
    hw_set_response_header(response, &content_type_name, &content_type_value);

    SETSTRING(status_code, HTTP_STATUS_200);
    expand_range_set(req, request->body->value);
    SETSTRING(body, req->dp);
    hw_set_body(response, &body);
    hw_set_response_status_code(response, &status_code);

    if (request->keep_alive) {
        SETSTRING(keep_alive_name, "Connection");
        SETSTRING(keep_alive_value, "close");
        hw_set_response_header(response, &keep_alive_name, &keep_alive_value);
    } else {
        hw_set_http_version(response, 1, 0);
    }

    hw_http_response_send(response, req, response_complete);
}

/**
 * 添加配置文件监听接口
 * @return
 */
int opendhcp_add_listener() {
    int    i;
    vector listen_ip = config_get_dhcp_listen_on();

    for (i = 0; listen_ip && i < vect_size(listen_ip); i++) {
        const char *pIp = (const char *)vect_get_at(listen_ip, i);
        if (pIp && strlen(pIp) > 0) {
            MYDWORD addr = inet_addr(pIp);
            addServer(cfig.specifiedServers, MAX_SERVERS, addr);
        }
    }

    return i;
}

/**
 * 增加 DHCP Server HTTP服务接口
 */
void opendhcp_init_http_server() {
    static int added = FALSE;

    if (!added) {
        hw_http_add_route("/", opendhcp_http_info, nullptr);
        hw_http_add_route("dchp/info/getuser", opendhcp_http_get_userinfo, nullptr);
        hw_http_add_route("dchp/info/allusers", opendhcp_http_get_alluser, nullptr);
        hw_http_add_route("dchp/config/rangeset", opendhcp_http_expand_rangeset, nullptr);
        added = TRUE;
    }
}