#include <QMessageBox>
#include <QInputDialog>
#include <QLineEdit>
#include <QApplication>
#include <QNetworkInterface>
#include <QTextCodec>
#include <QSettings>
#include <QProcess>
#include <QtSystemDetection>
#include <QDir>
#include <QFileInfo>
#include <QCoreApplication>
#include <QStandardPaths>
#if defined(Q_OS_WIN)
#include <windows.h>
#include <shellapi.h>
#elif defined(Q_OS_UNIX)
#include <unistd.h>
#endif

#include "utils.h"
#include "profilemanager.h"

namespace
{
QString profileIdFromConfigPath(const QString &configPath)
{
    QFileInfo info(configPath);
    if (!info.exists() || info.suffix() != "ini")
    {
        return "custom";
    }

    if (info.fileName() == "state.ini")
    {
        return "custom";
    }

    QDir parentDir = info.dir();
    if (parentDir.dirName() != "profiles")
    {
        return "custom";
    }

    return info.baseName();
}
}

QString Utils::consoleOutputToQString(const QByteArray &byteArray)
{
    static QString codeName = "";
    if (codeName == "UTF-8")
    {
        return QTextCodec::codecForName("UTF-8")->toUnicode(byteArray);;
    }
    else if (codeName == "GBK")
    {
        return QTextCodec::codecForName("GBK")->toUnicode(byteArray);;
    }
    else if (codeName == "locale")
    {
        return QString::fromLocal8Bit(byteArray);
    }
    else
    {
        QTextCodec *utf8Codec = QTextCodec::codecForName("UTF-8");
        QString utf8Str = utf8Codec->toUnicode(byteArray);
        QByteArray utf8ByteArrayBack = utf8Codec->fromUnicode(utf8Str);

        QTextCodec *gbkCodec = QTextCodec::codecForName("GBK");
        QString gbkStr = gbkCodec->toUnicode(byteArray);
        QByteArray gbkByteArrayBack = gbkCodec->fromUnicode(gbkStr);

        if (utf8ByteArrayBack == byteArray && gbkByteArrayBack != byteArray)
        {
            codeName = "UTF-8";
            return utf8Str;
        }
        else if (gbkByteArrayBack == byteArray && utf8ByteArrayBack != byteArray)
        {
            codeName = "GBK";
            return gbkStr;
        }
        else if (gbkByteArrayBack == byteArray && utf8ByteArrayBack == byteArray)
        {
            return utf8Str;
        }
        else
        {
            codeName = "locale";
            return QString::fromLocal8Bit(byteArray);
        }
    }
}

QString Utils::getArgValue(const QStringList &args, const QString &key)
{
    const QString prefix = key + "=";
    for (int i = 0; i < args.size(); ++i)
    {
        const QString &arg = args.at(i);
        if (arg == key)
        {
            if (i + 1 < args.size())
            {
                return args.at(i + 1);
            }
            return "";
        }
        if (arg.startsWith(prefix))
        {
            return arg.mid(prefix.size());
        }
    }
    return "";
}

void Utils::setWidgetFixedWhenHidden(QWidget *widget)
{
    QSizePolicy originPolicy = widget->sizePolicy();
    originPolicy.setRetainSizeWhenHidden(true);
    widget->setSizePolicy(originPolicy);
}

void Utils::showAboutMessageBox(QWidget *parent)
{
    QMessageBox messageBox(parent);
    messageBox.setWindowTitle("关于");
    messageBox.setTextFormat(Qt::RichText);
    QString aboutText = QApplication::applicationDisplayName() + " " + QApplication::applicationVersion() +
                        "<br>改进的 ZJU-Connect 图形界面" +
                        "<br>作者：<a href='https://github.com/chenx-dust'>Chenx Dust</a>" +
                        "<br>项目主页：<a href='https://github.com/" + REPO_NAME + "'>https://github.com/" + REPO_NAME + "</a>" +
                        "<br><br><b>致谢：</b>" +
                        "<br><br>ZJU-Connect-for-Windows" +
                        "<br>基于 Qt 编写的 ZJU 网络客户端" +
                        "<br>作者：<a href='https://myth.cx'>Myth</a>" +
                        "<br>项目主页：<a href='https://github.com/Mythologyli/ZJU-Connect-for-Windows'>https://github.com/Mythologyli/ZJU-Connect-for-Windows</a>" +
                        "<br><br>zju-connect" +
                        "<br>ZJU RVPN 客户端的 Go 语言实现" +
                        "<br>作者：<a href='https://myth.cx'>Myth</a>" +
                        "<br>项目主页：<a href='https://github.com/Mythologyli/zju-connect'>https://github.com/Mythologyli/zju-connect</a>" +
                        "<br><br>EasierConnect" +
                        "<br>EasyConnect 客户端的开源实现" +
                        "<br>作者：<a href='https://github.com/lyc8503'>lyc8503</a>" +
                        "<br>项目主页：<a href='https://github.com/lyc8503/EasierConnect'>https://github.com/lyc8503/EasierConnect</a>";
    messageBox.setText(aboutText);
    messageBox.setIconPixmap(QPixmap(":/resource/icon.png").scaled(
        100, 100, Qt::KeepAspectRatio, Qt::SmoothTransformation
    ));
    messageBox.exec();
}

QString Utils::getIpv4Address(const QString &interfaceName)
{
    auto interfaces = QNetworkInterface::allInterfaces();
    for (auto &singleInterface: interfaces)
    {
        if (singleInterface.humanReadableName() == interfaceName)
        {
            auto addresses =
                singleInterface.addressEntries();
            for (auto &address: addresses)
            {
                if (address.ip().protocol() == QAbstractSocket::IPv4Protocol)
                {
                    return address.ip().toString();
                }
            }
        }
    }

    return "";
}

QString macOSAppBundlePath()
{
#ifdef Q_OS_MAC
    QDir dir = QDir(QCoreApplication::applicationDirPath());
    dir.cdUp();
    dir.cdUp();
    QString absolutePath = dir.absolutePath();
    // absolutePath will contain a "/" at the end,
    // but we want the clean path to the .app bundle
    if (absolutePath.length() > 0 && absolutePath.right(1) == "/")
    {
        absolutePath.chop(1);
    }
    return absolutePath;
#else
    return "";
#endif
}

QString macOSAppBundleName()
{
#ifdef Q_OS_MAC
    QString bundlePath = macOSAppBundlePath();
    QFileInfo fileInfo(bundlePath);
    return fileInfo.baseName();
#else
    return "";
#endif
}

QString nativeAppPath()
{
    return QDir::toNativeSeparators(QCoreApplication::applicationFilePath());
}

bool Utils::isRunningAsAdmin()
{
#if defined(Q_OS_WIN)
    BOOL isAdmin = FALSE;
    PSID adminGroup = nullptr;
    SID_IDENTIFIER_AUTHORITY ntAuthority = SECURITY_NT_AUTHORITY;

    if (AllocateAndInitializeSid(&ntAuthority, 2, SECURITY_BUILTIN_DOMAIN_RID,
        DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &adminGroup))
    {
        CheckTokenMembership(nullptr, adminGroup, &isAdmin);
        FreeSid(adminGroup);
    }
    return isAdmin;
#elif defined(Q_OS_UNIX)
    return geteuid() == 0;
#else
    return false;
#endif
}

bool Utils::relaunchAsAdmin()
{
    QString program = QCoreApplication::applicationFilePath();
    QStringList args = QCoreApplication::arguments();
    if (!args.isEmpty())
    {
        args.removeFirst();
    }

    if (!args.contains("--connect"))
    {
        args << "--connect";
    }

#if defined(Q_OS_WIN)
    QStringList quotedArgs;
    for (const QString &arg : args)
    {
        if (arg.contains(' '))
        {
            quotedArgs << "\"" + arg + "\"";
        }
        else
        {
            quotedArgs << arg;
        }
    }
    QString arguments = quotedArgs.join(" ");
    HINSTANCE res = ShellExecuteW(
        nullptr,
        L"runas",
        reinterpret_cast<LPCWSTR>(program.utf16()),
        reinterpret_cast<LPCWSTR>(arguments.utf16()),
        nullptr,
        SW_SHOWNORMAL
    );

    return reinterpret_cast<INT_PTR>(res) > 32;
#else
    Q_UNUSED(program)
    Q_UNUSED(args)
    return false;
#endif
}

void Utils::setAutoStart(bool enable)
{
#if defined(Q_OS_WINDOWS)
    QSettings autoStartSettings(R"(HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run)",
                                QSettings::NativeFormat);
    if (enable)
    {
        autoStartSettings.setValue(
            QApplication::applicationName(),
            nativeAppPath()
        );
    }
    else
    {
        autoStartSettings.remove(QApplication::applicationName());
    }
    autoStartSettings.sync();
#elif defined(Q_OS_MACOS)
    {
        QStringList args;
        args << "-e" << "tell application \"System Events\" to delete login item \"" + macOSAppBundleName() + "\"";
        qDebug() << args;

        QProcess process;
        process.start("osascript", args);
        process.waitForFinished();
        QString error = process.readAllStandardError();
        qDebug() << process.readAllStandardOutput();
        qDebug() << error;
        if (!enable && process.error() != QProcess::UnknownError)
        {
            QMessageBox::critical(nullptr, "取消开机自启动失败", "无法删除登录项：" + process.errorString());
            return;
        }
        if (!enable && process.exitCode() != 0)
        {
            QMessageBox::critical(nullptr, "取消开机自启动失败", "无法删除登录项：" + error);
            return;
        }
    }
    if (enable)
    {
        QStringList args;
        args << "-e" << "tell application \"System Events\" to make login item at end with properties {path:\"" +
                    macOSAppBundlePath() + "\", hidden:false}";
        qDebug() << args;

        QProcess process;
        process.start("osascript", args);
        process.waitForFinished();
        qDebug() << process.readAllStandardOutput();
        qDebug() << process.readAllStandardError();
        if (process.error() != QProcess::UnknownError)
        {
            QMessageBox::critical(nullptr, "设置开机自启动失败", "无法创建登录项：" + process.errorString());
            return;
        }
        if (process.exitCode() != 0)
        {
            QMessageBox::critical(nullptr, "设置开机自启动失败", "无法创建登录项：" + process.readAllStandardError());
            return;
        }
    }
#elif defined(Q_OS_LINUX)
    QString autostartPath = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + "/autostart/";
    QDir dir(autostartPath);
    QString desktopFilePath = autostartPath + QApplication::applicationName() + ".desktop";
    QFile desktopFile(desktopFilePath);

    if (dir.exists() && desktopFile.exists())
    {
        if (!desktopFile.remove())
        {
            QMessageBox::critical(nullptr, "取消开机自启动失败", "无法删除 .desktop 文件：" + desktopFilePath);
            return;
        }
    }

    if (enable)
    {
        if (!dir.exists())
        {
            if (!dir.mkpath("."))
            {
                QMessageBox::critical(nullptr, "设置开机自启动失败", "无法创建 autostart 目录：" + autostartPath);
                return;
            }
        }

        if (!desktopFile.open(QIODevice::WriteOnly | QIODevice::Text))
        {
            QMessageBox::critical(nullptr, "设置开机自启动失败", "无法创建 .desktop 文件：" + desktopFilePath);
            return;
        }

        QTextStream out(&desktopFile);
        out << "[Desktop Entry]\n";
        out << "Type=Application\n";
        out << "Name=" << QApplication::applicationName() << "\n";
        out << "Exec=" << nativeAppPath() << "\n";
        out << "X-GNOME-Autostart-enabled=true\n";
        desktopFile.close();
    }
#endif
}

bool Utils::credentialCheck(const QString &username, const QString &password)
{
    if (username.isEmpty() || password.isEmpty())
    {
        int status = QMessageBox::warning(nullptr, "警告", "账号或密码为空！\n\n是否继续？", QMessageBox::Ok, QMessageBox::Cancel);
        return status == QMessageBox::Ok;
    }

    bool asciiOnly = true;
    for (QChar c: username)
    {
        if (c.unicode() > 127)
        {
            asciiOnly = false;
            break;
        }
    }
    for (QChar c: password)
    {
        if (c.unicode() > 127)
        {
            asciiOnly = false;
            break;
        }
    }
    if (!asciiOnly)
    {
        int status = QMessageBox::warning(nullptr, "警告", "账号或密码存在非 ASCII 字符！\n建议检查输入法设置。\n\n是否继续？", QMessageBox::Ok, QMessageBox::Cancel);
        return status == QMessageBox::Ok;
    }

    return true;
}

void Utils::clearClientData(const QString &profileId)
{
    QFile::remove(getClientDataPath(profileId));
}

QString Utils::getClientDataPath(const QString &profileId)
{
    QString defaultPath = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation);
    QDir dir(defaultPath);
    if (!dir.exists())
    {
        dir.mkpath(".");
    }

    QDir profileDir(dir.filePath("profiles/" + profileId));
    if (!profileDir.exists())
    {
        profileDir.mkpath(".");
    }

    QFile clientDataFile(profileDir.filePath("client-data.json"));
    if (!clientDataFile.exists() && clientDataFile.open(QIODevice::WriteOnly))
    {
        clientDataFile.write("{}");
        clientDataFile.close();
    }

    return profileDir.filePath("client-data.json");
}

QString Utils::getLogFilePath()
{
    QString logPath = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation);
    QDir logDir(logPath);
    if (!logDir.exists())
    {
        logDir.mkpath(".");
    }
    return logDir.filePath("zjuconnect.log");
}

void Utils::resetDefaultSettings(QSettings& settings)
{
    settings.setValue("Credential/Username", "");
    settings.setValue("Credential/Password", "");
    settings.setValue("Credential/TOTPSecret", "");

    settings.setValue("Common/ConnectAfterStart", false);
    settings.setValue("Common/CheckUpdateAfterStart", false);
    settings.setValue("Common/AutoSetProxy", false);
    settings.setValue("Common/ReconnectTime", 1);
    settings.setValue("Common/AutoReconnect", false);
    settings.setValue("Common/SystemProxyBypass", "");


    settings.setValue("ZJUConnect/ServerAddress", "trust.hitsz.edu.cn");
    settings.setValue("ZJUConnect/ServerPort", 443);
    settings.setValue("ZJUConnect/DNS", "");
    settings.setValue("ZJUConnect/DNSAuto", true);
    settings.setValue("ZJUConnect/SecondaryDNS", "");
    settings.setValue("ZJUConnect/DNSTTL", 3600);
    settings.setValue("ZJUConnect/SOCKS5Port", 11080);
    settings.setValue("ZJUConnect/HTTPPort", 11081);
    settings.setValue("ZJUConnect/ShadowsocksURL", "");
    settings.setValue("ZJUConnect/DialDirectProxy", "");
    settings.setValue("ZJUConnect/UpdateBestNodesInterval", 300);

    settings.setValue("ZJUConnect/Protocol", "atrust");
    settings.setValue("ZJUConnect/LoginDomain", "hitcas");
    settings.setValue("ZJUConnect/AuthType", "cas");
    settings.setValue("ZJUConnect/CasLoginURL", "");
    settings.setValue("ZJUConnect/PhoneCountryCode", "86");
    settings.setValue("ZJUConnect/PhoneNumber", "");

    settings.setValue("ZJUConnect/MultiLine", false);
    settings.setValue("ZJUConnect/KeepAlive", false);
    settings.setValue("ZJUConnect/KeepAliveURL", "");
    settings.setValue("ZJUConnect/OutsideAccess", false);

    settings.setValue("ZJUConnect/SkipDomainResource", false);
    settings.setValue("ZJUConnect/DisableServerConfig", false);
    settings.setValue("ZJUConnect/ProxyAll", false);

    settings.setValue("ZJUConnect/DisableZJUDNS", false);
    settings.setValue("ZJUConnect/ZJUDefault", false);
    settings.setValue("ZJUConnect/Debug", false);

    settings.setValue("ZJUConnect/TUNMode", false);
    settings.setValue("ZJUConnect/AddRoute", false);
    settings.setValue("ZJUConnect/DNSHijack", false);
    settings.setValue("ZJUConnect/FakeIP", false);
    settings.setValue("ZJUConnect/TCPTunnelMode", false);


    settings.setValue("ZJUConnect/TCPPortForwarding", "");
    settings.setValue("ZJUConnect/UDPPortForwarding", "");
    settings.setValue("ZJUConnect/CustomDNS", "");
    settings.setValue("ZJUConnect/CustomProxyDomain", "");
    settings.setValue("ZJUConnect/ExtraArguments", "");

    settings.setValue("Common/ConfigVersion", Utils::CONFIG_VERSION);
}
