#include "include/configs/outbounds/wireguard.h"

#include <QJsonArray>
#include <QUrlQuery>
#include <include/global/Utils.hpp>

#include "include/configs/common/utils.h"

namespace Configs {
    bool Peer::ParseFromLink(const QString& link)
    {
        auto url = QUrl(link);
        if (!url.isValid()) return false;
        auto query = QUrlQuery(url.query(QUrl::ComponentFormattingOption::FullyDecoded));

        address = url.host();
        port = url.port();
        if (query.hasQueryItem("public_key")) public_key = query.queryItemValue("public_key");
        if (query.hasQueryItem("peer_public_key")) public_key = query.queryItemValue("peer_public_key");
        if (query.hasQueryItem("pre_shared_key")) pre_shared_key = query.queryItemValue("pre_shared_key");
        if (query.hasQueryItem("reserved")) {
            QString rawReserved = query.queryItemValue("reserved");
            if (!rawReserved.isEmpty()) {
                for (const auto& item : rawReserved.split("-")) {
                    int val = item.toInt();
                    if (val > 0) reserved.append(val);
                }
            }
        }
        if (query.hasQueryItem("persistent_keepalive_interval")) persistent_keepalive = query.queryItemValue("persistent_keepalive").toInt();
        
        return true;
    }

    bool Peer::ParseFromJson(const QJsonObject& object)
    {
        if (object.isEmpty()) return false;
        if (object.contains("address")) address = object["address"].toString();
        if (object.contains("port")) port = object["port"].toInt();
        if (object.contains("public_key")) public_key = object["public_key"].toString();
        if (object.contains("pre_shared_key")) pre_shared_key = object["pre_shared_key"].toString();
        if (object.contains("reserved")) {
            reserved = QJsonArray2QListInt(object["reserved"].toArray());
        }
        if (object.contains("persistent_keepalive_interval")) persistent_keepalive = object["persistent_keepalive"].toInt();
        return true;
    }

    QString Peer::ExportToLink()
    {
        QUrlQuery query;
        if (!public_key.isEmpty()) query.addQueryItem("public_key", public_key);
        if (!pre_shared_key.isEmpty()) query.addQueryItem("pre_shared_key", pre_shared_key);
        if (!reserved.isEmpty()) {
            QStringList reservedStr;
            for (auto val : reserved) {
                reservedStr.append(QString::number(val));
            }
            query.addQueryItem("reserved", reservedStr.join("-"));
        }
        if (persistent_keepalive > 0) query.addQueryItem("persistent_keepalive_interval", QString::number(persistent_keepalive));
        return query.toString();
    }

    QJsonObject Peer::ExportToJson()
    {
        QJsonObject object;
        if (!address.isEmpty()) object["address"] = address;
        if (port > 0) object["port"] = port;
        if (!public_key.isEmpty()) object["public_key"] = public_key;
        if (!pre_shared_key.isEmpty()) object["pre_shared_key"] = pre_shared_key;
        if (!reserved.isEmpty()) object["reserved"] = QListInt2QJsonArray(reserved);
        if (persistent_keepalive > 0) object["persistent_keepalive_interval"] = persistent_keepalive;
        return object;
    }

    BuildResult Peer::Build()
    {
        QJsonObject object;
        if (!address.isEmpty()) object["address"] = address;
        if (port > 0) object["port"] = port;
        if (!public_key.isEmpty()) object["public_key"] = public_key;
        if (!pre_shared_key.isEmpty()) object["pre_shared_key"] = pre_shared_key;
        if (!reserved.isEmpty()) object["reserved"] = QListInt2QJsonArray(reserved);
        if (persistent_keepalive > 0) object["persistent_keepalive_interval"] = persistent_keepalive;
        object["allowed_ips"] = QListStr2QJsonArray({"0.0.0.0/0", "::/0"});
        return {object, ""};
    }

    bool wireguard::ParseFromLink(const QString& link)
    {
        // Try WireGuard config file format first
        if (link.contains("[Interface]") && link.contains("[Peer]")) {
            auto lines = link.split("\n");
            for (const auto& line : lines) {
                QString trimmed = line.trimmed();
                if (trimmed.isEmpty()) continue;
                if (trimmed == "[Peer]" || trimmed == "[Interface]") {
                    continue;
                }
                if (!trimmed.contains("=")) continue;
                auto eqIdx = trimmed.indexOf("=");
                QString key = trimmed.left(eqIdx).trimmed();
                QString value = trimmed.mid(eqIdx + 1).trimmed();
                
                if (key == "PrivateKey") private_key = value;
                if (key == "Address") address = value.replace(" ", "").split(",");
                if (key == "MTU") mtu = value.toInt();
                if (key == "PublicKey") peer->public_key = value;
                if (key == "PresharedKey") peer->pre_shared_key = value;
                if (key == "PersistentKeepalive") peer->persistent_keepalive = value.toInt();
                if (key == "Endpoint") {
                    QStringList parts = value.split(":");
                    if (parts.size() >= 2) {
                        peer->address = parts[0].trimmed();
                        peer->port = parts.last().trimmed().toInt();
                        server = peer->address;
                        server_port = peer->port;
                    }
                }
                if (key == "S1") enable_amnezia = true, init_packet_junk_size = value.toInt();
                if (key == "S2") enable_amnezia = true, response_packet_junk_size = value.toInt();
                if (key == "Jc") enable_amnezia = true, junk_packet_count = value.toInt();
                if (key == "Jmin") enable_amnezia = true, junk_packet_min_size = value.toInt();
                if (key == "Jmax") enable_amnezia = true, junk_packet_max_size = value.toInt();
                if (key == "H1") enable_amnezia = true, init_packet_magic_header = value.toInt();
                if (key == "H2") enable_amnezia = true, response_packet_magic_header = value.toInt();
                if (key == "H3") enable_amnezia = true, underload_packet_magic_header = value.toInt();
                if (key == "H4") enable_amnezia = true, transport_packet_magic_header = value.toInt();
            }
            return !private_key.isEmpty() && !peer->public_key.isEmpty();
        }
        
        // Standard wg:// URL format
        auto url = QUrl(link);
        if (!url.isValid()) return false;
        auto query = QUrlQuery(url.query(QUrl::ComponentFormattingOption::FullyDecoded));

        outbound::ParseFromLink(link);

        if (query.hasQueryItem("private_key")) private_key = query.queryItemValue("private_key");
        peer->ParseFromLink(link);
        
        QString rawLocalAddr = query.queryItemValue("local_address");
        if (!rawLocalAddr.isEmpty()) {
            address = rawLocalAddr.split("-");
        }
        
        if (query.hasQueryItem("mtu")) mtu = query.queryItemValue("mtu").toInt();
        if (query.hasQueryItem("use_system_interface")) system = query.queryItemValue("use_system_interface") == "true";
        if (query.hasQueryItem("workers")) worker_count = query.queryItemValue("workers").toInt();
        if (query.hasQueryItem("udp_timeout")) udp_timeout = query.queryItemValue("udp_timeout");

        enable_amnezia = query.queryItemValue("enable_amnezia") == "true";
        if (enable_amnezia) {
            if (query.hasQueryItem("junk_packet_count")) junk_packet_count = query.queryItemValue("junk_packet_count").toInt();
            if (query.hasQueryItem("junk_packet_min_size")) junk_packet_min_size = query.queryItemValue("junk_packet_min_size").toInt();
            if (query.hasQueryItem("junk_packet_max_size")) junk_packet_max_size = query.queryItemValue("junk_packet_max_size").toInt();
            if (query.hasQueryItem("init_packet_junk_size")) init_packet_junk_size = query.queryItemValue("init_packet_junk_size").toInt();
            if (query.hasQueryItem("response_packet_junk_size")) response_packet_junk_size = query.queryItemValue("response_packet_junk_size").toInt();
            if (query.hasQueryItem("init_packet_magic_header")) init_packet_magic_header = query.queryItemValue("init_packet_magic_header").toInt();
            if (query.hasQueryItem("response_packet_magic_header")) response_packet_magic_header = query.queryItemValue("response_packet_magic_header").toInt();
            if (query.hasQueryItem("underload_packet_magic_header")) underload_packet_magic_header = query.queryItemValue("underload_packet_magic_header").toInt();
            if (query.hasQueryItem("transport_packet_magic_header")) transport_packet_magic_header = query.queryItemValue("transport_packet_magic_header").toInt();
        }

        return !(private_key.isEmpty() || peer->public_key.isEmpty() || server.isEmpty());
    }

    bool wireguard::ParseFromJson(const QJsonObject& object)
    {
        if (object.isEmpty() || object["type"].toString() != "wireguard") return false;
        outbound::ParseFromJson(object);
        if (object.contains("private_key")) private_key = object["private_key"].toString();
        if (object.contains("peers") && object["peers"].isArray() && !object["peers"].toArray().empty()) peer->ParseFromJson(object["peers"].toArray()[0].toObject());
        if (object.contains("address")) address = QJsonArray2QListString(object["address"].toArray());
        if (object.contains("mtu")) mtu = object["mtu"].toInt();
        if (object.contains("system")) system = object["system"].toBool();
        if (object.contains("worker_count")) worker_count = object["worker_count"].toInt();
        if (object.contains("udp_timeout")) udp_timeout = object["udp_timeout"].toString();
        if (object.contains("junk_packet_count")) junk_packet_count = object["junk_packet_count"].toInt(), enable_amnezia = true;
        if (object.contains("junk_packet_min_size")) junk_packet_min_size = object["junk_packet_min_size"].toInt(), enable_amnezia = true;
        if (object.contains("junk_packet_max_size")) junk_packet_max_size = object["junk_packet_max_size"].toInt(), enable_amnezia = true;
        if (object.contains("init_packet_junk_size")) init_packet_junk_size = object["init_packet_junk_size"].toInt(), enable_amnezia = true;
        if (object.contains("response_packet_junk_size")) response_packet_junk_size = object["response_packet_junk_size"].toInt(), enable_amnezia = true;
        if (object.contains("init_packet_magic_header")) init_packet_magic_header = object["init_packet_magic_header"].toInt(), enable_amnezia = true;
        if (object.contains("response_packet_magic_header")) response_packet_magic_header = object["response_packet_magic_header"].toInt(), enable_amnezia = true;
        if (object.contains("underload_packet_magic_header")) underload_packet_magic_header = object["underload_packet_magic_header"].toInt(), enable_amnezia = true;
        if (object.contains("transport_packet_magic_header")) transport_packet_magic_header = object["transport_packet_magic_header"].toInt(), enable_amnezia = true;
        return true;
    }

    QString wireguard::ExportToLink()
    {
        QUrl url;
        QUrlQuery query;
        url.setScheme("wg");
        url.setHost(peer->address);
        url.setPort(peer->port);
        if (!name.isEmpty()) url.setFragment(name);

        if (!private_key.isEmpty()) query.addQueryItem("private_key", private_key);
        
        if (!address.isEmpty()) query.addQueryItem("local_address", address.join("-"));
        if (mtu > 0 && mtu != 1420) query.addQueryItem("mtu", QString::number(mtu));
        if (system) query.addQueryItem("use_system_interface", "true");
        if (worker_count > 0) query.addQueryItem("workers", QString::number(worker_count));
        if (!udp_timeout.isEmpty()) query.addQueryItem("udp_timeout", udp_timeout);

        if (enable_amnezia) {
            query.addQueryItem("enable_amnezia", "true");
            if (junk_packet_count > 0) query.addQueryItem("junk_packet_count", QString::number(junk_packet_count));
            if (junk_packet_min_size > 0) query.addQueryItem("junk_packet_min_size", QString::number(junk_packet_min_size));
            if (junk_packet_max_size > 0) query.addQueryItem("junk_packet_max_size", QString::number(junk_packet_max_size));
            if (init_packet_junk_size > 0) query.addQueryItem("init_packet_junk_size", QString::number(init_packet_junk_size));
            if (response_packet_junk_size > 0) query.addQueryItem("response_packet_junk_size", QString::number(response_packet_junk_size));
            if (init_packet_magic_header > 0) query.addQueryItem("init_packet_magic_header", QString::number(init_packet_magic_header));
            if (response_packet_magic_header > 0) query.addQueryItem("response_packet_magic_header", QString::number(response_packet_magic_header));
            if (underload_packet_magic_header > 0) query.addQueryItem("underload_packet_magic_header", QString::number(underload_packet_magic_header));
            if (transport_packet_magic_header > 0) query.addQueryItem("transport_packet_magic_header", QString::number(transport_packet_magic_header));
        }
        
        mergeUrlQuery(query, outbound::ExportToLink());
        mergeUrlQuery(query, peer->ExportToLink());
        
        if (!query.isEmpty()) url.setQuery(query);
        return url.toString();
    }

    QJsonObject wireguard::ExportToJson()
    {
        QJsonObject object;
        object["type"] = "wireguard";
        if (!name.isEmpty()) object["tag"] = name;
        mergeJsonObjects(object, dialFields->ExportToJson());
        if (!private_key.isEmpty()) object["private_key"] = private_key;
        if (!address.isEmpty()) object["address"] = QListStr2QJsonArray(address);
        if (mtu > 0) object["mtu"] = mtu;
        if (system) object["system"] = system;
        if (worker_count > 0) object["worker_count"] = worker_count;
        if (!udp_timeout.isEmpty()) object["udp_timeout"] = udp_timeout;
        if (enable_amnezia) {
            if (junk_packet_count > 0) object["junk_packet_count"] = junk_packet_count;
            if (junk_packet_min_size > 0) object["junk_packet_min_size"] = junk_packet_min_size;
            if (junk_packet_max_size > 0) object["junk_packet_max_size"] = junk_packet_max_size;
            if (init_packet_junk_size > 0) object["init_packet_junk_size"] = init_packet_junk_size;
            if (response_packet_junk_size > 0) object["response_packet_junk_size"] = response_packet_junk_size;
            if (init_packet_magic_header > 0) object["init_packet_magic_header"] = init_packet_magic_header;
            if (response_packet_magic_header > 0) object["response_packet_magic_header"] = response_packet_magic_header;
            if (underload_packet_magic_header > 0) object["underload_packet_magic_header"] = underload_packet_magic_header;
            if (transport_packet_magic_header > 0) object["transport_packet_magic_header"] = transport_packet_magic_header;
        }
        
        auto peerObj = peer->ExportToJson();
        if (!peerObj.isEmpty()) {
            object["peers"] = QJsonArray({peerObj});
        }
        
        return object;
    }

    BuildResult wireguard::Build()
    {
        QJsonObject object;
        object["type"] = "wireguard";
        if (!name.isEmpty()) object["tag"] = name;
        mergeJsonObjects(object, dialFields->Build().object);
        if (!private_key.isEmpty()) object["private_key"] = private_key;
        if (!address.isEmpty()) object["address"] = QListStr2QJsonArray(address);
        if (mtu > 0) object["mtu"] = mtu;
        if (system) object["system"] = system;
        if (worker_count > 0) object["worker_count"] = worker_count;
        if (!udp_timeout.isEmpty()) object["udp_timeout"] = udp_timeout;
        if (enable_amnezia) {
            if (junk_packet_count > 0) object["junk_packet_count"] = junk_packet_count;
            if (junk_packet_min_size > 0) object["junk_packet_min_size"] = junk_packet_min_size;
            if (junk_packet_max_size > 0) object["junk_packet_max_size"] = junk_packet_max_size;
            if (init_packet_junk_size > 0) object["init_packet_junk_size"] = init_packet_junk_size;
            if (response_packet_junk_size > 0) object["response_packet_junk_size"] = response_packet_junk_size;
            if (init_packet_magic_header > 0) object["init_packet_magic_header"] = init_packet_magic_header;
            if (response_packet_magic_header > 0) object["response_packet_magic_header"] = response_packet_magic_header;
            if (underload_packet_magic_header > 0) object["underload_packet_magic_header"] = underload_packet_magic_header;
            if (transport_packet_magic_header > 0) object["transport_packet_magic_header"] = transport_packet_magic_header;
        }

        auto peerObj = peer->Build().object;
        if (!peerObj.isEmpty()) {
            object["peers"] = QJsonArray({peerObj});
        }
        return {object, ""};
    }

    void wireguard::SetAddress(QString newAddr) {
        peer->address = newAddr;
    }

    QString wireguard::GetAddress() {
        return peer->address;
    }

    void wireguard::SetPort(int newPort) {
        peer->port = newPort;
    }

    QString wireguard::GetPort() {
        return QString::number(peer->port);
    }

    QString wireguard::DisplayAddress()
    {
        return ::DisplayAddress(peer->address, peer->port);
    }

    QString wireguard::DisplayType()
    {
        return "WireGuard";
    }

    bool wireguard::IsEndpoint()
    {
        return true;
    }
}
