#include "mainwindow.h"
#include "ui_mainwindow.h"


/**
 * @brief   切换到开卡页面
 *  点击工具栏的“开卡”触发，切换到开卡页面。
 * @param   void
 * @return  void
 * @author  柯劲帆
 * @date    2024-07-28
 */
void MainWindow::on_NewCardAction_triggered()
{
    ui->stackedWidget->setCurrentWidget(ui->newCardPage);
}


/**
 * @brief   读卡器扫描卡片
 *  点击开卡页面的“查询”触发。
 *  如果读卡器未连接，显示警告信息并跳转到设置页面。
 *  显示Inventory的查询结果，最多显示10张卡。
 * @param   void
 * @return  void
 * @author  柯劲帆
 * @date    2024-07-29
 */
void MainWindow::on_inventoryButton_clicked()
{
    if (!reader.is_connected())
    {
        QMessageBox::warning(this, QString("提示"), QString("读卡器未连接，请设置。"));
        if (ui->stackedWidget->currentWidget() != ui->settingPage)
        {
            ui->stackedWidget->setCurrentWidget(ui->settingPage);
        }
        return;
    }

    QStringList cardIdList = reader.inventory(10);  // 最多显示10张卡
    ui->cardIdBox->clear();
    if (cardIdList.empty())
    {
        QMessageBox::warning(this, "提示", "未发现卡片，请将卡片放置于读卡器上方。");
    }
    else
    {
        ui->cardIdBox->addItems(cardIdList);
    }

}


/**
 * @brief   开卡
 * 如果数据库未连接，显示警告信息并跳转到设置页面。
 * 该函数在用户点击“确认”按钮时触发，用于处理新卡片的注册和绑定操作。
 * 函数首先检查是否选择了卡片和填写了用户ID，然后进行各种验证和数据库操作，确保卡片和用户的状态正确。
 * 如果所有操作成功，最终将新卡片和用户绑定。
 * @details
 * 如果数据库未连接，显示警告信息并跳转到设置页面。
 * 函数首先从用户界面中获取选择的卡片ID和填写的用户ID，并进行非空检查。
 * 如果未选择卡片或未填写用户ID，则弹出警告对话框并退出函数。
 * 然后，函数会查询数据库，验证卡片和用户的状态，并根据不同情况进行相应处理：
 * - 如果卡片已被启用，提示用户开卡失败。
 * - 如果卡片已挂失，需要用户输入密码进行验证和重开卡。
 * - 如果卡片未在数据库中，则将其初始化为未启用状态。
 * 函数还会检查用户是否为新用户，如果是新用户，则插入新用户记录并绑定卡片。
 * 如果用户已有卡片，则需要根据卡片状态决定是否允许开新卡或进行移资操作。
 * @param   void
 * @return  void
 * @author  柯劲帆
 * @date    2024-07-29
 */
void MainWindow::on_newCardButton_clicked()
{
    if (!softwareReady())
    {
        QMessageBox::warning(this, QString("提示"), QString("数据库未连接，请设置。"));
        if (ui->stackedWidget->currentWidget() != ui->settingPage)
        {
            ui->stackedWidget->setCurrentWidget(ui->settingPage);
        }
        return;
    }

    if (ui->cardIdBox->currentIndex() == -1)
    {
        QMessageBox::warning(this, "提示", "请放置卡片并点击查询按钮。");
        return;
    }

    if (!device.is_depositAllowed())
    {
        QMessageBox::warning(this, QString("提示"), QString("本设备不支持开卡。"));
        return;
    }

    if (!newCardUserIdFilled)
    {
        QMessageBox::warning(this, "提示", "请填写学/工号。");
        return;
    }

    QString cardIdSelected = ui->cardIdBox->currentText();
    int userId = ui->userIdBox->value();

    // 查询卡片能否使用
    QSqlQuery query(db->getDatabase());
    query.finish();
    query.prepare(QString("select `status`, userId from card "
                  "where id = :cardId;"));
    query.bindValue(":cardId", cardIdSelected);
    bool success = query.exec();
    if (!success)
    {
        qDebug() << "select `status`, userId from card where id = :cardId;1";
        QMessageBox::warning(this, "提示", QString("数据库异常。开卡失败，请重试。"));
        return;
    }
    int cardStatus; ///< 0未启用；1已启用；-1已挂失
    if (query.next())
    {
        cardStatus = query.value("status").toInt();
        if (cardStatus == 1)    // 已被启用，不能开卡
        {
            QMessageBox::warning(this, "卡状态提示", "本卡已被启用，开卡失败。");
            return;
        }
        if (cardStatus == -1)   // 已被挂失，需要密码重开
        {
            // 弹出验证用户界面，要求用户输入密码（注意这里需要查询数据库得到挂失卡的用户）
            int cardUserId = query.value("userId").toInt();
            query.finish();
            query.prepare("select `name` from `user` "
                          "where id = :userId");
            query.bindValue(":userId", cardUserId);
            bool success = query.exec();
            if (!success)
            {
                QMessageBox::warning(this, "提示", QString("数据库异常。\n重开卡失败，请重试。"));
                return;
            }
            if (!query.next())
            {
                QMessageBox::warning(this, "提示", QString("数据库异常。\n重开卡失败，请重试。"));
                return;
            }
            QString cardUserName = query.value("name").toString();
            QString prompt = QString("本卡关联的学/工号为") + QString::number(cardUserId);
            prompt += QString("，关联的姓名为") + cardUserName + QString("。\n");
            prompt += QString("如需重开本卡，请输入密码。");
            QString info;
            success = verifyUser(cardUserId, prompt, info);
            if (!success)
            {
                QMessageBox::warning(this, "提示", info + QString("\n验证用户失败，请重试。"));
                return;
            }

            success = reopenCard(cardIdSelected, info);
            if (!success)
            {
                QMessageBox::warning(this, "提示", info + QString("\n重开卡失败，请重试。"));
                return;
            }

            return;
        }
    }
    else    // 该卡不在数据库中，将该卡在数据库中初始化，设置为未启用
    {
        query.finish();
        query.prepare("insert into card "
                      "values (:cardId, 0, 0.00, null);");
        query.bindValue(":cardId", cardIdSelected);
        success = query.exec();
        if (!success)
        {
            QMessageBox::warning(this, "提示", QString("数据库异常。开卡失败，请重试。"));
            return;
        }

        success = reader.initCard(cardIdSelected);  // 初始化卡
        if (!success)
        {
            QMessageBox::warning(this, "提示", QString("读卡器设备异常。写卡失败，请重试。"));
            return;
        }
    }

    // 检查是否是新用户
    query.finish();
    query.prepare("select 1 from `user` "
                  "where id = :userId;");
    query.bindValue(":userId", userId);
    success = query.exec();
    if (!success)
    {
        qDebug() << "select 1 from `user` where id = :userId;";
        QMessageBox::warning(this, "提示", QString("数据库异常。开卡失败，请重试。"));
        return;
    }
    if (!query.next())  // 库中无用户记录，将新用户插入数据库并开卡
    {
        QString info;
        bool success = createUser(userId, info);    // 将新用户插入数据库
        if (!success)
        {
            QMessageBox::warning(this, "提示", info + QString("\n注册失败，请重新开卡。"));
            return;
        }
        success = bindUserWithCard(userId, cardIdSelected, info);   // 将新用户和卡绑定（卡在读取时已写入数据库）
        if (!success)
        {
            QMessageBox::warning(this, "提示", info + QString("\n注册失败，请重新开卡。"));
            return;
        }
        QMessageBox::information(this, "提示", "新用户开卡成功。");

        success = reader.initCard(cardIdSelected);  // 初始化卡
        if (!success)
        {
            QMessageBox::warning(this, "提示", QString("读卡器设备异常。写卡失败，请重试。"));
            return;
        }

        return;
    }
    else    // 库中有用户记录
    {
        // 检查用户是否有卡，不可开卡/有挂失卡，是否需要移资
        query.finish();
        query.prepare("select `status`, id from card "
                      "where userId = :userId;");
        query.bindValue(":userId", userId);
        bool success = query.exec();
        if (!success)
        {
            qDebug() << "select `status`, id from card where userId = :userId;2";
            QMessageBox::warning(this, "提示", QString("数据库异常。开卡失败，请重试。"));
            return;
        }
        if (query.next())  // 用户有卡
        {
            int userCardStatus = query.value("status").toInt();
            if (userCardStatus == 1)    // 用户已启用卡
            {
                QMessageBox::warning(this, "提示", "该用户已有正在使用的卡，开卡失败。\n如需开新卡，请挂失正在使用的卡。");
                return;
            }
            else if (userCardStatus == -1)  // 用户有挂失卡，需要移资
            {
                // 弹出验证用户界面，要求用户输入密码；在数据库中将挂失卡的信息和消费记录移到新卡
                QString info, prompt = QString("如需将挂失卡移资到本卡，请输入密码。");
                bool success = verifyUser(userId, prompt, info);
                if (!success)
                {
                    QMessageBox::warning(this, "提示", info + QString("\n验证用户失败。移资失败，请重试。"));
                    return;
                }

                QString userCardId = query.value("id").toString();
                success = transferCard(userId, cardIdSelected, userCardId, info);
                if (!success)
                {
                    QMessageBox::warning(this, "提示", info + QString("\n移资失败，请重试。"));
                    return;
                }

                success = reader.initCard(cardIdSelected);  // 初始化卡
                if (!success)
                {
                    QMessageBox::warning(this, "提示", QString("读卡器设备异常。写卡失败，请重试。"));
                    return;
                }

                QMessageBox::information(this, "提示", "移资成功。");
                return;
            }
        }
        else    // 用户无卡
        {
            QString info;
            bool success = bindUserWithCard(userId, cardIdSelected, info);   // 将新用户和卡绑定（卡在读取时已写入数据库）
            if (!success)
            {
                QMessageBox::warning(this, "提示", info + QString("\n注册失败，请重新开卡。"));
                return;
            }

            success = reader.initCard(cardIdSelected);  // 初始化卡
            if (!success)
            {
                QMessageBox::warning(this, "提示", QString("读卡器设备异常。写卡失败，请重试。"));
                return;
            }

            QMessageBox::information(this, "提示", "新用户开卡成功。");
            return;
        }
    }
}


/**
 * @brief   绑定用户和卡号
 * 该函数用于在数据库中绑定用户和卡号。函数将更新卡片的状态、余额和用户ID。
 * 如果操作失败，函数将返回失败并设置错误信息。
 * @details
 * 函数首先创建一个QSqlQuery对象，并准备一个更新卡片信息的SQL语句，将卡片的状态设置为启用（1），余额设置为0，
 * 并将用户ID绑定到卡片ID。如果SQL执行失败，函数会将错误信息写入info并返回false。
 * @param   userId  要绑定的用户的学/工号
 * @param   cardId  要绑定的卡号
 * @param   info    如果出现异常，填入异常信息
 * @return  是否绑定成功
 * - true   成功
 * - false  失败
 * @author  柯劲帆
 * @date    2024-07-30
 */
bool MainWindow::bindUserWithCard(int userId, QString cardId, QString &info)
{
    QSqlQuery query(db->getDatabase());
    query.finish();
    query.prepare(QString("update card "
                          "set `status` = :status, balance = :balance, userId = :userId "
                          "where id = :cardId;"));
    query.bindValue(":status", 1);
    query.bindValue(":balance", (double)0.00);
    query.bindValue(":userId", userId);
    query.bindValue(":cardId", cardId);
    bool updateCardIsExecuted = query.exec();
    if (!updateCardIsExecuted)
    {
        info = QString("数据库异常。");
        return false;
    }

    return true;
}


/**
 * @brief   在数据库中创建新用户
 * 该函数用于在数据库中创建新用户。首先，它调用 `getNewUserInfo` 函数获取新用户的姓名和密码。
 * 然后，将这些信息与用户ID一起插入到数据库中。如果任何步骤失败，函数将返回失败并设置错误信息。
 * @details
 * 函数首先调用 `getNewUserInfo` 获取新用户的姓名和密码。如果获取信息失败，函数立即返回失败并设置错误信息。
 * 然后，函数准备并执行一个插入新用户信息的SQL语句，将用户ID、姓名和密码插入到数据库中的 `user` 表。
 * 如果SQL执行失败，函数返回失败并设置错误信息。
 * @param   userId  要创建的用户的学/工号
 * @param   info    如果出现异常，填入异常信息
 * @return  bool    是否创建成功
 * - true   成功
 * - false  失败
 * @author  柯劲帆
 * @date    2024-07-30
 */
bool MainWindow::createUser(int userId, QString &info)
{
    QString username, password;
    if (!getNewUserInfo(username, password, info)) return false;

    QSqlQuery query(db->getDatabase());
    query.finish();
    query.prepare(QString("insert into `user` "
                          "values (:id, :name, :password);"));
    query.bindValue(":id", userId);
    query.bindValue(":name", username);
    query.bindValue(":password", password);
    bool insertUserIsExecuted = query.exec();
    if (!insertUserIsExecuted)
    {
        info = QString("数据库异常。");
        return false;
    }

    return true;
}


/**
 * @brief   弹出交互窗口获取新用户的信息
 * 该函数弹出多个输入对话框，依次获取新用户的姓名和密码，并进行简单验证。
 * 如果用户在任一步骤取消输入或输入无效，函数将返回失败并设置错误信息。
 * @details
 * 函数依次弹出三个输入对话框，获取新用户的姓名、密码和确认密码。
 * - 如果用户取消输入或输入为空，函数返回失败并设置错误信息。
 * - 如果两次输入的密码不一致，函数返回失败并设置错误信息。
 * - 如果所有输入均有效，函数返回成功。
 * @param   username    获取的新用户的姓名
 * @param   password    获取的新用户的密码
 * @param   info    如果出现异常，填入异常信息
 * @return  bool    是否获取成功
 * - true   成功
 * - false  失败
 * @author  柯劲帆
 * @date    2024-07-30
 */
bool MainWindow::getNewUserInfo(QString &username, QString &password, QString &info)
{
    bool ok = false;
    username = QInputDialog::getText(this, tr("新用户注册"), tr("请输入姓名"), QLineEdit::Normal, 0, &ok);
    if (!ok || username.isEmpty())
    {
        info = QString("输入信息异常。");
        return false;
    }
    ok = false;
    password = QInputDialog::getText(this, tr("新用户注册"), tr("请输入密码"), QLineEdit::Password, 0, &ok);
    if (!ok || password.isEmpty())
    {
        info = QString("输入信息异常。");
        return false;
    }
    ok = false;
    QString confirmPassword = QInputDialog::getText(this, tr("新用户注册"), tr("请再次输入密码"), QLineEdit::Password, 0, &ok);
    if (!ok || confirmPassword.isEmpty())
    {
        info = QString("输入信息异常。");
        return false;
    }
    if (confirmPassword != password)
    {
        info = QString("两次输入的密码不一致。");
        return false;
    }

    return true;
}


/**
 * @brief   弹出交互窗口认证用户，在数据库中比对
 * 该函数用于弹出一个窗口要求用户输入密码，并在数据库中比对用户信息以验证身份。
 * 如果用户输入正确的密码，则认证成功，否则认证失败。
 * @details
 * 函数首先弹出一个输入对话框，要求用户输入密码。如果用户取消输入或未输入密码，函数返回认证失败并设置错误信息。
 * 然后，函数在数据库中查询对应的用户ID和密码，并与用户输入的密码进行比对。如果密码正确，则返回认证成功。
 * 否则，函数返回认证失败并设置相应的错误信息。
 * @param   userId  要认证的用户学/工号
 * @param   prompt  在交互窗口的提示文字
 * @param   info    如果出现异常，填入异常信息
 * @return  bool    是否认证成功
 * - true   成功
 * - false  失败
 * @author  柯劲帆
 * @date    2024-07-30
 */
bool MainWindow::verifyUser(int userId, QString prompt, QString &info)
{
    bool ok = false;
    QString text;
    if (prompt.isEmpty()) text = QString("请输入密码");
    else text = prompt + QString("\n请输入密码");
    QString password = QInputDialog::getText(this, tr("验证用户"), text, QLineEdit::Password, 0, &ok);
    if (!ok || password.isEmpty())
    {
        info = QString("输入信息异常。");
        return false;
    }

    QSqlQuery query(db->getDatabase());
    query.finish();
    query.prepare(QString("select `password` from `user` "
                          "where id = :id;"));
    query.bindValue(":id", userId);
    if (!query.exec())
    {
        info = QString("数据库异常。");
        return false;
    }
    if (query.next())
    {
        QString validPassword = query.value(0).toString();
        if (validPassword != password)
        {
            info = QString("密码不正确。");
            return false;
        }
        else
        {
            return true;
        }
    }
    else
    {
        info = QString("学/工号不存在。");
        return false;
    }
}


/**
 * @brief   移资，将旧卡信息和记录移到新卡，删除旧卡信息
 * 该函数用于将旧卡的余额和相关记录移到新卡，并删除旧卡的信息。函数执行以下步骤：
 * - 查询旧卡的余额，并将其转移到新卡。
 * - 更新新卡的状态、余额和用户ID。
 * - 更新所有记录，将旧卡ID替换为新卡ID。
 * - 删除旧卡的信息
 * @details
 * 函数首先查询旧卡的余额，并在执行过程中进行多次数据库操作，以确保所有相关信息和记录都正确地从旧卡移到新卡。
 * 如果任何操作失败，函数会将错误信息写入info并返回false。
 * @param   userId      新旧卡所属的用户的学/工号
 * @param   newCardId   新卡的ID
 * @param   oldCardId   旧卡的ID
 * @param   info    如果出现异常，填入异常信息
 * @return  bool    是否移资成功
 * - true   成功
 * - false  失败
 * @author  柯劲帆
 * @date    2024-07-30
 */
bool MainWindow::transferCard(int userId, QString newCardId, QString oldCardId, QString &info)
{
    QSqlQuery query(db->getDatabase());
    bool isExecuted = false;

    // 查询旧卡余额
    query.finish();
    query.prepare("select balance from card "
                  "where id = :cardId;");
    query.bindValue(":cardId", oldCardId);
    isExecuted = query.exec();
    if (!isExecuted)
    {
        info = QString("数据库异常。");
        return false;
    }
    if (!query.next())
    {
        info = QString("数据库异常。");
        return false;
    }
    double balance = query.value("balance").toDouble();

    // 更新新卡信息
    query.finish();
    query.prepare(QString("update card "
                          "set `status` = :status, balance = :balance, userId = :userId "
                          "where id = :cardId;"));
    query.bindValue(":status", 1);
    query.bindValue(":balance", balance);
    query.bindValue(":userId", userId);
    query.bindValue(":cardId", newCardId);
    isExecuted = query.exec();
    if (!isExecuted)
    {
        info = QString("数据库异常。");
        return false;
    }

    // 更新所有记录，将旧卡ID替换为新卡ID
    query.finish();
    query.prepare(QString("update record "
                          "set cardId = :newCardId "
                          "where cardId = :oldCardId;"));
    query.bindValue(":newCardId", newCardId);
    query.bindValue(":oldCardId", oldCardId);
    isExecuted = query.exec();
    if (!isExecuted)
    {
        info = QString("数据库异常。");
        return false;
    }

    // 删除旧卡信息
    query.finish();
    query.prepare(QString("delete from card "
                          "where id = :cardId;"));
    query.bindValue(":cardId", oldCardId);
    isExecuted = query.exec();
    if (!isExecuted)
    {
        info = QString("数据库异常。");
        return false;
    }

    return true;
}


/**
 * @brief   重开卡
 * 该函数用于重新启用一张已挂失的卡片。卡片信息和记录都保留在数据库中，只需将卡片的状态从-1（挂失）更改为1（启用）。
 * @details
 * 函数首先创建一个QSqlQuery对象，并准备一个更新卡片状态的SQL语句，将指定卡片的状态更改为启用状态（1）。
 * 如果SQL执行失败，函数会将错误信息写入info并返回false。
 * @param   userId  卡所属的用户的学/工号
 * @param   info    如果出现异常，填入异常信息
 * @return  bool    是否重开卡成功
 * - true   成功
 * - false  失败
 * @author  柯劲帆
 * @date    2024-07-30
 */
bool MainWindow::reopenCard(QString cardId, QString &info)
{
    // 卡信息和记录都还在数据库中，只是status为-1，更改即可
    QSqlQuery query(db->getDatabase());
    query.finish();
    query.prepare("update card "
                  "set `status` = 1 "
                  "where id = :cardId");
    query.bindValue(":cardId", cardId);
    bool success = query.exec();
    if (!success)
    {
        info = QString("数据库异常。");
        return false;
    }

    return true;
}
