#include "ofdscene.h"
#include "ui_ofdscene.h"
#include "utils/utils.h"

#include <QFileDialog>
#include <QMessageBox>
#include <adjustpicturedialog.h>
#include <httplib.h>
#include <outputdialog.h>
#include <qpixmap.h>
#include <solvecaptchadialog.h>

#include <net/net.h>
#include <exceptions/ofdrequestexception.h>
#include <bits/basic_string.h>
#include <qrencode.h>

#include <bits/basic_string.h>

OFDScene::OFDScene(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::OFDScene) {
    ui->setupUi(this);
    ui->stop_server_button->hide();

    QObject::connect(this, &OFDScene::httpErrorOccured, this, &OFDScene::notifyHttpServerFailure);
}

OFDScene::~OFDScene() {
    delete ui;
}

void OFDScene::startHttpServer() {
    std::string localIp = "";

    try {
        localIp = get_local_ip_address();
    } catch(std::exception e) {
        std::cerr << e.what() << std::endl;
        return;
    }

    unsigned short number_of_retries = 0;

    do {
        if (number_of_retries == 10) {
            emit httpErrorOccured();
            return;
        }
        this->port = rand() % (65535 - 1024) + 1024;

        std::string connectionString = "binaryeye://scan/?ret=http://"+ localIp +":"+ std::to_string(port) +"/?result={RESULT}";

        server.Get("/", [&](const httplib::Request &req, httplib::Response &res){
            std::cout << "New http connection" <<std::endl;
            std::map<std::string, std::string> paramsMap;
            if (req.params.size() < 1) {
                res.set_redirect(connectionString, 301);
                std::cerr << "Too few params: " << req.params.size() << std::endl;
                return;
            }
            std::string result = req.params.find("result")->second;
            std::vector<std::string> dataSplit = split(result, "&");
            for (std::string &pair : dataSplit) {
                std::vector<std::string> values = split(pair, "=");
                paramsMap.insert(std::pair<std::string, std::string>(values[0], values[1]));
            }

            emit onDataDecode(paramsMap);

            res.set_redirect(connectionString, 301);
        });

        std::cout << "Listening on port: " << this->port << std::endl;
        if (!server.listen("0.0.0.0", this->port)) {
            std::cerr << "Random port seems to be occupied. Trying to generate another one" << std::endl;
            number_of_retries ++;
            continue;
        } else {
            break;
        }
    } while(true);
}

void OFDScene::on_choose_image_button_clicked() {
    QString filename = QFileDialog::getOpenFileName();

    if (filename == "") {
        QMessageBox infoDialog;
        infoDialog.setText(tr("Please, select a picture where QR code that contains info about check is present"));
        infoDialog.setIcon(QMessageBox::Critical);
        infoDialog.setWindowTitle(tr("Picture was not selected"));
        infoDialog.exec();
        return;
    }

    ui->info_label->setText(tr("Selected image: ") + filename);

    AdjustPictureDialog dialog = AdjustPictureDialog(this, filename.toStdString());
    connect(&dialog, &AdjustPictureDialog::decodedData, this, &OFDScene::onDataDecode);
    dialog.exec();
}

void OFDScene::onDataDecode(std::map<std::string, std::string> data) {
    ui->fn_line_edit->setText(QString::fromStdString(data["fn"]));
    ui->fd_line_edit->setText(QString::fromStdString(data["i"]));
    ui->fi_line_edit->setText(QString::fromStdString(data["fp"]));

    QString extractedDateTime = QString::fromStdString(data["t"]);
    //TODO: some QRs contain datetime in format yyyyMMddThhmmss. Perhaps there is more different formats, should write function to detect them.
    QDateTime datetime = QDateTime::fromString(extractedDateTime, "yyyyMMddThhmm");
    if (datetime == QDateTime::fromString(extractedDateTime, "20000101T1200")) {
        datetime = QDateTime::fromString(extractedDateTime, "yyyyMMddThhmmss");
    }
    ui->purchase_datetime_edit->setDateTime(datetime);

    int type = std::stoi(data["n"]);
    ui->operation_type_combo_box->setCurrentIndex(type - 1);

    std::string total = data["s"];

    ui->total_spin_box->setValue(std::stod(total));
}

void OFDScene::on_parse_button_clicked() {
    Net net;
    net.get_captcha_from_ofdru();

    std::string solved_captcha = "";
    bool success = true;
    bool is_captcha_solved = true;
    Check check;

    do {
        SolveCaptchaDialog dialog = SolveCaptchaDialog(this, &solved_captcha);
        dialog.exec();
        is_captcha_solved = true;

        try {
            std::string check_content = net.fetch_check_data_from_ofdru(
                ui->fn_line_edit->text().toStdString(),
                ui->fd_line_edit->text().toStdString(),
                ui->fi_line_edit->text().toStdString(),
                ui->purchase_datetime_edit->dateTime().toString(Qt::ISODate).toStdString(),
                ui->operation_type_combo_box->currentIndex() + 1,
                // In the request to ofd.ru, total is in a format with 2 last digits represent decimal part of a number.
                ui->total_spin_box->text().toDouble() * 100,
                solved_captcha);

            check = parseOfdRuAnswer(check_content);
        } catch(OfdRequestException e) {
            success = false;
            if (!strcmp(e.what(), "Incorrect captcha")) {
                is_captcha_solved = false;
                QMessageBox infoDialog;
                infoDialog.setText(tr("Captcha was not solved correctly!"));
                infoDialog.setIcon(QMessageBox::Critical);
                infoDialog.setWindowTitle(tr("Captcha is incorrect"));
                infoDialog.exec();
                break;
            } else if (!strcmp(e.what(), "Internal server error")) {
                QMessageBox infoDialog;
                infoDialog.setText(tr("Internal server error. Please, try again later."));
                infoDialog.setIcon(QMessageBox::Critical);
                infoDialog.setWindowTitle(tr("Internal server error"));
                infoDialog.exec();
                return;
            } else if (!strcmp(e.what(), "Does not exist")) {
                QMessageBox infoDialog;
                infoDialog.setText(tr("Check not found. Please, ensure correctness of entered data."));
                infoDialog.setIcon(QMessageBox::Critical);
                infoDialog.setWindowTitle(tr("Check was not found"));
                infoDialog.exec();
                return;
            }
        }
    } while (!is_captcha_solved);

    if (success) {
        OutputDialog *d = new OutputDialog(this, check);
        d->exec();

        delete d;
    }
}

void OFDScene::on_binary_eye_button_clicked() {
    http_thread = new std::thread(&OFDScene::startHttpServer, this);
    ui->binary_eye_button->setEnabled(false);
    ui->stop_server_button->show();

    while (!server.is_running());
    std::string localIp;
    try {
        localIp = get_local_ip_address();
    } catch(std::exception e) {
        std::cerr << e.what() << std::endl;
        return;
    }
    std::string connectionString = "binaryeye://scan?ret=http://" + localIp + ":" + std::to_string(port) + "/?result={RESULT}";

    generate_qr_code(connectionString);

    QMessageBox infoDialog = QMessageBox();
    infoDialog.setText(QString::fromStdString(connectionString));
    infoDialog.setIconPixmap(QPixmap(QString::fromStdString(get_path_relative_to_home(".local/share/checks_parser/binaryeye_connection.png"))).scaled(400, 400, Qt::KeepAspectRatio));
    infoDialog.setWindowTitle(tr("QR code for binaryeye to connect"));
    infoDialog.setButtonText(1, tr("I've scanned"));
    infoDialog.exec();
}

void OFDScene::notifyHttpServerFailure() {
    QMessageBox infoDialog = QMessageBox();
    infoDialog.setText(tr("Could not start http server. 10 times in a row random port was occupied. Either you should run for a lottery ticket, or the problem is in the program. If the lottery ticket wasn't lucky, please, contact the developer."));
    infoDialog.setIcon(QMessageBox::Warning);
    infoDialog.setWindowTitle(tr("Could not start http server."));
    infoDialog.exec();
}

unsigned int OFDScene::getPort() {
    return port;
}


void OFDScene::on_stop_server_button_clicked() {
    server.stop();
    http_thread->join();
    ui->stop_server_button->hide();
    ui->binary_eye_button->setEnabled(true);
}