#include "Config.h"

#include <QDebug>
#include <QDir>
#include <QFile>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonValue>
#include <QtGlobal>

#include <QStandardPaths>

#include <dracal/common/log.hpp>

bool createDefaultConfigIfMissing() {
#if defined(Q_OS_WIN)
    QString roamingPath = QDir::fromNativeSeparators(qgetenv("APPDATA"));
#else
    QString roamingPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
#endif
    QString dracalPath = roamingPath + "/Dracal";
    QString configFilePath = dracalPath + "/dracal-view-config.json";

    QDir dracalDir(dracalPath);
    if (!dracalDir.exists()) {
        dracalDir.mkpath(".");
    }

    QFile configFile(configFilePath);
    if (configFile.exists()) {
        dracal_info("default config file already exists at: {}", configFilePath.toStdString());
        return true;
    }

    QJsonObject defaultConfig;
    defaultConfig["local_tenkinet_port"] = "10395";
    defaultConfig["dracal_usb_service_port"] = "11395";
    defaultConfig["enable_trace_logs"] = false;
    defaultConfig["enable_debug_logs"] = false;
    defaultConfig["prevent_sleep_during_logging"] = true;
    defaultConfig["show_all_hidden_channels"] = false;
    QJsonDocument doc(defaultConfig);

    if (!configFile.open(QIODevice::WriteOnly)) {
        dracal_warn("failed to create default config file already exists at: {}", configFilePath.toStdString());
        return false;
    }

    configFile.write(doc.toJson());
    configFile.close();
    dracal_info("created default config file already exists at: {}", configFilePath.toStdString());
    return true;
}

QString loadTenkiNetPort() {
    static const auto port = []() -> QString {
        const QString default_port = "10395";

        if (!createDefaultConfigIfMissing()) {
            return default_port;
        }

#if defined(Q_OS_WIN)
        QString roamingPath = QDir::fromNativeSeparators(qgetenv("APPDATA"));
#else
        QString roamingPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
#endif
        QString dracalPath = roamingPath + "/Dracal";
        QString configFilePath = dracalPath + "/dracal-view-config.json";

        QFile configFile(configFilePath);
        if (!configFile.open(QIODevice::ReadOnly)) {
            dracal_warn("Failed to read config file: {}", configFilePath.toStdString());
            return default_port;
        }

        QByteArray data = configFile.readAll();
        configFile.close();

        QJsonDocument doc = QJsonDocument::fromJson(data);
        if (doc.isNull() || !doc.isObject()) {
            dracal_warn("Invalid JSON in config file");
            return default_port;
        }

        QJsonObject config = doc.object();
        if (!config.contains("local_tenkinet_port")) {
            dracal_warn("local_tenkinet_port not found in config file");
            return default_port;
        }

        QJsonValue portValue = config["local_tenkinet_port"];
        if (!portValue.isString()) {
            dracal_warn("local_tenkinet_port is not a string in config file");
            return default_port;
        }

        const auto port = portValue.toString();
        dracal_info("Loaded local tenkinet port: {}", port.toStdString());
        return port;
    }();

    return port;
}

QString loadDracalUsbServicePort() {
    static const auto port = []() -> QString {
        const QString default_port = "11395";

        if (!createDefaultConfigIfMissing()) {
            return default_port;
        }

#if defined(Q_OS_WIN)
        QString roamingPath = QDir::fromNativeSeparators(qgetenv("APPDATA"));
#else
        QString roamingPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
#endif
        QString dracalPath = roamingPath + "/Dracal";
        QString configFilePath = dracalPath + "/dracal-view-config.json";

        QFile configFile(configFilePath);
        if (!configFile.open(QIODevice::ReadOnly)) {
            dracal_warn("Failed to read config file: {}", configFilePath.toStdString());
            return default_port;
        }

        QByteArray data = configFile.readAll();
        configFile.close();

        QJsonDocument doc = QJsonDocument::fromJson(data);
        if (doc.isNull() || !doc.isObject()) {
            dracal_warn("Invalid JSON in config file");
            return default_port;
        }

        QJsonObject config = doc.object();
        if (!config.contains("dracal_usb_service_port")) {
            dracal_warn("dracal_usb_service_port not found in config file");
            return default_port;
        }

        QJsonValue portValue = config["dracal_usb_service_port"];
        if (!portValue.isString()) {
            dracal_warn("dracal_usb_service_port is not a string in config file");
            return default_port;
        }

        const auto port = portValue.toString();
        dracal_info("Loaded dracal usb service port: {}", port.toStdString());
        return port;
    }();

    return port;
}

bool loadEnableDebugLogs() {
    static const auto enable = []() -> bool {
        const bool default_enable = false;

        if (!createDefaultConfigIfMissing()) {
            return default_enable;
        }

#if defined(Q_OS_WIN)
        QString roamingPath = QDir::fromNativeSeparators(qgetenv("APPDATA"));
#else
        QString roamingPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
#endif
        QString dracalPath = roamingPath + "/Dracal";
        QString configFilePath = dracalPath + "/dracal-view-config.json";

        QFile configFile(configFilePath);
        if (!configFile.open(QIODevice::ReadOnly)) {
            dracal_warn("Failed to read config file: {}", configFilePath.toStdString());
            return default_enable;
        }

        QByteArray data = configFile.readAll();
        configFile.close();

        QJsonDocument doc = QJsonDocument::fromJson(data);
        if (doc.isNull() || !doc.isObject()) {
            dracal_warn("Invalid JSON in config file");
            return default_enable;
        }

        QJsonObject config = doc.object();
        if (!config.contains("enable_debug_logs")) {
            dracal_warn("enable_debug_logs not found in config file");
            return default_enable;
        }

        QJsonValue value = config["enable_debug_logs"];
        if (!value.isBool()) {
            dracal_warn("enable_debug_logs is not a boolean in config file");
            return default_enable;
        }

        const auto enable = value.toBool();
        dracal_info("Loaded dracal enable debug logs: {}", enable);
        return enable;
    }();

    return enable;
}

bool loadEnableTraceLogs() {
    static const auto enable = []() -> bool {
        const bool default_enable = false;

        if (!createDefaultConfigIfMissing()) {
            return default_enable;
        }

#if defined(Q_OS_WIN)
        QString roamingPath = QDir::fromNativeSeparators(qgetenv("APPDATA"));
#else
        QString roamingPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
#endif
        QString dracalPath = roamingPath + "/Dracal";
        QString configFilePath = dracalPath + "/dracal-view-config.json";

        QFile configFile(configFilePath);
        if (!configFile.open(QIODevice::ReadOnly)) {
            dracal_warn("Failed to read config file: {}", configFilePath.toStdString());
            return default_enable;
        }

        QByteArray data = configFile.readAll();
        configFile.close();

        QJsonDocument doc = QJsonDocument::fromJson(data);
        if (doc.isNull() || !doc.isObject()) {
            dracal_warn("Invalid JSON in config file");
            return default_enable;
        }

        QJsonObject config = doc.object();
        if (!config.contains("enable_trace_logs")) {
            dracal_warn("enable_trace_logs not found in config file");
            return default_enable;
        }

        QJsonValue value = config["enable_trace_logs"];
        if (!value.isBool()) {
            dracal_warn("enable_trace_logs is not a boolean in config file");
            return default_enable;
        }

        const auto enable = value.toBool();
        dracal_info("Loaded dracal enable trace logs: {}", enable);
        return enable;
    }();

    return enable;
}

bool loadEnablePreventSleepDuringLogging() {
    static const auto enable = []() -> bool {
        const bool default_enable = true;

        if (!createDefaultConfigIfMissing()) {
            return default_enable;
        }

#if defined(Q_OS_WIN)
        QString roamingPath = QDir::fromNativeSeparators(qgetenv("APPDATA"));
#else
        QString roamingPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
#endif
        QString dracalPath = roamingPath + "/Dracal";
        QString configFilePath = dracalPath + "/dracal-view-config.json";

        QFile configFile(configFilePath);
        if (!configFile.open(QIODevice::ReadOnly)) {
            dracal_warn("Failed to read config file: {}", configFilePath.toStdString());
            return default_enable;
        }

        QByteArray data = configFile.readAll();
        configFile.close();

        QJsonDocument doc = QJsonDocument::fromJson(data);
        if (doc.isNull() || !doc.isObject()) {
            dracal_warn("Invalid JSON in config file");
            return default_enable;
        }

        QJsonObject config = doc.object();
        if (!config.contains("prevent_sleep_during_logging")) {
            dracal_warn("prevent_sleep_during_logging not found in config file");
            return default_enable;
        }

        QJsonValue value = config["prevent_sleep_during_logging"];
        if (!value.isBool()) {
            dracal_warn("prevent_sleep_during_logging is not a boolean in config file");
            return default_enable;
        }

        const auto enable = value.toBool();
        dracal_info("Loaded dracal enable prevent sleep: {}", enable);
        return enable;
    }();

    return enable;
}


bool loadShowAllHiddenChannels() {
    static const auto enable = []() -> bool {
        const bool default_enable = false;

        if (!createDefaultConfigIfMissing()) {
            return default_enable;
        }

#if defined(Q_OS_WIN)
        QString roamingPath = QDir::fromNativeSeparators(qgetenv("APPDATA"));
#else
        QString roamingPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
#endif
        QString dracalPath = roamingPath + "/Dracal";
        QString configFilePath = dracalPath + "/dracal-view-config.json";

        QFile configFile(configFilePath);
        if (!configFile.open(QIODevice::ReadOnly)) {
            dracal_warn("Failed to read config file: {}", configFilePath.toStdString());
            return default_enable;
        }

        QByteArray data = configFile.readAll();
        configFile.close();

        QJsonDocument doc = QJsonDocument::fromJson(data);
        if (doc.isNull() || !doc.isObject()) {
            dracal_warn("Invalid JSON in config file");
            return default_enable;
        }

        QJsonObject config = doc.object();
        if (!config.contains("show_all_hidden_channels")) {
            dracal_warn("show_all_hidden_channels not found in config file");
            return default_enable;
        }

        QJsonValue value = config["show_all_hidden_channels"];
        if (!value.isBool()) {
            dracal_warn("show_all_hidden_channels is not a boolean in config file");
            return default_enable;
        }

        const auto enable = value.toBool();
        dracal_info("Loaded dracal show all hidden channels : {}", enable);
        return enable;
    }();

    return enable;
}

#ifdef Q_OS_MAC
    #include <CoreFoundation/CoreFoundation.h>
    #include <IOKit/pwr_mgt/IOPMLib.h>

MacSleepInhibitor::MacSleepInhibitor() : m_assertionID(0) {}

MacSleepInhibitor::~MacSleepInhibitor() { uninhibit(); }

bool MacSleepInhibitor::inhibit(const QString &reason) {
    if (m_assertionID != 0) {
        dracal_info("Sleep already inhibited");
        return true;
    }

    CFStringRef reasonStr = reason.toCFString();

    IOReturn success =
        IOPMAssertionCreateWithName(kIOPMAssertionTypeNoIdleSleep, kIOPMAssertionLevelOn, reasonStr, &m_assertionID);

    CFRelease(reasonStr);

    if (success == kIOReturnSuccess) {
        dracal_info("macOS sleep prevention enabled: {}", reason.toStdString());
        return true;
    } else {
        dracal_warn("Failed to prevent sleep on macOS. Error code: {}", success);
        m_assertionID = 0;
        return false;
    }
}

void MacSleepInhibitor::uninhibit() {
    if (m_assertionID != 0) {
        IOReturn success = IOPMAssertionRelease(m_assertionID);

        if (success == kIOReturnSuccess) {
            dracal_info("macOS sleep prevention disabled");
        } else {
            dracal_warn("Failed to release sleep assertion. Error code: {}", success);
        }

        m_assertionID = 0;
    }
}

bool MacSleepInhibitor::isActive() const { return m_assertionID != 0; }

#endif // Q_OS_MAC
