#include "userinfoinputview.h"

#include <vector>

#include <DSpinner>

#include <QDebug>
#include <QTcpSocket>
#include <QBuffer>
#include <QImageReader>
#include <QtMath>
#include <QBoxLayout>
#include <QGridLayout>
#include <QLineEdit>
#include <QLabel>
#include <QPushButton>
#include <QComboBox>
#include <QApplication>

#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QFile>
#include <QTimer>

#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QStandardPaths>
#include <QDir>

#include <jcon/json_rpc_tcp_client.h>
#include <jcon/json_rpc_tcp_server.h>
#include <jcon/json_rpc_tcp_socket.h>
#include <jcon/json_rpc_request.h>
#include <jcon/json_rpc_result.h>
#include <jcon/json_rpc_debug_logger.h>

#include <DThemeManager>

#include "systeminfo.h"
#include "loader.h"
#include "widget/searchresult.h"
#include "widget/statuswidget.h"

#include "search.h"

class OrganizationItem
{
public:
    OrganizationItem(const QString &v)
    {
        value = v;
    }

    QString id;
    QString value;
};


class UserInfoInputViewPrivate
{
public:
    UserInfoInputViewPrivate(UserInfoInputView *parent) : q_ptr(parent)
    {
        organizationSearch = new Dtk::Util::Search;

        rpcClinet = new jcon::JsonRpcTcpClient(parent, nullptr, 15000);
        rpcClinet->connectToServer("127.0.0.1", 9300);

        QueryCityNames();
        QueryOrgcodes();
        QueryUserInfo();

        rpcServer = new jcon::JsonRpcTcpServer(parent, std::make_shared<jcon::JsonRpcDebugLogger>());
        rpcServer->registerServices({parent});
        rpcServer->listen();

        try {
            std::shared_ptr<jcon::JsonRpcResult> reply = rpcClinet->call(
                        "RegisterClientPort",
                        (int)rpcServer->port()
                    );
        } catch (...) {
            qCritical() << "registerClientPort failed";
            // TODO: fallback
            return;
        }
        qDebug() << rpcServer->port();
    }

    void QueryCityNames()
    {
        try {
            std::shared_ptr<jcon::JsonRpcResult> reply = rpcClinet->call("QueryCityNames");
            QStringList names = reply->result().toStringList();
            arealist = names;
            qDebug() << "QueryCityNames" << reply->result() << arealist;
        } catch (...) {
            qCritical() << "QueryCityNames failed";
            // TODO: fallback
            return;
        }
    }

    void QueryOrgcodes()
    {
        try {
            std::shared_ptr<jcon::JsonRpcResult> reply = rpcClinet->call("QueryOrgcodes");
            auto orgs = reply->result().toMap();

            for (auto orgCode : orgs.keys()) {
                auto orgName = orgs.value(orgCode).toString();
                organizations[orgName] = orgCode;
                auto item = new OrganizationItem(orgName);
                organizationSearch->addEntry(item->value, item);
            }

        } catch (...) {
            qCritical() << "QueryOrgcodes failed";
            // TODO: fallback
            return;
        }
        qDebug() << "organizations: " << organizations;
    }

    void QueryUserInfo()
    {
        try {
            std::shared_ptr<jcon::JsonRpcResult> reply = rpcClinet->call("QueryUserInfo");
            qDebug() << reply->result().toMap();
            auto obj = QJsonObject::fromVariantMap(reply->result().toMap());
            userInfo = UserInfo::fromObject(obj);
        } catch (...) {
            qCritical() << "QueryOrgcodes failed";
            // TODO: fallback
            return;
        }
        qDebug() << "userInfo: " << userInfo.toJson();
    }

    bool connetToServer()
    {
        bool isConnected = rpcClinet->isConnected();
        if (!isConnected) {
            isConnected = rpcClinet->connectToServer("127.0.0.1", 9300);
            qDebug() << "re connet to 127.0.0.1:9300" << isConnected;
            if (isConnected) {
                try {
                    std::shared_ptr<jcon::JsonRpcResult> reply = rpcClinet->call(
                                "RegisterClientPort",
                                (int)rpcServer->port()
                            );
                } catch (...) {
                    qCritical() << "registerClientPort failed";
                }
            }
        }
        return isConnected;
    }

    void switchWaitingUI()
    {
        auto status = true;
        qrBt->setDisabled(status);
        usernameEdit->setDisabled(status);
        orgnameEdit->setDisabled(status);
        departmentEdit->setDisabled(status);
        roomNumberEdit->setDisabled(status);
        submitButton->setDisabled(status);
        submitButton->hide();
        spinner->show();
        spinner->start();
        showMessage(UserInfoInputView::tr("Query system information, please wait..."),
                    false,
                    -1);
    }

    void switchNormalUI()
    {
        auto status = false;
        qrBt->setDisabled(status);
        usernameEdit->setDisabled(status);
        orgnameEdit->setDisabled(status);
        departmentEdit->setDisabled(status);
        roomNumberEdit->setDisabled(status);
        submitButton->setDisabled(status);
        submitButton->show();
        spinner->stop();
        spinner->hide();
    }

    void showMessage(const QString &msg, bool failed, int timeout = 5000)
    {
        Q_Q(UserInfoInputView);
        tipLabel->setText(msg);
        tipLabel->setObjectName(failed ? "FailLabel" : "SuccessLabel");
        q->setStyleSheet(q->styleSheet());
        QTimer::singleShot(timeout, [ = ]() {
            tipLabel->setText("");
        });
    }

    void cleanMessage()
    {
        tipLabel->setText("");
    }

    QNetworkAccessManager   manager;
    QWidget                 *qrWidget       = Q_NULLPTR;
    QPushButton             *qrBt           = Q_NULLPTR;
    QWidget                 *infoFrame      = Q_NULLPTR;
    QWidget                 *qrFrame        = Q_NULLPTR;
    StatusWidget            *statusFrame    = Q_NULLPTR;
    QLineEdit               *orgnameEdit    = Q_NULLPTR;
    QLineEdit               *usernameEdit   = Q_NULLPTR;
    QLineEdit               *departmentEdit = Q_NULLPTR;
    QLineEdit               *roomNumberEdit = Q_NULLPTR;
    QLabel                  *tipLabel       = Q_NULLPTR;
    QLabel                  *qrLabel        = Q_NULLPTR;
    SearchResult            *searchResult   = Q_NULLPTR;
    QPushButton             *submitButton   = Q_NULLPTR;
    QPushButton             *backButton     = Q_NULLPTR;
    Dtk::Widget::DSpinner   *spinner        = Q_NULLPTR;

    Dtk::Util::Search       *organizationSearch = Q_NULLPTR;
    jcon::JsonRpcTcpClient  *rpcClinet = Q_NULLPTR;
    jcon::JsonRpcTcpServer  *rpcServer = Q_NULLPTR;

    QMap<QString, QString> organizations;
    QStringList arealist;

    QString provinceName;
    QString cityName;
    QString districtName;

    UserInfo userInfo;

    UserInfoInputView *q_ptr;
    Q_DECLARE_PUBLIC(UserInfoInputView)
};

UserInfoInputView::UserInfoInputView(QWidget *parent) :
    QWidget(parent), dd_ptr(new UserInfoInputViewPrivate(this))
{
    Q_D(UserInfoInputView);

    Dtk::Widget::DThemeManager::registerWidget(this);
#ifdef Q_OS_WIN
    setStyleSheet(styleSheet() + "QComboBox:editable {padding-left:5px;padding-right:5px;}");
#endif

    auto layout = new QVBoxLayout;
    layout->setContentsMargins(0, 0, 0, 0);

    auto areainfos = d->arealist;
    QString districtName = areainfos.isEmpty() ? "" : areainfos.takeLast();

    areainfos.removeAll("省直辖");
    areainfos.removeAll("");

    auto title = districtName;
    auto titleLabel = new QLabel(tr("Legalization Checker"));
    titleLabel->setObjectName("UserInfoTitle");
    titleLabel->setText(title);

    // info frame
    d->infoFrame = new QWidget;
    auto infoLayout = new QVBoxLayout(d->infoFrame);
    infoLayout->setContentsMargins(0, 0, 0, 0);

    d->qrWidget = new QWidget;
    auto qrBtLayout = new QHBoxLayout(d->qrWidget);
    qrBtLayout->setContentsMargins(0, 12, 0, 12);

    d->qrBt = new QPushButton;
    d->qrBt->setObjectName("UserInfoQRButton");
    d->qrBt->setFixedSize(26, 26);
    qrBtLayout->addWidget(d->qrBt);

    auto gridLayout = new QHBoxLayout;

    if (!areainfos.isEmpty()) {
        gridLayout->addStretch();
        for (auto area : areainfos.join(" >> ").split(" ")) {
            gridLayout->addWidget(new QLabel(area));
        }
        gridLayout->addStretch();
    }

    d->orgnameEdit = new QLineEdit;
    d->orgnameEdit->setMinimumSize(310, 32);
    d->orgnameEdit->setMaximumHeight(100);
    d->orgnameEdit->setText(d->userInfo.orgcode);
    d->usernameEdit = new QLineEdit;
    d->usernameEdit->setMaxLength(20);
    d->usernameEdit->setMinimumSize(310, 32);
    d->departmentEdit = new QLineEdit;
    d->departmentEdit->setMaxLength(20);
    d->departmentEdit->setMinimumSize(310, 32);
    d->roomNumberEdit = new QLineEdit;
    d->roomNumberEdit->setMaxLength(20);
    d->roomNumberEdit->setMinimumSize(310, 32);

    d->tipLabel = new QLabel();
    d->tipLabel->setAlignment(Qt::AlignVCenter);
    d->tipLabel->setObjectName("NormalLabel");

    d->submitButton = new QPushButton(tr("Submit"));
    d->submitButton->setMinimumSize(310, 36);

    d->spinner = new Dtk::Widget::DSpinner;
    d->spinner->setFixedSize(36, 36);
    d->spinner->hide();

    infoLayout->addSpacing(8);
//    infoLayout->addWidget(inputHint, 0, Qt::AlignCenter);
    auto inputWidget = new QWidget;
    inputWidget->setMaximumWidth(320);
    auto inputLayout = new QVBoxLayout(inputWidget);
    inputLayout->setContentsMargins(0, 0, 0, 0);
    inputLayout->addWidget(new QLabel(tr("Organization:")), 0, Qt::AlignVCenter | Qt::AlignLeft);
    inputLayout->addSpacing(2);
    inputLayout->addWidget(d->orgnameEdit, 0, Qt::AlignCenter);
    inputLayout->addSpacing(4);
    inputLayout->addWidget(new QLabel(tr("Name:")), 0, Qt::AlignVCenter | Qt::AlignLeft);
    inputLayout->addSpacing(2);
    inputLayout->addWidget(d->usernameEdit, 0, Qt::AlignCenter);
    inputLayout->addSpacing(4);
    inputLayout->addWidget(new QLabel(tr("Room Number:")), 0, Qt::AlignVCenter | Qt::AlignLeft);
    inputLayout->addSpacing(2);
    inputLayout->addWidget(d->roomNumberEdit, 0, Qt::AlignCenter);
    inputLayout->addSpacing(4);
    inputLayout->addWidget(new QLabel(tr("Department:")), 0, Qt::AlignVCenter | Qt::AlignLeft);
    inputLayout->addSpacing(2);
    inputLayout->addWidget(d->departmentEdit, 0, Qt::AlignCenter);
    infoLayout->addWidget(inputWidget, 0, Qt::AlignCenter);
    infoLayout->addStretch();
    infoLayout->addWidget(d->tipLabel, 0, Qt::AlignCenter);
    infoLayout->addStretch();
    infoLayout->addWidget(d->submitButton, 0, Qt::AlignCenter);
    infoLayout->addWidget(d->spinner, 0, Qt::AlignCenter);

    // qr frame
    d->qrFrame = new QWidget;
    auto qrLayout = new QVBoxLayout(d->qrFrame);
    qrLayout->setContentsMargins(0, 0, 0, 0);

    d->qrLabel = new QLabel;
    d->qrLabel->setObjectName("UserInfoQRCode");
    d->qrLabel->setFixedSize(300, 300);

    auto qrHint = new QLabel(tr("Please scan QR code to view information and feedback"));
    qrHint->setObjectName("UserInfoQRHint");

    d->backButton = new QPushButton(tr("Back"));
    d->backButton->setMinimumSize(310, 36);

    qrLayout->addStretch();
    qrLayout->addWidget(d->qrLabel, 0, Qt::AlignCenter);
    qrLayout->addWidget(qrHint, 0, Qt::AlignCenter);
    qrLayout->addStretch();
    qrLayout->addWidget(d->backButton, 0, Qt::AlignCenter);

    // status frame
    d->statusFrame = new StatusWidget;
    d->statusFrame->hide();

    layout->addSpacing(5);
    layout->addWidget(titleLabel, 0, Qt::AlignCenter);
    layout->addWidget(d->qrWidget, 0, Qt::AlignCenter);
    layout->addLayout(gridLayout);
    layout->addWidget(d->infoFrame);
    layout->addWidget(d->qrFrame);
    layout->addWidget(d->statusFrame);
    layout->addSpacing(40);

    d->qrFrame->hide();

    d->searchResult = new SearchResult(this);
    d->searchResult->hide();

    loadFormData();
    setLayout(layout);
    initConnect();

    qApp->installEventFilter(this);
}

void UserInfoInputView::initConnect()
{
    Q_D(UserInfoInputView);

    connect(d->qrBt, &QPushButton::clicked, this, &UserInfoInputView::showQRCode);
    connect(d->orgnameEdit, &QLineEdit::textEdited,
    d->searchResult, [ = ](const QString & str) {
        auto entrys = d->organizationSearch->findEntryValue(str);
        // only show ten
        QStringList result;
        for (auto e : entrys) {
            result << e;
            if (result.length() >= 10) {
                break;
            }
        }
        d->searchResult->setResultList(result);

        auto pos =  d->orgnameEdit->mapToGlobal(QPoint(0, d->orgnameEdit->height()));
        pos = mapFromGlobal(pos);
        d->searchResult->show();
        d->searchResult->setFixedWidth(d->orgnameEdit->width());
        d->searchResult->autoResize();
        d->searchResult->move(pos);
        d->searchResult->raise();
    });

    connect(d->orgnameEdit, &QLineEdit::editingFinished,
    d->searchResult, [ = ]() {
        d->searchResult->hide();
    });

    connect(d->searchResult, &SearchResult::itemSelected,
    d->searchResult, [ = ](const QString & value) {
        d->orgnameEdit->setText(value);
        d->searchResult->hide();
    });

    connect(d->backButton, &QPushButton::clicked,
    this, [ = ]() {
        d->infoFrame->show();
        d->qrWidget->show();
        d->qrFrame->hide();
    });

    connect(d->statusFrame, &StatusWidget::back,
    this, [ = ]() {
        d->infoFrame->show();
        d->qrFrame->hide();
        d->statusFrame->hide();
        d->qrWidget->show();
    });

    connect(d->submitButton, &QPushButton::clicked,
    this, [ = ]() {
        try {
            d->searchResult->hide();
            qDebug() << d->usernameEdit->text().isEmpty();
            qDebug() << d->departmentEdit->text().isEmpty();
            qDebug() << d->orgnameEdit->text().isEmpty();

            if (!d->organizations.contains(d->orgnameEdit->text())) {
                showOrgErrorMessage();
            } else {
                d->tipLabel->setText("");
                QString username = d->usernameEdit->text();
                QString department = d->departmentEdit->text();
                QString orgName = d->orgnameEdit->text();
                QString orgCode = d->organizations.value(orgName);
                QString roomNumber = d->roomNumberEdit->text();

                UserInfo userInfo;
                userInfo.orgcode = orgCode;
                userInfo.orgname = orgName;
                userInfo.roomNumber = roomNumber;
                userInfo.department = department;
                userInfo.ownner = username;
                d->connetToServer();

                auto reply = d->rpcClinet->callAsync("SubmitSystemInfo", userInfo.toJson());
                d->switchWaitingUI();
                connect(reply.get(), &jcon::JsonRpcRequest::result,
                this, [ = ](const QVariant & base64Data) {
                    d->switchNormalUI();
                    d->cleanMessage();

                    QByteArray json = QByteArray::fromBase64(base64Data.toString().toUtf8());
                    qDebug() << "call SubmitSystemInfo" << json;

                    QJsonObject result = QJsonDocument::fromJson(json).object();
                    auto qtNetworkErrorCode = result.value("errcode").toInt();
                    qDebug() << result << qtNetworkErrorCode;

                    auto responeData = result.value("response").toVariant().toByteArray();
                    auto offlineResult = result.value("data").toObject(); qDebug() << result;
                    auto onlineResult = QJsonDocument::fromJson(responeData).object();

                    if (QNetworkReply::NetworkError(qtNetworkErrorCode) == QNetworkReply::NoError) {
                        showStatus(offlineResult, onlineResult);
                    } else {
                        showFailMessage();
                    }
                });
            }
        } catch (...) {
            qCritical() << "call SubmitSystemInfo failed";
            // TODO: fallback
            showFailMessage();
        }
        setStyleSheet(this->styleSheet());
    });
}

void UserInfoInputView::loadFormData()
{
    Q_D(UserInfoInputView);
    d->orgnameEdit->setText(d->userInfo.orgname);
    d->usernameEdit->setText(d->userInfo.ownner);
    d->departmentEdit->setText(d->userInfo.department);
    d->roomNumberEdit->setText(d->userInfo.roomNumber);
}

UserInfoInputView::~UserInfoInputView()
{

}

void UserInfoInputView::showQRCode()
{
    Q_D(UserInfoInputView);

    d->tipLabel->setText("");
    d->searchResult->hide();

    if (!d->organizations.contains(d->orgnameEdit->text())) {
        showOrgErrorMessage();
        return;
    }


    try {
        QString username = d->usernameEdit->text();
        QString department = d->departmentEdit->text();
        QString orgName = d->orgnameEdit->text();
        QString orgCode = d->organizations.value(orgName);
        QString roomNumber = d->roomNumberEdit->text();

        UserInfo userInfo;
        userInfo.orgcode = orgCode;
        userInfo.orgname = orgName;
        userInfo.roomNumber = roomNumber;
        userInfo.department = department;
        userInfo.ownner = username;

        d->qrLabel->setPixmap(QPixmap());
        d->connetToServer();
        auto reply = d->rpcClinet->callAsync("GetQrMessage", userInfo.toJson());
        d->switchWaitingUI();
        connect(reply.get(), &jcon::JsonRpcRequest::result,
        this, [ = ](const QVariant & result) {
            d->switchNormalUI();
            d->cleanMessage();
            QString qrMessage;
            qrMessage = (result.toString());
            QByteArray buffer = qrMessage.toUtf8();
            QBuffer svgBuffer(&buffer);
            QImageReader reader(&svgBuffer);
            if (reader.canRead()) {
                reader.setScaledSize(d->qrLabel->size());
                auto pixmap = QPixmap::fromImage(reader.read());
                d->qrLabel->setPixmap(pixmap);
            }

            d->infoFrame->hide();
            d->qrWidget->hide();
            d->qrFrame->show();
        });

    } catch (...) {
        qCritical() << "call getQrMessage failed";
        // TODO: fallback
        return;
    }
}

void UserInfoInputView::showStatus(QJsonObject offlineResult, QJsonObject onlineResult)
{
    Q_D(UserInfoInputView);
    d->searchResult->hide();
    d->infoFrame->hide();
    d->qrFrame->hide();
    d->qrWidget->hide();

    d->statusFrame->updateStatus(offlineResult, onlineResult);
    d->statusFrame->show();
}

void UserInfoInputView::StartUpdate()
{
    qDebug() << "start update";
}

void UserInfoInputView::FinishUpdate()
{
    qDebug() << "finish update";
}

void UserInfoInputView::showOrgErrorMessage()
{
    Q_D(UserInfoInputView);
    d->tipLabel->setText(tr("Organization not exist, please input an vaild origanization"));
    d->tipLabel->setObjectName("FailLabel");
    setStyleSheet(this->styleSheet());
    QTimer::singleShot(5000, [ = ]() {
        d->tipLabel->setText("");
    });
}

void UserInfoInputView::showIncompleteMessage()
{
    Q_D(UserInfoInputView);
    d->tipLabel->setText(tr("Your infomation is incomplete, please input your username or department"));
    d->tipLabel->setObjectName("FailLabel");
    setStyleSheet(this->styleSheet());
    QTimer::singleShot(5000, [ = ]() {
        d->tipLabel->setText("");
    });
}

void UserInfoInputView::showFailMessage()
{
    Q_D(UserInfoInputView);
    d->tipLabel->setText(tr("Failed to submit, please check your network connection!"));
    d->tipLabel->setObjectName("FailLabel");
    setStyleSheet(this->styleSheet());
    QTimer::singleShot(5000, [ = ]() {
        d->tipLabel->setText("");
    });
}

void UserInfoInputView::showSuccessMessage()
{
    Q_D(UserInfoInputView);
    d->tipLabel->setText(tr("Your information has been submitted successfully!"));
    d->tipLabel->setObjectName("SuccessLabel");
    QTimer::singleShot(5000, [ = ]() {
        d->tipLabel->setText("");
    });
    setStyleSheet(this->styleSheet());
}

void UserInfoInputView::keyPressEvent(QKeyEvent *e)
{
    Q_D(UserInfoInputView);
    if (e->key() == Qt::Key_Escape) {
        d->searchResult->hide();
    }
}

bool UserInfoInputView::eventFilter(QObject *watched, QEvent *event)
{
    Q_D(UserInfoInputView);
    if (watched == d->orgnameEdit && d->searchResult && d->searchResult->isVisible()) {
        auto keyEvent = reinterpret_cast<QKeyEvent *>(event);
        if (keyEvent && event->type() == QEvent::KeyPress) {
            switch (keyEvent->key()) {
            case Qt::Key_Up:
                d->searchResult->selectUp();
                return true;
            case Qt::Key_Down:
                d->searchResult->selectDown();
                return true;
            case Qt::Key_Return:
            case Qt::Key_Enter:
                d->searchResult->accpectSelect();
                return true;
            default:
                break;
            }
        }
    }

    if (d->searchResult && d->searchResult->isVisible()) {
        if (event->type() == QEvent::MouseButtonPress) {
            auto mouseEvent = reinterpret_cast<QMouseEvent *>(event);
            auto pos = d->searchResult->mapFromGlobal(mouseEvent->globalPos());
            if (!d->searchResult->rect().contains(pos)) {
                d->searchResult->hide();
            }
        }
    }

//    qDebug() << watched << event->type();
    return QWidget::eventFilter(watched, event);
}
