//
// Created by xajhuang on 2023/3/23.
//

#include <time.h>
#include "lease.h"
#include "user_errno.h"
#include "zlog_module.h"
#include "database.h"
#include "user_mgr.h"
#include "rfc2131.h"
#include "dhcp_network.h"

#define LEASE_DB_NAME       "lease"
#define PREALLOC_IP_TIMEOUT (60)

typedef struct {
    U32            ipAddr;
    U8             macAddr[ETH_ALEN];
    U32            bitset;
    PIPPOOL_INFO   pCtx;
    U32            timeStamp;
    UT_hash_handle hh;
} PRE_ALLOC_IP, *PPRE_ALLOC_IP;

//static PMAC_FILTER   g_allowTbl     = NULL;
//static PMAC_FILTER   g_blackListTbl = NULL;
static PPRE_ALLOC_IP g_pPreAllocIp = NULL;

#define CREATE_LEASE_TABLE()                            \
    "CREATE TABLE IF NOT EXISTS lease "                 \
    " (  id       INTEGER PRIMARY KEY AUTOINCREMENT,"   \
    "    uid      INTEGER         NOT NULL,"            \
    "    mac      CHAR(20)        NOT NULL,"            \
    "    ip       INTEGER         NOT NULL,"            \
    "    lease    INTEGER         NOT NULL,"            \
    "    createTm INTEGER         NOT NULL,"            \
    "    netmask  INTEGER,"                             \
    "    gateway  INTEGER,"                             \
    "    dns1     INTEGER,"                             \
    "    dns2     INTEGER,"                             \
    "    server   INTEGER         NOT NULL,"            \
    "    hostname CHAR(64)        DEFAULT '' NOT NULL," \
    "    keyType  INTEGER         NOT NULL"             \
    ");"                                                \
    "CREATE INDEX IF NOT EXISTS "                       \
    "lease_index ON lease (uid, mac);"

#define CREATE_PRE_ASSIGN_TABLE()                                              \
    "CREATE TABLE IF NOT EXISTS pre_assign"                                    \
    " (  id       INTEGER PRIMARY KEY AUTOINCREMENT,"                          \
    "    uid      INTEGER         NOT NULL,"                                   \
    "    xid      INTEGER         NOT NULL,"                                   \
    "    hostname CHAR(64)        DEFAULT '' NOT NULL,"                        \
    "    mac      CHAR(20)        NOT NULL,"                                   \
    "    ip       INTEGER         NOT NULL,"                                   \
    "    lease    INTEGER         NOT NULL,"                                   \
    "    netmask  INTEGER,"                                                    \
    "    gateway  INTEGER,"                                                    \
    "    dns1     INTEGER,"                                                    \
    "    dns2     INTEGER,"                                                    \
    "    createTm TIMESTAMP   DEFAULT (datetime('now', 'localtime')) NOT NULL" \
    "); CREATE INDEX IF NOT EXISTS pre_assign_index ON pre_assign(ip, uid);"

#define INSERT_PRE_ASSIGN_ROW_FMT                                                                \
    "INSERT INTO pre_assign (uid, xid, hostname, mac, ip, lease, netmask, gateway, dns1, dns2) " \
    "VALUES (%d, %d, \'%s\', \'%s\', %d, %d, %d, %d, %d, %d);"

static int lease_db_add_pre_assign(PDHCP_REQ pReq, U32 ip, PIPPOOL_INFO pPool) {
    int  rc;
    char buf[1024]  = {0};
    char macStr[20] = {0};

    MAC_TO_STR(pReq->cliMac, macStr);
    snprintf(buf,
             1024,
             INSERT_PRE_ASSIGN_ROW_FMT,
             pReq->uid,
             pReq->xid,
             pReq->hostName,
             macStr,
             ip,
             pPool->leaseTime,
             pPool->netMask,
             pPool->gwAddr,
             pPool->primeDNS,
             pPool->salveDNS);

    rc = db_sqlite3_sql_exec(CREATE_PRE_ASSIGN_TABLE(), NULL, NULL, NULL);

    if (rc != ERR_SUCCESS) {
        return rc;
    }

    DEBUG_CODE_LINE();
    return ERR_SUCCESS;
}

//int pre_alloc_dhcp_res(U32 uid, const char *pMac, U32 *pOutIp, PIPPOOL_INFO *pOutPool) {
int pre_alloc_dhcp_res(PDHCP_REQ pReq, U32 *pOutIp, PIPPOOL_INFO *pOutPool) {
    PIPPOOL_INFO  pPool, pTemp;
    PPRE_ALLOC_IP pNewIp, pTmp, pCache = NULL;
    PIPPOOL_INFO  pUserPool;

    if (pReq == NULL || pOutIp == NULL || pOutPool == NULL) {
        LOG_MOD(error, ZLOG_MOD_DHCPD, "Input params error: %p, %p\n", pOutIp, pOutPool);
        return -ERR_INPUT_PARAMS;
    }

    pUserPool = user_get_pool(pReq->uid);
    if (pUserPool == NULL) {
        LOG_MOD(error, ZLOG_MOD_DHCPD, "Can't found avaliable address pool\n");
        return -ERR_DHCP_NO_POOL;
    }

    // 遍历当前用户所有IP地址池
    HASH_ITER(hh, pUserPool, pPool, pTemp) {
        U32 addr;

        // 查看是否预分配过该设备
#if 0
        if (pMac && strlen(pMac) > 0) {
            HASH_ITER(hh, g_pPreAllocIp, pNewIp, pTmp) {
                if (strcmp(pNewIp->macAddr, pMac) == 0) {
                    *pOutIp   = pNewIp->ipAddr;
                    *pOutPool = pPool;

                    return ERR_SUCCESS;
                }
            }
        }
#endif
        while ((addr = bitset_minimum(pPool->assignPool)) > 0) {
            U32 ipAddr = (pPool->minAddr & pPool->netMask) + addr;

            // 查找租约配置文件中是否记录了曾经分配的地址, 如果已经分配则获取下一个
            // TODO: add process
            //            if (FALSE) {
            //                bitset_set(pPool->assignPool, addr);
            //                continue;
            //            }

            // 查找IP地址是否已经被预分配
            HASH_FIND_INT(g_pPreAllocIp, &ipAddr, pNewIp);

            if (pNewIp == NULL) {
                pNewIp = (PPRE_ALLOC_IP)malloc(sizeof(PRE_ALLOC_IP));

                if (!pNewIp) {
                    LOG_MOD(error, ZLOG_MOD_DHCPD, "Malloc memory error: %lu\n", sizeof(PRE_ALLOC_IP));
                    continue;
                }

                memset(pNewIp, 0, sizeof(PRE_ALLOC_IP));

                pNewIp->timeStamp = time(NULL);
                pNewIp->pCtx      = pPool;
                pNewIp->ipAddr    = ipAddr;
                pNewIp->bitset    = addr;
                memcpy(pNewIp->macAddr, pReq->cliMac, ETH_ALEN);

                //HASH_ADD_INT(g_pPreAllocIp, ipAddr, pNewIp);
                DEBUG_CODE_LINE();
                lease_db_add_pre_assign(pReq, ipAddr, pPool);

                *pOutIp   = ipAddr;
                *pOutPool = pPool;

                bitset_cls_bit(pPool->assignPool, addr);

                LOG_MOD(trace, ZLOG_MOD_DHCPD, "Select ipaddr %08X at %d of %p\n", ipAddr, addr, pPool->assignPool);
                return ERR_SUCCESS;
            } else {
                DEBUG_CODE_LINE();
                if (time(NULL) - pNewIp->timeStamp < PREALLOC_IP_TIMEOUT) {
                    continue;
                }

                *pOutIp   = pNewIp->ipAddr;
                *pOutPool = pPool;

                LOG_MOD(trace,
                        ZLOG_MOD_DHCPD,
                        "Used prepard ipaddr %08X at %d of %p\n",
                        ipAddr,
                        addr,
                        pPool->assignPool);

                return ERR_SUCCESS;
            }
        }
    }

    // 如果没有分配到IP,清理过期的预分配IP
    HASH_ITER(hh, g_pPreAllocIp, pNewIp, pTmp) {
        if (time(NULL) - pNewIp->timeStamp > PREALLOC_IP_TIMEOUT) {
            if (pCache == NULL) {
                pCache = pNewIp;
            } else {
                HASH_DEL(g_pPreAllocIp, pNewIp);
                free(pNewIp);
                bitset_cls_bit(pNewIp->pCtx->assignPool, pNewIp->bitset);
            }
        }
    }

    if (pCache) {
        *pOutIp   = pCache->ipAddr;
        *pOutPool = pPool;
        return ERR_SUCCESS;
    }

    // 没有可预分配的IP,报错
    LOG_MOD(error,
            ZLOG_MOD_DHCPD,
            "No free ipaddress in poll:  uid = %u, pool = 0x%08X\n",
            pReq->uid,
            pUserPool->poolKey);
    return -ERR_DHCP_NO_ADDR;
}

int dhcp_lease_init() {
    int rc = 0;

    rc = db_sqlite3_sql_exec(CREATE_LEASE_TABLE(), NULL, NULL, NULL);

    if (rc != ERR_SUCCESS) {
        return rc;
    }

    rc = db_sqlite3_sql_exec(CREATE_PRE_ASSIGN_TABLE(), NULL, NULL, NULL);

    if (rc != ERR_SUCCESS) {
        return rc;
    }

    return ERR_SUCCESS;
}