#include "opt_log.h"
#include "../opcua/ua-server.h"
#include <sys/statvfs.h>
#include <sys/sysinfo.h>
#include "../cmdclient.h"
#include "collector-api.h"
#include "dy_utils/led.h"
#include <openssl/md5.h>
#include "define.h"
#include "../ems/utils.h"
#include <math.h>
#include <libgen.h>
#include <string.h>
#include <time.h>
#include <stdio.h>
#include "../mqtt_emms2/mqtt_emms2.h"

typedef struct opt_log_it_s
{
    list_head_t list;
    time_t time;   // 操作时间
    char *who;     // 操作者
    char *opt;     // 操作
    char *optdev;  // 被操作设备
    char *what;    // 被操作对象(点位)
    char *content; // 内容（value）
} opt_log_it_s;

static opt_log_it_s *new_opt_log_it(const char *who, const char *opt, const char *optdev,const char *what, const char *content);
static int delete_opt_log_it(opt_log_it_s *this_it);
static opt_log_it_s *opt_log_wait(opt_log_s *this_p, uint32_t timeout_ms);
static void *opt_log_thread(void *par);

static int opt_log_open_db(opt_log_s *this_p);
static void opt_log_close_db(opt_log_s *this_p);

static char *safe_strdup(const char *s)
{
    if (!s) return strdup("");
    return strdup(s);
}

opt_log_s *new_opt_log(char *name)
{
    opt_log_s *this_p = (opt_log_s *)calloc(1, sizeof(opt_log_s));
    if (this_p == NULL)
    {
        return NULL;
    }
    if (sem_init(&this_p->sem, 0, 0) < 0)
    {
        free(this_p);
        return NULL;
    }
    INIT_LIST_HEAD(&this_p->list);
    pthread_mutex_init(&this_p->mutex, NULL);
    pthread_mutex_init(&this_p->db_mutex, NULL);
    snprintf(this_p->name, sizeof(this_p->name), "%s", name ? name : "");
    this_p->time = 0;

    this_p->db = NULL;
    this_p->insert_stmt = NULL;
    snprintf(this_p->db_path, sizeof(this_p->db_path), "/mnt/ssd/%s.db", this_p->name);

    return this_p;
}

int delete_opt_log(opt_log_s *this_p)
{
    if (!this_p) return 0;

    pthread_mutex_lock(&this_p->mutex);
    while (!list_empty(&this_p->list))
    {
        opt_log_it_s *it = list_first_entry(&this_p->list, opt_log_it_s, list);
        list_del(&it->list);
        delete_opt_log_it(it);
    }
    pthread_mutex_unlock(&this_p->mutex);

    opt_log_close_db(this_p);

    pthread_mutex_destroy(&this_p->mutex);
    pthread_mutex_destroy(&this_p->db_mutex);
    sem_destroy(&this_p->sem);
    free(this_p);
    return 0;
}

int opt_log_start(opt_log_s *this_p)
{
    if (!this_p) return -1;
    pthread_t thread_id;
    if (0 == pthread_create(&thread_id, NULL, opt_log_thread, this_p))
    {
        pthread_setname_np(thread_id, this_p->name);
        pthread_detach(thread_id);
    }
    else
    {
        return -1;
    }
    return 0;
}

int opt_log_add(opt_log_s *this_p, const char *who, const char *opt,const char *optdev, const char *what, const char *content)
{
    if (!this_p) return -1;

    opt_log_it_s *it = new_opt_log_it(who, opt,optdev ,what, content);
    if (it == NULL)
    {
        return -2;
    }

    pthread_mutex_lock(&this_p->mutex);
    list_add_tail(&it->list, &this_p->list);
    pthread_mutex_unlock(&this_p->mutex);

    sem_post(&this_p->sem);
    return 0;
}

static int delete_opt_log_it(opt_log_it_s *this_it)
{
    if (!this_it) return 0;
    if (this_it->content) free(this_it->content);
    if (this_it->opt) free(this_it->opt);
    if (this_it->who) free(this_it->who);
    if (this_it->what) free(this_it->what);
    free(this_it);
    return 0;
}

static opt_log_it_s *new_opt_log_it(const char *who, const char *opt, const char *optdev,const char *what, const char *content)
{
    opt_log_it_s *this_it = (opt_log_it_s *)calloc(1, sizeof(opt_log_it_s));
    if (this_it == NULL)
    {
        return NULL;
    }
    this_it->time = time(NULL);
    this_it->content = safe_strdup(content);
    this_it->opt = safe_strdup(opt);
    this_it->optdev = safe_strdup(optdev);
    this_it->who = safe_strdup(who);
    this_it->what = safe_strdup(what);

    if (this_it->content == NULL || this_it->opt == NULL || this_it->who == NULL || this_it->what == NULL)
    {
        delete_opt_log_it(this_it);
        return NULL;
    }
    return this_it;
}

static opt_log_it_s *opt_log_wait(opt_log_s *this_p, uint32_t timeout_ms)
{
    struct timespec now;
    struct timespec wt;
    opt_log_it_s *it = NULL;
    int s_value = 0;

    sem_getvalue(&this_p->sem, &s_value);
    ems_syslog(LOG_NOTICE, "opt_log_wait, cur sem value:%d", s_value);

    clock_gettime(CLOCK_REALTIME, &now);
    wt.tv_sec = now.tv_sec + timeout_ms / 1000;
    wt.tv_nsec = now.tv_nsec + (timeout_ms % 1000) * 1000 * 1000;
    if (wt.tv_nsec >= 1000000000L)
    {
        wt.tv_nsec -= 1000000000L;
        wt.tv_sec += 1;
    }
    if (0 == sem_timedwait(&this_p->sem, &wt))
    {
        pthread_mutex_lock(&this_p->mutex);
        if (!list_empty(&this_p->list))
        {
            it = list_first_entry(&this_p->list, opt_log_it_s, list);
            list_del(&it->list);
        }
        pthread_mutex_unlock(&this_p->mutex);
    }
    return it;
}

static int opt_log_open_db(opt_log_s *this_p)
{
    pthread_mutex_lock(&this_p->db_mutex);
    if (this_p->db) 
    {
        pthread_mutex_unlock(&this_p->db_mutex);
        return 0;
    }
    int rc = sqlite3_open(this_p->db_path, &this_p->db);
    if (rc != SQLITE_OK)
    {
        ems_syslog(LOG_ERR, "sqlite3_open(%s) failed: %s", this_p->db_path, sqlite3_errmsg(this_p->db));
        if (this_p->db)
        {
            sqlite3_close(this_p->db);
            this_p->db = NULL;
        }
        pthread_mutex_unlock(&this_p->db_mutex);
        return -1;
    }

    // 性能/安全开关（PRAGMA journal_mode=WAL）但是会多出来两个辅助文件）
    char *errmsg = NULL;
    sqlite3_exec(this_p->db, "PRAGMA journal_mode=DELETE;", NULL, NULL, NULL);
    //sqlite3_exec(this_p->db, "PRAGMA synchronous=NORMAL;", NULL, NULL, NULL);

    const char *create_sql =
    "CREATE TABLE IF NOT EXISTS opt_log ("
    "  id INTEGER PRIMARY KEY AUTOINCREMENT,"
    "  time TEXT NOT NULL,"      // YYYY-MM-DD HH:MM:SS
    "  who TEXT,"
    "  optdev TEXT,"
    "  opt TEXT,"
    "  what TEXT,"
    "  content TEXT"
    ");"
    "CREATE INDEX IF NOT EXISTS idx_opt_log_time ON opt_log(time);";

    int rc2 = sqlite3_exec(this_p->db, create_sql, NULL, NULL, &errmsg);
    if (rc2 != SQLITE_OK)
    {
        ems_syslog(LOG_ERR, "sqlite3_exec(create table/index) failed: %s", errmsg ? errmsg : "unknown");
        if (errmsg) sqlite3_free(errmsg);
        opt_log_close_db(this_p);
        pthread_mutex_unlock(&this_p->db_mutex);
        return -2;
    }

    const char *insert_sql = "INSERT INTO opt_log (time, who, optdev, opt, what, content) VALUES (?,?,?,?,?,?);";
    rc2 = sqlite3_prepare_v2(this_p->db, insert_sql, -1, &this_p->insert_stmt, NULL);
    if (rc2 != SQLITE_OK)
    {
        ems_syslog(LOG_ERR, "sqlite3_prepare_v2(insert) failed: %s", sqlite3_errmsg(this_p->db));
        opt_log_close_db(this_p);
        pthread_mutex_unlock(&this_p->db_mutex);
        return -3;
    }

    pthread_mutex_unlock(&this_p->db_mutex);
    return 0;
}

static void opt_log_close_db(opt_log_s *this_p)
{
    pthread_mutex_lock(&this_p->db_mutex);
    if (this_p->insert_stmt)
    {
        sqlite3_finalize(this_p->insert_stmt);
        this_p->insert_stmt = NULL;
    }
    if (this_p->db)
    {
        sqlite3_close(this_p->db);
        this_p->db = NULL;
    }
    pthread_mutex_unlock(&this_p->db_mutex);
}

static void opt_log_cleanup_db(opt_log_s *this_p, int keep_count)
{
    if (!this_p->db) return;

    const char *count_sql = "SELECT COUNT(*) FROM opt_log;";
    sqlite3_stmt *count_stmt = NULL;
    int rc_count = sqlite3_prepare_v2(this_p->db, count_sql, -1, &count_stmt, NULL);
    int total_count = 0;
    if (rc_count == SQLITE_OK) {
        if (sqlite3_step(count_stmt) == SQLITE_ROW) {
            total_count = sqlite3_column_int(count_stmt, 0);
        }
    }
    sqlite3_finalize(count_stmt);

    if (total_count > keep_count) {
        int del_num = total_count - keep_count;
        char del_sql[128];
        snprintf(del_sql, sizeof(del_sql),
            "DELETE FROM opt_log WHERE id IN (SELECT id FROM opt_log ORDER BY id ASC LIMIT %d);",
            del_num);
        char *errmsg = NULL;
        sqlite3_exec(this_p->db, del_sql, NULL, NULL, &errmsg);
        if (errmsg) {
            ems_syslog(LOG_ERR, "delete old log failed: %s", errmsg);
            sqlite3_free(errmsg);
        }
    }
}


static void *opt_log_thread(void *par)
{
    opt_log_s *this_p = (opt_log_s *)par;
    opt_log_it_s *it = NULL;
    struct tm target_time = {};

    while (1)
    {
        it = opt_log_wait(this_p, 1000);
        if (it != NULL)
        {
            this_p->time = 0;

            if (opt_log_open_db(this_p) != 0)
            {
                ems_syslog(LOG_ERR, "open db failed, drop one log");
                delete_opt_log_it(it);
                continue;
            }

            char timebuf[20] = {0};
            localtime_r(&it->time, &target_time);
            strftime(timebuf, sizeof(timebuf), "%Y-%m-%d %H:%M:%S", &target_time);

            // 绑定并写入
            if(this_p->insert_stmt != NULL)
            {
                sqlite3_reset(this_p->insert_stmt);
                sqlite3_clear_bindings(this_p->insert_stmt);
            }

            // 1: time, 2: who, 3: opt, 4: optdev, 5: what, 6: content
            int rc = 0;
            rc |= sqlite3_bind_text(this_p->insert_stmt, 1, timebuf, -1, SQLITE_TRANSIENT);
            rc |= sqlite3_bind_text(this_p->insert_stmt, 2, it->who ? it->who : "", -1, SQLITE_TRANSIENT);
            rc |= sqlite3_bind_text(this_p->insert_stmt, 3, it->optdev ? it->optdev : "", -1, SQLITE_TRANSIENT);
            rc |= sqlite3_bind_text(this_p->insert_stmt, 4, it->opt ? it->opt : "", -1, SQLITE_TRANSIENT);
            rc |= sqlite3_bind_text(this_p->insert_stmt, 5, it->what ? it->what : "", -1, SQLITE_TRANSIENT);
            rc |= sqlite3_bind_text(this_p->insert_stmt, 6, it->content ? it->content : "", -1, SQLITE_TRANSIENT);

            if (rc != SQLITE_OK)
            {
                ems_syslog(LOG_ERR, "sqlite3_bind_text failed");
            }
            else
            {
                rc = sqlite3_step(this_p->insert_stmt);
                if (rc != SQLITE_DONE)
                {
                    ems_syslog(LOG_ERR, "sqlite3_step(insert) failed: %s", sqlite3_errmsg(this_p->db));
                }
            }

            delete_opt_log_it(it);
            continue;
        }

        // 空闲计时，到 60 秒关闭数据库句柄
        this_p->time++;
        if (this_p->time >= 60)
        {
            opt_log_cleanup_db(this_p, OPT_LOG_TABLE_MAX_ITEM);

            opt_log_close_db(this_p);
            this_p->time = 0;
        }
    }
    return NULL;
}

int get_opt_log_count()
{
    int count = 0;
    sqlite3_stmt* stmt;
    opt_log_s *opt_log = ((proto_forward_t*)get_pcs_ctrl_var()->device_layer_ptr)->opt_log;
    if (opt_log == NULL)
    {
        ems_syslog(LOG_ERR, "opt_log is NULL");
        return -1;
    }

    if (opt_log_open_db(opt_log) != 0)
    {
        ems_syslog(LOG_ERR, "Failed to open database");
        return -1;
    }

    int ret = sqlite3_prepare_v2(opt_log->db, OPT_LOG_NUM_GET, -1, &stmt, NULL);
    if (ret != SQLITE_OK)
    {
        ems_syslog(LOG_ERR, "prepare sql failed: %s", sqlite3_errmsg(opt_log->db));
        return -1;
    }

    if (sqlite3_step(stmt) == SQLITE_ROW)
    {
        count = sqlite3_column_int(stmt, 0);
    }

    sqlite3_finalize(stmt);
    return count;
}


int need_display_content(const char *who ,const char *what)
{
    if ((strcmp(who, "mqtt_emms2") == 0 || strcmp(who, "mqtt_stactrl") == 0))
    {
        return 0;
    }
    return 1;
}

struct opt_log_buf* get_opt_log_data(int jump, int num)
{
    struct opt_log_buf* result_info = (struct opt_log_buf*)calloc(1, sizeof(struct opt_log_buf));
    if (result_info == NULL)
    {
        ems_syslog(LOG_ERR, "Failure to allocate memory!");
        return NULL;
    }

    int count = get_opt_log_count();
    if (count < 0)
    {
        ems_syslog(LOG_ERR, "Failure to get the total number of entries returns -1");
        count = 0;
    }
    result_info->num = count;

    lbuff_init(&result_info->buff, 1024);
    lbuff_sprintf(&result_info->buff, "{\"total\":%d,\"result\":[", result_info->num);

    char sql[512] = {0};
    snprintf(sql, sizeof(sql), OPT_LOG_GET_TO_UA, num, jump * num);

    opt_log_s *opt_log = ((proto_forward_t*)get_pcs_ctrl_var()->device_layer_ptr)->opt_log;
    if (opt_log == NULL)
    {
        ems_syslog(LOG_ERR, "opt_log is NULL");
        lbuff_sprintf(&result_info->buff, "]}");
        return result_info;
    }

    if (opt_log_open_db(opt_log) != 0)
    {
        ems_syslog(LOG_ERR, "Failed to open database");
        lbuff_sprintf(&result_info->buff, "]}");
        return result_info;
    }

    sqlite3_stmt* stmt;
    int ret = sqlite3_prepare_v2(opt_log->db, sql, -1, &stmt, NULL);
    if (ret != SQLITE_OK)
    {
        ems_syslog(LOG_ERR, "prepare sql failed: %s", sqlite3_errmsg(opt_log->db));
        goto getOptLog_END;
    }

    while ((ret = sqlite3_step(stmt)) == SQLITE_ROW)
    {
        const char* time = (const char*)sqlite3_column_text(stmt, 1);
        const char* who  = (const char*)sqlite3_column_text(stmt, 2);
        const char* opt = (const char*)sqlite3_column_text(stmt, 3);
        const char* optdev  = (const char*)sqlite3_column_text(stmt, 4);
        const char* what = (const char*)sqlite3_column_text(stmt, 5);
        const char* content = (const char*)sqlite3_column_text(stmt, 6);

        if (need_display_content(who, what) == 0)
        {
            content = "";
        }

        lbuff_sprintf(&result_info->buff,
            "{\"Time\":\"%s\",\"Who\":\"%s\",\"Opt\":\"%s\",\"OptDev\":\"%s\",\"What\":\"%s\",\"Content\":\"%s\"},",
            time ? time : "",
            who ? who : "",
            opt ? opt : "",
            optdev ? optdev : "",
            what ? what : "",
            content ? content : ""
        );
    }

    size_t len = strlen(result_info->buff.bufptr);
    if (len > 0 && result_info->buff.bufptr[len - 1] == ',')
    {
        result_info->buff.bufptr[len - 1] = '\0';
        result_info->buff.curlen -= 1;
    }

getOptLog_END:
    lbuff_sprintf(&result_info->buff, "]}");
    sqlite3_finalize(stmt);
    return result_info;
}

