#include "xrayConfigModel.h"

#include "core/utils/protocolEnum.h"
#include "core/protocols/protocolUtils.h"
#include "core/utils/constants/configKeys.h"
#include "core/utils/constants/protocolConstants.h"

using namespace amnezia;
using namespace ProtocolUtils;

XrayConfigModel::XrayConfigModel(QObject* parent) : QAbstractListModel(parent)
{
}

int XrayConfigModel::rowCount(const QModelIndex& parent) const
{
    Q_UNUSED(parent);
    return 1;
}

bool XrayConfigModel::setData(const QModelIndex& index, const QVariant& value, int role)
{
    // This model always has a single row (row 0). Using rowCount() avoids
    // coupling editing ability to global container list size.
    if (!index.isValid() || index.row() < 0 || index.row() >= rowCount())
    {
        return false;
    }

    const bool wasUnsavedChanges = hasUnsavedChanges();

    auto& srv = m_protocolConfig.serverConfig;
    auto& xhttp = srv.xhttp;
    auto& mkcp = srv.mkcp;
    auto& pad = xhttp.xPadding;
    auto& mux = xhttp.xmux;

    QString str = value.toString();

    switch (role)
    {
    // ── Main ──────────────────────────────────────────────────────────
    case Roles::SiteRole: srv.site = str;
        break;
    case Roles::PortRole: srv.port = str;
        break;
    case Roles::TransportRole: srv.transport = str;
        break;
    case Roles::SecurityRole: srv.security = str;
        break;
    case Roles::FlowRole: srv.flow = str;
        break;

    // ── Security ──────────────────────────────────────────────────────
    case Roles::FingerprintRole: srv.fingerprint = str;
        break;
    case Roles::SniRole: srv.sni = str;
        break;
    case Roles::AlpnRole: srv.alpn = str;
        break;

    // ── XHTTP ─────────────────────────────────────────────────────────
    case Roles::XhttpModeRole: xhttp.mode = str;
        break;
    case Roles::XhttpHostRole: xhttp.host = str;
        break;
    case Roles::XhttpPathRole: xhttp.path = str;
        break;
    case Roles::XhttpHeadersTemplateRole: xhttp.headersTemplate = str;
        break;
    case Roles::XhttpUplinkMethodRole: xhttp.uplinkMethod = str;
        break;
    case Roles::XhttpDisableGrpcRole: xhttp.disableGrpc = value.toBool();
        break;
    case Roles::XhttpDisableSseRole: xhttp.disableSse = value.toBool();
        break;

    case Roles::XhttpSessionPlacementRole: xhttp.sessionPlacement = str;
        break;
    case Roles::XhttpSessionKeyRole: xhttp.sessionKey = str;
        break;
    case Roles::XhttpSeqPlacementRole: xhttp.seqPlacement = str;
        break;
    case Roles::XhttpSeqKeyRole: xhttp.seqKey = str;
        break;
    case Roles::XhttpUplinkDataPlacementRole: xhttp.uplinkDataPlacement = str;
        break;
    case Roles::XhttpUplinkDataKeyRole: xhttp.uplinkDataKey = str;
        break;

    case Roles::XhttpUplinkChunkSizeRole: xhttp.uplinkChunkSize = str;
        break;
    case Roles::XhttpScMaxBufferedPostsRole: xhttp.scMaxBufferedPosts = str;
        break;
    case Roles::XhttpScMaxEachPostBytesMinRole: xhttp.scMaxEachPostBytesMin = str;
        break;
    case Roles::XhttpScMaxEachPostBytesMaxRole: xhttp.scMaxEachPostBytesMax = str;
        break;
    case Roles::XhttpScMinPostsIntervalMsMinRole: xhttp.scMinPostsIntervalMsMin = str;
        break;
    case Roles::XhttpScMinPostsIntervalMsMaxRole: xhttp.scMinPostsIntervalMsMax = str;
        break;
    case Roles::XhttpScStreamUpServerSecsMinRole: xhttp.scStreamUpServerSecsMin = str;
        break;
    case Roles::XhttpScStreamUpServerSecsMaxRole: xhttp.scStreamUpServerSecsMax = str;
        break;

    // ── mKCP ──────────────────────────────────────────────────────────
    case Roles::MkcpTtiRole: mkcp.tti = str;
        break;
    case Roles::MkcpUplinkCapacityRole: mkcp.uplinkCapacity = str;
        break;
    case Roles::MkcpDownlinkCapacityRole: mkcp.downlinkCapacity = str;
        break;
    case Roles::MkcpReadBufferSizeRole: mkcp.readBufferSize = str;
        break;
    case Roles::MkcpWriteBufferSizeRole: mkcp.writeBufferSize = str;
        break;
    case Roles::MkcpCongestionRole: mkcp.congestion = value.toBool();
        break;

    // ── xPadding ──────────────────────────────────────────────────────
    case Roles::XPaddingBytesMinRole: pad.bytesMin = str;
        break;
    case Roles::XPaddingBytesMaxRole: pad.bytesMax = str;
        break;
    case Roles::XPaddingObfsModeRole: pad.obfsMode = value.toBool();
        break;
    case Roles::XPaddingKeyRole: pad.key = str;
        break;
    case Roles::XPaddingHeaderRole: pad.header = str;
        break;
    case Roles::XPaddingPlacementRole: pad.placement = str;
        break;
    case Roles::XPaddingMethodRole: pad.method = str;
        break;

    // ── xmux ──────────────────────────────────────────────────────────
    case Roles::XmuxEnabledRole: mux.enabled = value.toBool();
        break;
    case Roles::XmuxMaxConcurrencyMinRole: mux.maxConcurrencyMin = str;
        break;
    case Roles::XmuxMaxConcurrencyMaxRole: mux.maxConcurrencyMax = str;
        break;
    case Roles::XmuxMaxConnectionsMinRole: mux.maxConnectionsMin = str;
        break;
    case Roles::XmuxMaxConnectionsMaxRole: mux.maxConnectionsMax = str;
        break;
    case Roles::XmuxCMaxReuseTimesMinRole: mux.cMaxReuseTimesMin = str;
        break;
    case Roles::XmuxCMaxReuseTimesMaxRole: mux.cMaxReuseTimesMax = str;
        break;
    case Roles::XmuxHMaxRequestTimesMinRole: mux.hMaxRequestTimesMin = str;
        break;
    case Roles::XmuxHMaxRequestTimesMaxRole: mux.hMaxRequestTimesMax = str;
        break;
    case Roles::XmuxHMaxReusableSecsMinRole: mux.hMaxReusableSecsMin = str;
        break;
    case Roles::XmuxHMaxReusableSecsMaxRole: mux.hMaxReusableSecsMax = str;
        break;
    case Roles::XmuxHKeepAlivePeriodRole: mux.hKeepAlivePeriod = str;
        break;

    default:
        return false;
    }

    emit dataChanged(index, index, QList{role});
    if (wasUnsavedChanges != hasUnsavedChanges()) {
        emit hasUnsavedChangesChanged();
    }
    return true;
}

QVariant XrayConfigModel::data(const QModelIndex& index, int role) const
{
    if (!index.isValid() || index.row() < 0 || index.row() >= rowCount()) {
        return QVariant();
    }

    const auto& srv = m_protocolConfig.serverConfig;
    const auto& xhttp = srv.xhttp;
    const auto& mkcp = srv.mkcp;
    const auto& pad = xhttp.xPadding;
    const auto& mux = xhttp.xmux;

    switch (role)
    {
    // ── Main ──────────────────────────────────────────────────────────
    case Roles::SiteRole: return srv.site;
    case Roles::PortRole: return srv.port;
    case Roles::TransportRole: return srv.transport;
    case Roles::SecurityRole: return srv.security;
    case Roles::FlowRole: return srv.flow;

    // ── Security ──────────────────────────────────────────────────────
    case Roles::FingerprintRole: return srv.fingerprint;
    case Roles::SniRole: return srv.sni;
    case Roles::AlpnRole: return srv.alpn;

    // ── XHTTP ─────────────────────────────────────────────────────────
    case Roles::XhttpModeRole: return xhttp.mode;
    case Roles::XhttpHostRole: return xhttp.host;
    case Roles::XhttpPathRole: return xhttp.path;
    case Roles::XhttpHeadersTemplateRole: return xhttp.headersTemplate;
    case Roles::XhttpUplinkMethodRole: return xhttp.uplinkMethod;
    case Roles::XhttpDisableGrpcRole: return xhttp.disableGrpc;
    case Roles::XhttpDisableSseRole: return xhttp.disableSse;

    case Roles::XhttpSessionPlacementRole: return xhttp.sessionPlacement;
    case Roles::XhttpSessionKeyRole: return xhttp.sessionKey;
    case Roles::XhttpSeqPlacementRole: return xhttp.seqPlacement;
    case Roles::XhttpSeqKeyRole: return xhttp.seqKey;
    case Roles::XhttpUplinkDataPlacementRole: return xhttp.uplinkDataPlacement;
    case Roles::XhttpUplinkDataKeyRole: return xhttp.uplinkDataKey;

    case Roles::XhttpUplinkChunkSizeRole: return xhttp.uplinkChunkSize;
    case Roles::XhttpScMaxBufferedPostsRole: return xhttp.scMaxBufferedPosts;
    case Roles::XhttpScMaxEachPostBytesMinRole: return xhttp.scMaxEachPostBytesMin;
    case Roles::XhttpScMaxEachPostBytesMaxRole: return xhttp.scMaxEachPostBytesMax;
    case Roles::XhttpScMinPostsIntervalMsMinRole: return xhttp.scMinPostsIntervalMsMin;
    case Roles::XhttpScMinPostsIntervalMsMaxRole: return xhttp.scMinPostsIntervalMsMax;
    case Roles::XhttpScStreamUpServerSecsMinRole: return xhttp.scStreamUpServerSecsMin;
    case Roles::XhttpScStreamUpServerSecsMaxRole: return xhttp.scStreamUpServerSecsMax;

    // ── mKCP ──────────────────────────────────────────────────────────
    case Roles::MkcpTtiRole: return mkcp.tti;
    case Roles::MkcpUplinkCapacityRole: return mkcp.uplinkCapacity;
    case Roles::MkcpDownlinkCapacityRole: return mkcp.downlinkCapacity;
    case Roles::MkcpReadBufferSizeRole: return mkcp.readBufferSize;
    case Roles::MkcpWriteBufferSizeRole: return mkcp.writeBufferSize;
    case Roles::MkcpCongestionRole: return mkcp.congestion;

    // ── xPadding ──────────────────────────────────────────────────────
    case Roles::XPaddingBytesMinRole: return pad.bytesMin;
    case Roles::XPaddingBytesMaxRole: return pad.bytesMax;
    case Roles::XPaddingObfsModeRole: return pad.obfsMode;
    case Roles::XPaddingKeyRole: return pad.key;
    case Roles::XPaddingHeaderRole: return pad.header;
    case Roles::XPaddingPlacementRole: return pad.placement;
    case Roles::XPaddingMethodRole: return pad.method;

    // ── xmux ──────────────────────────────────────────────────────────
    case Roles::XmuxEnabledRole: return mux.enabled;
    case Roles::XmuxMaxConcurrencyMinRole: return mux.maxConcurrencyMin;
    case Roles::XmuxMaxConcurrencyMaxRole: return mux.maxConcurrencyMax;
    case Roles::XmuxMaxConnectionsMinRole: return mux.maxConnectionsMin;
    case Roles::XmuxMaxConnectionsMaxRole: return mux.maxConnectionsMax;
    case Roles::XmuxCMaxReuseTimesMinRole: return mux.cMaxReuseTimesMin;
    case Roles::XmuxCMaxReuseTimesMaxRole: return mux.cMaxReuseTimesMax;
    case Roles::XmuxHMaxRequestTimesMinRole: return mux.hMaxRequestTimesMin;
    case Roles::XmuxHMaxRequestTimesMaxRole: return mux.hMaxRequestTimesMax;
    case Roles::XmuxHMaxReusableSecsMinRole: return mux.hMaxReusableSecsMin;
    case Roles::XmuxHMaxReusableSecsMaxRole: return mux.hMaxReusableSecsMax;
    case Roles::XmuxHKeepAlivePeriodRole: return mux.hKeepAlivePeriod;
    }

    return QVariant();
}

void XrayConfigModel::updateModel(amnezia::DockerContainer container, const amnezia::XrayProtocolConfig& protocolConfig)
{
    const bool wasUnsavedChanges = hasUnsavedChanges();

    beginResetModel();

    m_container = container;

    m_protocolConfig = protocolConfig;

    applyDefaultsToServerConfig(m_protocolConfig.serverConfig);

    m_originalProtocolConfig = m_protocolConfig;

    endResetModel();
    if (wasUnsavedChanges != hasUnsavedChanges()) {
        emit hasUnsavedChangesChanged();
    }
}

void XrayConfigModel::applyDefaultsToServerConfig(amnezia::XrayServerConfig &config)
{
    if (config.port.isEmpty()) {
        config.port = protocols::xray::defaultPort;
    }

    if (config.transportProto.isEmpty()) {
        config.transportProto = ProtocolUtils::transportProtoToString(
            ProtocolUtils::defaultTransportProto(amnezia::Proto::Xray), amnezia::Proto::Xray);
    }

    if (config.site.isEmpty()) {
        config.site = protocols::xray::defaultSite;
    }

    if (config.transport.isEmpty()) {
        config.transport = protocols::xray::defaultTransport;
    }

    if (config.security.isEmpty()) {
        config.security = protocols::xray::defaultSecurity;
    }

    if (config.flow.isEmpty()) {
        config.flow = protocols::xray::defaultFlow;
    }

    if (config.fingerprint.isEmpty()) {
        config.fingerprint = protocols::xray::defaultFingerprint;
    } else if (config.fingerprint.contains(QLatin1String("Mozilla/5.0"), Qt::CaseInsensitive)) {
        config.fingerprint = QString::fromLatin1(protocols::xray::defaultFingerprint);
    }

    if (config.sni.isEmpty()) {
        config.sni = protocols::xray::defaultSni;
    }

    if (config.alpn.isEmpty()) {
        config.alpn = protocols::xray::defaultAlpn;
    }

    // XHTTP transport defaults
    if (config.xhttp.host.isEmpty()) {
        config.xhttp.host = protocols::xray::defaultXhttpHost;
    }
    if (config.xhttp.mode.isEmpty()) {
        config.xhttp.mode = protocols::xray::defaultXhttpMode;
    }
    if (config.xhttp.headersTemplate.isEmpty()) {
        config.xhttp.headersTemplate = protocols::xray::defaultXhttpHeadersTemplate;
    }
    if (config.xhttp.uplinkMethod.isEmpty()) {
        config.xhttp.uplinkMethod = protocols::xray::defaultXhttpUplinkMethod;
    }
    if (config.xhttp.sessionPlacement.isEmpty()) {
        config.xhttp.sessionPlacement = protocols::xray::defaultXhttpSessionPlacement;
    }
    if (config.xhttp.sessionKey.isEmpty()) {
        config.xhttp.sessionKey = protocols::xray::defaultXhttpSessionKey;
    }
    if (config.xhttp.seqPlacement.isEmpty()) {
        config.xhttp.seqPlacement = protocols::xray::defaultXhttpSeqPlacement;
    }
    if (config.xhttp.uplinkDataPlacement.isEmpty()) {
        config.xhttp.uplinkDataPlacement = protocols::xray::defaultXhttpUplinkDataPlacement;
    }

    // xPadding defaults
    if (config.xhttp.xPadding.placement.isEmpty()) {
        config.xhttp.xPadding.placement = protocols::xray::defaultXPaddingPlacement;
    }
    if (config.xhttp.xPadding.method.isEmpty()) {
        config.xhttp.xPadding.method = protocols::xray::defaultXPaddingMethod;
    }
}

amnezia::XrayProtocolConfig XrayConfigModel::getProtocolConfig()
{
    const bool serverSettingsChanged =
            !m_protocolConfig.serverConfig.hasEqualServerSettings(m_originalProtocolConfig.serverConfig);

    if (serverSettingsChanged) {
        m_protocolConfig.clearClientConfig();
    }
    return m_protocolConfig;
}

bool XrayConfigModel::isServerSettingsEqual() const
{
    return m_protocolConfig.serverConfig.hasEqualServerSettings(m_originalProtocolConfig.serverConfig);
}

bool XrayConfigModel::hasUnsavedChanges() const
{
    return !isServerSettingsEqual();
}

QHash<int, QByteArray> XrayConfigModel::roleNames() const
{
    QHash<int, QByteArray> roles;

    // Main
    roles[SiteRole] = "site";
    roles[PortRole] = "port";
    roles[TransportRole] = "transport";
    roles[SecurityRole] = "security";
    roles[FlowRole] = "flow";

    // Security
    roles[FingerprintRole] = "fingerprint";
    roles[SniRole] = "sni";
    roles[AlpnRole] = "alpn";

    // XHTTP
    roles[XhttpModeRole] = "xhttpMode";
    roles[XhttpHostRole] = "xhttpHost";
    roles[XhttpPathRole] = "xhttpPath";
    roles[XhttpHeadersTemplateRole] = "xhttpHeadersTemplate";
    roles[XhttpUplinkMethodRole] = "xhttpUplinkMethod";
    roles[XhttpDisableGrpcRole] = "xhttpDisableGrpc";
    roles[XhttpDisableSseRole] = "xhttpDisableSse";

    roles[XhttpSessionPlacementRole] = "xhttpSessionPlacement";
    roles[XhttpSessionKeyRole] = "xhttpSessionKey";
    roles[XhttpSeqPlacementRole] = "xhttpSeqPlacement";
    roles[XhttpSeqKeyRole] = "xhttpSeqKey";
    roles[XhttpUplinkDataPlacementRole] = "xhttpUplinkDataPlacement";
    roles[XhttpUplinkDataKeyRole] = "xhttpUplinkDataKey";

    roles[XhttpUplinkChunkSizeRole] = "xhttpUplinkChunkSize";
    roles[XhttpScMaxBufferedPostsRole] = "xhttpScMaxBufferedPosts";
    roles[XhttpScMaxEachPostBytesMinRole] = "xhttpScMaxEachPostBytesMin";
    roles[XhttpScMaxEachPostBytesMaxRole] = "xhttpScMaxEachPostBytesMax";
    roles[XhttpScMinPostsIntervalMsMinRole] = "xhttpScMinPostsIntervalMsMin";
    roles[XhttpScMinPostsIntervalMsMaxRole] = "xhttpScMinPostsIntervalMsMax";
    roles[XhttpScStreamUpServerSecsMinRole] = "xhttpScStreamUpServerSecsMin";
    roles[XhttpScStreamUpServerSecsMaxRole] = "xhttpScStreamUpServerSecsMax";

    // mKCP
    roles[MkcpTtiRole] = "mkcpTti";
    roles[MkcpUplinkCapacityRole] = "mkcpUplinkCapacity";
    roles[MkcpDownlinkCapacityRole] = "mkcpDownlinkCapacity";
    roles[MkcpReadBufferSizeRole] = "mkcpReadBufferSize";
    roles[MkcpWriteBufferSizeRole] = "mkcpWriteBufferSize";
    roles[MkcpCongestionRole] = "mkcpCongestion";

    // xPadding
    roles[XPaddingBytesMinRole] = "xPaddingBytesMin";
    roles[XPaddingBytesMaxRole] = "xPaddingBytesMax";
    roles[XPaddingObfsModeRole] = "xPaddingObfsMode";
    roles[XPaddingKeyRole] = "xPaddingKey";
    roles[XPaddingHeaderRole] = "xPaddingHeader";
    roles[XPaddingPlacementRole] = "xPaddingPlacement";
    roles[XPaddingMethodRole] = "xPaddingMethod";

    // xmux
    roles[XmuxEnabledRole] = "xmuxEnabled";
    roles[XmuxMaxConcurrencyMinRole] = "xmuxMaxConcurrencyMin";
    roles[XmuxMaxConcurrencyMaxRole] = "xmuxMaxConcurrencyMax";
    roles[XmuxMaxConnectionsMinRole] = "xmuxMaxConnectionsMin";
    roles[XmuxMaxConnectionsMaxRole] = "xmuxMaxConnectionsMax";
    roles[XmuxCMaxReuseTimesMinRole] = "xmuxCMaxReuseTimesMin";
    roles[XmuxCMaxReuseTimesMaxRole] = "xmuxCMaxReuseTimesMax";
    roles[XmuxHMaxRequestTimesMinRole] = "xmuxHMaxRequestTimesMin";
    roles[XmuxHMaxRequestTimesMaxRole] = "xmuxHMaxRequestTimesMax";
    roles[XmuxHMaxReusableSecsMinRole] = "xmuxHMaxReusableSecsMin";
    roles[XmuxHMaxReusableSecsMaxRole] = "xmuxHMaxReusableSecsMax";
    roles[XmuxHKeepAlivePeriodRole] = "xmuxHKeepAlivePeriod";

    return roles;
}

void XrayConfigModel::resetToDefaults()
{
    const bool wasUnsavedChanges = hasUnsavedChanges();

    beginResetModel();
    m_protocolConfig.serverConfig = amnezia::XrayServerConfig{};
    applyDefaultsToServerConfig(m_protocolConfig.serverConfig);
    endResetModel();

    if (wasUnsavedChanges != hasUnsavedChanges()) {
        emit hasUnsavedChangesChanged();
    }
}

void XrayConfigModel::applyServerConfig(const amnezia::XrayServerConfig &serverConfig)
{
    const bool wasUnsavedChanges = hasUnsavedChanges();

    beginResetModel();
    m_protocolConfig.serverConfig = serverConfig;
    // Clear client config since server settings changed
    m_protocolConfig.clearClientConfig();
    m_originalProtocolConfig = m_protocolConfig;
    endResetModel();

    if (wasUnsavedChanges != hasUnsavedChanges()) {
        emit hasUnsavedChangesChanged();
    }
}

QStringList XrayConfigModel::flowOptions()
{
    return {
        "",                        // Empty (no flow)
        "xtls-rprx-vision",
        "xtls-rprx-vision-udp443"
    };
}

QStringList XrayConfigModel::securityOptions()
{
    return { "none", "tls", "reality" };
}

QStringList XrayConfigModel::transportOptions()
{
    return { "raw", "xhttp", "mkcp" };
}

QStringList XrayConfigModel::fingerprintOptions()
{
    return { "chrome", "firefox", "safari", "ios", "android", "edge", "360", "qq", "random" };
}

QStringList XrayConfigModel::alpnOptions()
{
    return { "HTTP/2", "HTTP/1.1", "HTTP/2,HTTP/1.1" };
}

QStringList XrayConfigModel::xhttpModeOptions()
{
    return { "Auto", "Packet-up", "Stream-up", "Stream-one" };
}

QStringList XrayConfigModel::xhttpHeadersTemplateOptions()
{
    return { "HTTP", "None" };
}

QStringList XrayConfigModel::xhttpUplinkMethodOptions()
{
    return { "POST", "PUT", "PATCH" };
}

QStringList XrayConfigModel::xhttpSessionPlacementOptions()
{
    return { "Path", "Header", "Cookie", "None" };
}

QStringList XrayConfigModel::xhttpSessionKeyOptions()
{
    return { "Path", "Header", "None" };
}

QStringList XrayConfigModel::xhttpSeqPlacementOptions()
{
    return { "Path", "Header", "Cookie", "None" };
}

QStringList XrayConfigModel::xhttpUplinkDataPlacementOptions()
{
    // Matches splithttp uplink payload placement (packet-up / advanced)
    return { "Body", "Auto", "Header", "Cookie" };
}

QStringList XrayConfigModel::xPaddingPlacementOptions()
{
    // Xray-core: cookie | header | query | queryInHeader (not "body")
    return { "Cookie", "Header", "Query", "Query in header" };
}

QStringList XrayConfigModel::xPaddingMethodOptions()
{
    return { "Repeat-x", "Tokenish" };
}

QString XrayConfigModel::mkcpDefaultTti()
{
    return QString::fromLatin1(protocols::xray::defaultMkcpTti);
}

QString XrayConfigModel::mkcpDefaultUplinkCapacity()
{
    return QString::fromLatin1(protocols::xray::defaultMkcpUplinkCapacity);
}

QString XrayConfigModel::mkcpDefaultDownlinkCapacity()
{
    return QString::fromLatin1(protocols::xray::defaultMkcpDownlinkCapacity);
}

QString XrayConfigModel::mkcpDefaultReadBufferSize()
{
    return QString::fromLatin1(protocols::xray::defaultMkcpReadBufferSize);
}

QString XrayConfigModel::mkcpDefaultWriteBufferSize()
{
    return QString::fromLatin1(protocols::xray::defaultMkcpWriteBufferSize);
}
