/*
 * Copyright (c) 2023 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "timer_database.h"
#include "time_common.h"

#include <sys/stat.h>

namespace OHOS {
namespace MiscServices {
constexpr const char *CREATE_TIME_TIMER_HOLD_ON_REBOOT = "CREATE TABLE IF NOT EXISTS hold_on_reboot "
                                                         "(timerId INTEGER PRIMARY KEY, "
                                                         "type INTEGER, "
                                                         "flag INTEGER, "
                                                         "windowLength INTEGER, "
                                                         "interval INTEGER, "
                                                         "uid INTEGER, "
                                                         "bundleName TEXT, "
                                                         "wantAgent TEXT, "
                                                         "state INTEGER, "
                                                         "triggerTime INTEGER, "
                                                         "pid INTEGER, "
                                                         "name TEXT)";

constexpr const char *CREATE_TIME_TIMER_DROP_ON_REBOOT = "CREATE TABLE IF NOT EXISTS drop_on_reboot "
                                                         "(timerId INTEGER PRIMARY KEY, "
                                                         "type INTEGER, "
                                                         "flag INTEGER, "
                                                         "windowLength INTEGER, "
                                                         "interval INTEGER, "
                                                         "uid INTEGER, "
                                                         "bundleName TEXT, "
                                                         "wantAgent TEXT, "
                                                         "state INTEGER, "
                                                         "triggerTime INTEGER, "
                                                         "pid INTEGER, "
                                                         "name TEXT)";

constexpr const char *HOLD_ON_REBOOT_ADD_PID_COLUMN = "ALTER TABLE hold_on_reboot ADD COLUMN pid INTEGER";
constexpr const char *HOLD_ON_REBOOT_ADD_NAME_COLUMN = "ALTER TABLE hold_on_reboot ADD COLUMN name TEXT";
constexpr const char *DROP_ON_REBOOT_ADD_PID_COLUMN = "ALTER TABLE drop_on_reboot ADD COLUMN pid INTEGER";
constexpr const char *DROP_ON_REBOOT_ADD_NAME_COLUMN = "ALTER TABLE drop_on_reboot ADD COLUMN name TEXT";
constexpr const char *DB_NAME = "/data/service/el1/public/database/time/time.db";
constexpr int DATABASE_OPEN_VERSION_2 = 2;
constexpr int DATABASE_OPEN_VERSION_3 = 3;
constexpr int COLUMN_INDEX_BUNDLE_NAME = 0;
constexpr int COLUMN_INDEX_TIMER_NAME = 1;
constexpr int COLUMN_INDEX_COUNT = 2;

TimeDatabase::TimeDatabase()
{
    int errCode = OHOS::NativeRdb::E_OK;
    OHOS::NativeRdb::RdbStoreConfig config(DB_NAME);
    config.SetSecurityLevel(NativeRdb::SecurityLevel::S1);
    config.SetEncryptStatus(false);
    config.SetReadConSize(1);
    TimeDBOpenCallback timeDBOpenCallback;
    store_ = OHOS::NativeRdb::RdbHelper::GetRdbStore(config, DATABASE_OPEN_VERSION_3, timeDBOpenCallback, errCode);
    if (errCode) {
        TIME_HILOGI(TIME_MODULE_SERVICE, "Get database, ret:%{public}d", errCode);
    }
    if (errCode == OHOS::NativeRdb::E_SQLITE_CORRUPT) {
        auto ret = OHOS::NativeRdb::RdbHelper::DeleteRdbStore(config);
        if (ret != OHOS::NativeRdb::E_OK) {
            TIME_HILOGE(TIME_MODULE_SERVICE, "delete corrupt database failed, ret:%{public}d", ret);
            return;
        }
        store_ = OHOS::NativeRdb::RdbHelper::GetRdbStore(config, DATABASE_OPEN_VERSION_3, timeDBOpenCallback, errCode);
    }
}

TimeDatabase &TimeDatabase::GetInstance()
{
    static TimeDatabase timeDatabase;
    return timeDatabase;
}

bool TimeDatabase::RecoverDataBase()
{
    std::lock_guard<std::mutex> lock(storeMutex_);
    OHOS::NativeRdb::RdbStoreConfig config(DB_NAME);
    config.SetSecurityLevel(NativeRdb::SecurityLevel::S1);
    config.SetEncryptStatus(false);
    config.SetReadConSize(1);
    auto ret = OHOS::NativeRdb::RdbHelper::DeleteRdbStore(config);
    if (ret != OHOS::NativeRdb::E_OK) {
        TIME_HILOGE(TIME_MODULE_SERVICE, "delete corrupt database failed, ret %{public}d", ret);
        return false;
    }
    TimeDBOpenCallback timeDbOpenCallback;
    int errCode;
    store_ = OHOS::NativeRdb::RdbHelper::GetRdbStore(config, DATABASE_OPEN_VERSION_3, timeDbOpenCallback, errCode);
    if (store_ == nullptr) {
        return false;
    }
    return true;
}

std::shared_ptr<OHOS::NativeRdb::RdbStore> TimeDatabase::GetStore()
{
    std::lock_guard<std::mutex> lock(storeMutex_);
    return store_;
}

int GetInt(std::shared_ptr<OHOS::NativeRdb::ResultSet> resultSet, int line)
{
    int value = 0;
    resultSet->GetInt(line, value);
    return value;
}

int64_t GetLong(std::shared_ptr<OHOS::NativeRdb::ResultSet> resultSet, int line)
{
    int64_t value = 0;
    resultSet->GetLong(line, value);
    return value;
}

std::string GetString(std::shared_ptr<OHOS::NativeRdb::ResultSet> resultSet, int line)
{
    std::string value = "";
    resultSet->GetString(line, value);
    return value;
}

bool TimeDatabase::Insert(const std::string &table, const OHOS::NativeRdb::ValuesBucket &insertValues)
{
    auto store = GetStore();
    if (store == nullptr) {
        TIME_HILOGE(TIME_MODULE_SERVICE, "store_ is nullptr");
        return false;
    }

    int64_t outRowId = 0;
    auto ret = store->Insert(outRowId, table, insertValues);
    if (ret != OHOS::NativeRdb::E_OK) {
        TIME_HILOGE(TIME_MODULE_SERVICE, "insert values failed, ret:%{public}d", ret);
        if (ret != OHOS::NativeRdb::E_SQLITE_CORRUPT) {
            return false;
        }
        if (!RecoverDataBase()) {
            return false;
        }
        store = GetStore();
        if (store == nullptr) {
            TIME_HILOGE(TIME_MODULE_SERVICE, "store_ is nullptr");
            return false;
        }
        ret = store->Insert(outRowId, table, insertValues);
        if (ret != OHOS::NativeRdb::E_OK) {
            TIME_HILOGE(TIME_MODULE_SERVICE, "Insert values after RecoverDataBase failed, ret:%{public}d", ret);
        }
    }
    return true;
}

bool TimeDatabase::Update(
    const OHOS::NativeRdb::ValuesBucket values, const OHOS::NativeRdb::AbsRdbPredicates &predicates)
{
    auto store = GetStore();
    if (store == nullptr) {
        TIME_HILOGE(TIME_MODULE_SERVICE, "store_ is nullptr");
        return false;
    }

    int changedRows = 0;
    auto ret = store->Update(changedRows, values, predicates);
    if (ret != OHOS::NativeRdb::E_OK) {
        TIME_HILOGE(TIME_MODULE_SERVICE, "update values failed, ret:%{public}d", ret);
        if (ret != OHOS::NativeRdb::E_SQLITE_CORRUPT) {
            return false;
        }
        if (!RecoverDataBase()) {
            return false;
        }
        store = GetStore();
        if (store == nullptr) {
            TIME_HILOGE(TIME_MODULE_SERVICE, "store_ is nullptr");
            return false;
        }
        ret = store->Update(changedRows, values, predicates);
        if (ret != OHOS::NativeRdb::E_OK) {
            TIME_HILOGE(TIME_MODULE_SERVICE, "Update values after RecoverDataBase failed, ret:%{public}d", ret);
        }
    }
    return true;
}

std::shared_ptr<OHOS::NativeRdb::ResultSet> TimeDatabase::Query(
    const OHOS::NativeRdb::AbsRdbPredicates &predicates, const std::vector<std::string> &columns)
{
    auto store = GetStore();
    if (store == nullptr) {
        TIME_HILOGE(TIME_MODULE_SERVICE, "store_ is nullptr");
        return nullptr;
    }
    auto result = store->Query(predicates, columns);
    if (result == nullptr) {
        TIME_HILOGE(TIME_MODULE_SERVICE, "result is nullptr");
        return nullptr;
    }
    int32_t count = 0;
    if (result->GetRowCount(count) == OHOS::NativeRdb::E_SQLITE_CORRUPT) {
        RecoverDataBase();
        result->Close();
        return nullptr;
    }
    return result;
}

bool TimeDatabase::Delete(const OHOS::NativeRdb::AbsRdbPredicates &predicates)
{
    auto store = GetStore();
    if (store == nullptr) {
        TIME_HILOGE(TIME_MODULE_SERVICE, "store_ is nullptr");
        return false;
    }

    int deletedRows = 0;
    auto ret = store->Delete(deletedRows, predicates);
    if (ret != OHOS::NativeRdb::E_OK) {
        TIME_HILOGE(TIME_MODULE_SERVICE, "delete values failed, ret:%{public}d", ret);
        if (ret != OHOS::NativeRdb::E_SQLITE_CORRUPT) {
            return false;
        }
        if (!RecoverDataBase()) {
            return false;
        }
        store = GetStore();
        if (store == nullptr) {
            TIME_HILOGE(TIME_MODULE_SERVICE, "store_ is nullptr");
            return false;
        }
        ret = store->Delete(deletedRows, predicates);
        if (ret != OHOS::NativeRdb::E_OK) {
            TIME_HILOGE(TIME_MODULE_SERVICE, "Delete values after RecoverDataBase failed, ret:%{public}d", ret);
        }
    }
    return true;
}

std::shared_ptr<OHOS::NativeRdb::ResultSet> TimeDatabase::QuerySql(const std::string &sql)
{
    auto store = GetStore();
    if (store == nullptr) {
        TIME_HILOGE(TIME_MODULE_SERVICE, "store_ is nullptr");
        return nullptr;
    }
    auto result = store->QuerySql(sql, std::vector<OHOS::NativeRdb::ValueObject>());
    if (result == nullptr) {
        TIME_HILOGE(TIME_MODULE_SERVICE, "result is nullptr");
        return nullptr;
    }
    int32_t count = 0;
    if (result->GetRowCount(count) == OHOS::NativeRdb::E_SQLITE_CORRUPT) {
        RecoverDataBase();
        result->Close();
        return nullptr;
    }
    return result;
}

void TimeDatabase::ClearDropOnReboot()
{
    TIME_HILOGI(TIME_MODULE_SERVICE, "Clears drop_on_reboot table");
    auto store = GetStore();
    if (store == nullptr) {
        TIME_HILOGE(TIME_MODULE_SERVICE, "store_ is nullptr");
        return;
    }
    auto ret = store->ExecuteSql("DELETE FROM drop_on_reboot");
    if (ret != OHOS::NativeRdb::E_OK) {
        TIME_HILOGE(TIME_MODULE_SERVICE, "Clears drop_on_reboot table failed");
        if (ret != OHOS::NativeRdb::E_SQLITE_CORRUPT) {
            return;
        }
        if (!RecoverDataBase()) {
            return;
        }
        store = GetStore();
        if (store == nullptr) {
            TIME_HILOGE(TIME_MODULE_SERVICE, "store_ is nullptr");
            return;
        }
        ret = store->ExecuteSql("DELETE FROM drop_on_reboot");
        if (ret != OHOS::NativeRdb::E_OK) {
            TIME_HILOGE(TIME_MODULE_SERVICE, "Clears after RecoverDataBase failed, ret:%{public}d", ret);
        }
    }
}

void TimeDatabase::ClearInvaildDataInHoldOnReboot()
{
    TIME_HILOGI(TIME_MODULE_SERVICE, "Clears hold_on_reboot table");
    auto store = GetStore();
    if (store == nullptr) {
        TIME_HILOGE(TIME_MODULE_SERVICE, "store_ is nullptr");
        return;
    }
    auto ret = store->ExecuteSql("DELETE FROM hold_on_reboot WHERE state = 0 OR type = 2 OR type = 3");
    if (ret != OHOS::NativeRdb::E_OK) {
        TIME_HILOGE(TIME_MODULE_SERVICE, "Clears hold_on_reboot table failed");
        if (ret != OHOS::NativeRdb::E_SQLITE_CORRUPT) {
            return;
        }
        if (!RecoverDataBase()) {
            return;
        }
        store = GetStore();
        if (store == nullptr) {
            TIME_HILOGE(TIME_MODULE_SERVICE, "store_ is nullptr");
            return;
        }
        ret = store->ExecuteSql("DELETE FROM hold_on_reboot WHERE state = 0 OR type = 2 OR type = 3");
        if (ret != OHOS::NativeRdb::E_OK) {
            TIME_HILOGE(TIME_MODULE_SERVICE, "Clears after RecoverDataBase failed, ret:%{public}d", ret);
        }
    }
}

TimerDbSizeInfo TimeDatabase::GetDatabaseSizeDetail()
{
    TimerDbSizeInfo sizeInfo;
    struct stat st;
    if (stat(DB_NAME, &st) == 0) {
        sizeInfo.dbSize = st.st_size;
    }
    if (stat((std::string(DB_NAME) + "-shm").c_str(), &st) == 0) {
        sizeInfo.shmSize = st.st_size;
    }
    if (stat((std::string(DB_NAME) + "-wal").c_str(), &st) == 0) {
        sizeInfo.walSize = st.st_size;
    }
    return sizeInfo;
}

int32_t TimeDatabase::GetTotalRecordCount()
{
    int32_t totalCount = 0;
    auto result = QuerySql(
        "SELECT SUM(cnt) FROM ("
        "  SELECT COUNT(*) AS cnt FROM hold_on_reboot"
        "  UNION ALL"
        "  SELECT COUNT(*) AS cnt FROM drop_on_reboot"
        ")");
    if (result != nullptr && result->GoToFirstRow() == OHOS::NativeRdb::E_OK) {
        int32_t ret = result->GetInt(0, totalCount);
        if (ret != OHOS::NativeRdb::E_OK) {
            TIME_HILOGE(TIME_MODULE_SERVICE, "GetTotalRecordCount failed, ret:%{public}d", ret);
            totalCount = 0;
        }
        result->Close();
    }
    return totalCount;
}

std::vector<TimerDbTopAppInfo> TimeDatabase::GetTopApps(int topN)
{
    std::vector<TimerDbTopAppInfo> result;
    if (topN <= 0) {
        TIME_HILOGW(TIME_MODULE_SERVICE, "GetTopApps invalid topN:%{public}d", topN);
        return result;
    }
    std::string sql =
        "SELECT bundleName, name, SUM(cnt) as total FROM ("
        "  SELECT bundleName, name, COUNT(*) as cnt FROM hold_on_reboot GROUP BY bundleName, name"
        "  UNION ALL"
        "  SELECT bundleName, name, COUNT(*) as cnt FROM drop_on_reboot GROUP BY bundleName, name"
        ") GROUP BY bundleName, name ORDER BY total DESC LIMIT " + std::to_string(topN);
    auto queryResult = QuerySql(sql);
    if (queryResult != nullptr) {
        if (queryResult->GoToFirstRow() == OHOS::NativeRdb::E_OK) {
            do {
                TimerDbTopAppInfo info;
                info.bundleName = GetString(queryResult, COLUMN_INDEX_BUNDLE_NAME);
                info.timerName = GetString(queryResult, COLUMN_INDEX_TIMER_NAME);
                info.count = GetInt(queryResult, COLUMN_INDEX_COUNT);
                result.push_back(info);
            } while (queryResult->GoToNextRow() == OHOS::NativeRdb::E_OK);
        }
        queryResult->Close();
    }
    return result;
}

int TimeDBCreateTables(OHOS::NativeRdb::RdbStore &store)
{
    TIME_HILOGI(TIME_MODULE_SERVICE, "Creates hold_on_reboot table");
    // Creates hold_on_reboot table.
    int ret = store.ExecuteSql(CREATE_TIME_TIMER_HOLD_ON_REBOOT);
    if (ret != OHOS::NativeRdb::E_OK) {
        TIME_HILOGE(TIME_MODULE_SERVICE, "Creates hold_on_reboot table failed, ret:%{public}d", ret);
        return ret;
    }

    TIME_HILOGI(TIME_MODULE_SERVICE, "Creates drop_on_reboot table");
    // Creates drop_on_reboot table.
    ret = store.ExecuteSql(CREATE_TIME_TIMER_DROP_ON_REBOOT);
    if (ret != OHOS::NativeRdb::E_OK) {
        TIME_HILOGE(TIME_MODULE_SERVICE, "Creates drop_on_reboot table failed, ret:%{public}d", ret);
        return ret;
    }
    return ret;
}

int TimeDBOpenCallback::OnCreate(OHOS::NativeRdb::RdbStore &store)
{
    TIME_HILOGI(TIME_MODULE_SERVICE, "OnCreate");
    auto initRet = TimeDBCreateTables(store);
    if (initRet != OHOS::NativeRdb::E_OK) {
        TIME_HILOGE(TIME_MODULE_SERVICE, "Init database failed:%{public}d", initRet);
        return initRet;
    }
    return OHOS::NativeRdb::E_OK;
}

int TimeDBOpenCallback::OnOpen(OHOS::NativeRdb::RdbStore &store)
{
    return OHOS::NativeRdb::E_OK;
}

int TimeDBOpenCallback::OnUpgrade(OHOS::NativeRdb::RdbStore &store, int oldVersion, int newVersion)
{
    if (oldVersion < DATABASE_OPEN_VERSION_2 && newVersion >= DATABASE_OPEN_VERSION_2) {
        int ret = store.ExecuteSql(HOLD_ON_REBOOT_ADD_PID_COLUMN);
        if (ret != OHOS::NativeRdb::E_OK) {
            TIME_HILOGE(TIME_MODULE_SERVICE, "hold_on_reboot add column failed, ret:%{public}d", ret);
            return ret;
        }
        ret = store.ExecuteSql(DROP_ON_REBOOT_ADD_PID_COLUMN);
        if (ret != OHOS::NativeRdb::E_OK) {
            TIME_HILOGE(TIME_MODULE_SERVICE, "drop_on_reboot add column failed, ret:%{public}d", ret);
            return ret;
        }
    }
    if (oldVersion < DATABASE_OPEN_VERSION_3 && newVersion >= DATABASE_OPEN_VERSION_3) {
        int ret = store.ExecuteSql(HOLD_ON_REBOOT_ADD_NAME_COLUMN);
        if (ret != OHOS::NativeRdb::E_OK) {
            TIME_HILOGE(TIME_MODULE_SERVICE, "hold_on_reboot add column failed, ret:%{public}d", ret);
            return ret;
        }
        ret = store.ExecuteSql(DROP_ON_REBOOT_ADD_NAME_COLUMN);
        if (ret != OHOS::NativeRdb::E_OK) {
            TIME_HILOGE(TIME_MODULE_SERVICE, "drop_on_reboot add column failed, ret:%{public}d", ret);
            return ret;
        }
    }
    return OHOS::NativeRdb::E_OK;
}

int TimeDBOpenCallback::OnDowngrade(OHOS::NativeRdb::RdbStore &store, int oldVersion, int newVersion)
{
    return OHOS::NativeRdb::E_OK;
}
}
}