#include "LicenseModel.h"

// Qt
#include <QtConcurrent/QtConcurrent>
#include <QDebug>
#include <QUuid>
#include <algorithm>
#include <QtSql/QSqlDatabase>
#include <QtSql/QSqlQuery>
#include <QtSql/QSqlError>
#include <QtSql/QSqlRecord>

// Self
#include <qfile.h>

#include "../def.h"

const static int COLUMN_COUNT = 9;

namespace {
struct LoadResult
{
    QList<LicenseModel::LicenseItem> data;
    LicenseModel::Status status = LicenseModel::Status::Ok;
    QString error;
};

struct DeleteResult
{
    QList<QString> ids;
    LicenseModel::Status status = LicenseModel::Status::Ok;
    QString error;
};
}

LicenseModel::LicenseModel(QObject* parent)
    : QAbstractItemModel(parent)
{
    m_db = QSqlDatabase::addDatabase("QSQLITE");
    m_db.setDatabaseName(DB_PATH);

    if (!m_db.open())
    {
        m_status = Status::DbExistError;
        m_errors.append("Database connection failed: " + m_db.lastError().text());
    }
    else
    {
        if (!checkTables())
        {
            m_status = Status::DbStructError;
            m_errors.append("Database tables are not valid");
        }
        else
        {
            m_status = Status::Ok;
        }
    }

    if (m_db.open())
        m_db.close();
}

LicenseModel::~LicenseModel()
{
}

int LicenseModel::rowCount(const QModelIndex &parent) const
{
    return m_data.size();
}

int LicenseModel::columnCount(const QModelIndex &parent) const
{
    return COLUMN_COUNT;
}

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

    if (role == Qt::ToolTipRole && index.column() == 0)
        return m_data[index.row()].id;

    if (role != Qt::DisplayRole)
        return {};

    switch (index.column())
    {
    case 0:
        return m_data[index.row()].lastName ;
    case 1:
        return m_data[index.row()].firstName ;
    case 2:
        return m_data[index.row()].patronymic ;
    case 3:
        return m_data[index.row()].email ;
    case 4:
        return m_data[index.row()].phone ;
    case 5:
        return m_data[index.row()].yourCompany ;
    case 6:
        return m_data[index.row()].city ;
    case 7:
        return m_data[index.row()].createdAtUtc ;
    case 8:
        return m_data[index.row()].comment ;
    default:
        return {};
    }
}

QVariant LicenseModel::headerData(int section, Qt::Orientation orientation, int role) const
{
    if (role != Qt::DisplayRole)
        return {};

    if (orientation == Qt::Vertical)
        return QString::number(section + 1);

    switch (section)
    {
    case 0:
        return tr("Last name");
    case 1:
        return tr("First name");
    case 2:
        return tr("Patronymic");
    case 3:
        return tr("Email");
    case 4:
        return tr("Phone number");
    case 5:
        return tr("Company");
    case 6:
        return tr("City");
    case 7:
        return tr("Created date");
    case 8:
        return tr("Comment");
    default:
        return {};
    }
}

QModelIndex LicenseModel::parent(const QModelIndex &index) const
{
    Q_UNUSED(index)
    return {};
}

QModelIndex LicenseModel::index(int row, int column, const QModelIndex &parent) const
{
    if (row < 0 || row >= m_data.size() || column < 0 || column >= COLUMN_COUNT)
        return QModelIndex();

    if (parent.isValid())
        return QModelIndex();

    return createIndex(row, column, reinterpret_cast<quintptr>(&(m_data[row])));
}

LicenseModel::Status LicenseModel::getStatus()
{
    return m_status;
}

QString LicenseModel::getStatusText()
{
    return m_errors.join('\n');
}

bool LicenseModel::checkTables()
{
    bool clientTableExist = false;
    for (const auto &table : m_db.tables())
    {
        if (table == "clients")
            return true;
    }
    if (!clientTableExist)
        return false;
    else
        return true;
}

bool LicenseModel::prepareDatabase()
{
    QFile tablesFile(QStringLiteral(":/deps/tables.ddl"));
    if (!tablesFile.open(QIODevice::ReadOnly | QIODevice::Text))
        return false;

    m_status = Status::None;
    m_errors.clear();

    for (auto item : QString(tablesFile.readAll()).split(';'))
    {
        if (item.trimmed().isEmpty())
            continue;
        QSqlQuery query(m_db);
        if (!query.exec(item.trimmed()))
        {
            m_status = Status::DbStructError;
            qDebug() << item.trimmed() << query.lastError().text();
            m_errors.append(query.lastError().text());
            return false;
        }
    }

    return true;
}

void LicenseModel::addClient(const LicenseItem &item)
{
    m_status = Status::Working;
    emit statusChanged();

    // handler thread finish
    auto* watcher = new QFutureWatcher<LicenseItem>(this);
    connect(watcher, &QFutureWatcher<LicenseItem>::finished, this, [this, watcher]() {
        const int row = m_data.size();
        beginInsertRows({}, row, row);
        m_data.append(watcher->result());
        endInsertRows();

        m_status = Status::Ok;
        emit statusChanged();

        watcher->deleteLater();
    });

    // another thread lambda
    watcher->setFuture(QtConcurrent::run([item]() {
        const QString connectionName = QString("license_add_%1").arg(QUuid::createUuid().toString(QUuid::Id128));
        LicenseItem output;
        bool ok = false;
        {
            auto db = QSqlDatabase::addDatabase("QSQLITE", connectionName);
            db.setDatabaseName(DB_PATH);
            if (!db.open())
                qDebug() << "db not open";
            else
            {
                QSqlQuery queryInsert(db);
                queryInsert.prepare(QString(
                    "INSERT INTO clients(lastName, firstName, patronymic, phone, email, city, yourCompany, hardwareHash, comment) "
                    "VALUES (:lastName, :firstName, :patronymic, :phone, :email, :city, :yourCompany, :hardwareHash, :comment);"
                ));
                queryInsert.bindValue(":lastName", item.lastName);
                queryInsert.bindValue(":firstName", item.firstName);
                queryInsert.bindValue(":patronymic", item.patronymic);
                queryInsert.bindValue(":phone", item.phone);
                queryInsert.bindValue(":email", item.email);
                queryInsert.bindValue(":city", item.city);
                queryInsert.bindValue(":yourCompany", item.yourCompany);
                queryInsert.bindValue(":hardwareHash", item.hardwareHash);
                queryInsert.bindValue(":comment", item.comment);

                if (!queryInsert.exec())
                    qDebug() << "queryInsert error" << queryInsert.lastError();
                else
                {
                    QString itemId = QString::number(queryInsert.lastInsertId().toLongLong());

                    QSqlQuery querySelect(db);
                    querySelect.prepare(QString("SELECT * FROM clients WHERE id=:id;"));
                    querySelect.bindValue(":id", itemId);

                    if (!querySelect.exec())
                        qDebug() << "querySelect exec error" << querySelect.lastError();
                    else if (!querySelect.next())
                        qDebug() << "querySelect no rows for id" << itemId;
                    else
                    {
                        output.id = itemId;
                        output.lastName = querySelect.value("lastName").toString();
                        output.firstName = querySelect.value("firstName").toString();
                        output.patronymic = querySelect.value("patronymic").toString();
                        output.phone = querySelect.value("phone").toString();
                        output.email = querySelect.value("email").toString();
                        output.city = querySelect.value("city").toString();
                        output.yourCompany = querySelect.value("yourCompany").toString();
                        output.hardwareHash = querySelect.value("hardwareHash").toString();
                        output.comment = querySelect.value("comment").toString();
                        ok = true;
                    }
                }
            }
        }
        QSqlDatabase::removeDatabase(connectionName);
        if (!ok)
            return LicenseItem();
        return output;
    }));
}

void LicenseModel::deleteClient(const QList<int> &rows)
{
    m_status = Status::Working;
    emit statusChanged();

    QList<QString> ids;
    ids.reserve(rows.size());
    for (const auto row : rows)
    {
        if (row < 0 || row >= m_data.size())
            continue;
        const QString id = m_data[row].id;
        if (!id.isEmpty())
            ids.append(id);
    }

    auto* watcher = new QFutureWatcher<DeleteResult>(this);
    connect(watcher, &QFutureWatcher<DeleteResult>::finished, this, [this, watcher]() {
        const DeleteResult result = watcher->result();
        if (result.status != Status::Ok)
        {
            m_status = result.status;
            if (!result.error.isEmpty())
                m_errors.append(result.error);
            emit statusChanged();
            watcher->deleteLater();
            return;
        }

        QList<int> indices;
        indices.reserve(result.ids.size());
        for (const auto &id : result.ids)
        {
            for (int i = 0; i < m_data.size(); ++i)
            {
                if (m_data[i].id == id)
                {
                    indices.append(i);
                    break;
                }
            }
        }

        std::sort(indices.begin(), indices.end(), std::greater<int>());
        for (const auto row : indices)
        {
            if (row < 0 || row >= m_data.size())
                continue;
            beginRemoveRows({}, row, row);
            m_data.removeAt(row);
            endRemoveRows();
        }

        m_status = Status::Ok;
        emit statusChanged();
        watcher->deleteLater();
    });

    watcher->setFuture(QtConcurrent::run([ids]() {
        DeleteResult result;
        result.ids = ids;
        if (ids.isEmpty())
            return result;

        const QString connectionName = QString("license_delete_%1")
                                           .arg(QUuid::createUuid().toString(QUuid::Id128));
        {
            QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE", connectionName);
            db.setDatabaseName(DB_PATH);
            if (!db.open())
            {
                result.status = LicenseModel::Status::DbExistError;
                result.error = "Database connection failed: " + db.lastError().text();
            }
            else
            {
                QStringList placeholders;
                placeholders.reserve(ids.size());
                for (int i = 0; i < ids.size(); ++i)
                    placeholders << "?";

                QSqlQuery query(db);
                query.prepare(QString("DELETE FROM clients WHERE id IN (%1);")
                                  .arg(placeholders.join(',')));
                for (const auto &id : ids)
                    query.addBindValue(id);

                if (!query.exec())
                {
                    result.status = LicenseModel::Status::DbStructError;
                    result.error = query.lastError().text();
                }
            }
        }
        QSqlDatabase::removeDatabase(connectionName);
        return result;
    }));
}

void LicenseModel::editClient(const LicenseItem &item, int index)
{
    m_status = Status::Working;
    emit statusChanged();

    m_data[index] = item;
    dataChanged(this->index(index, 0), this->index(index, COLUMN_COUNT - 1));
}

void LicenseModel::updateModel()
{
    m_status = Status::Working;
    emit statusChanged();

    auto* watcher = new QFutureWatcher<LoadResult>(this);
    connect(watcher, &QFutureWatcher<LoadResult>::finished, this, [this, watcher]() {
        const LoadResult result = watcher->result();
        if (result.status != Status::Ok)
        {
            m_status = result.status;
            if (!result.error.isEmpty())
                m_errors.append(result.error);
            emit statusChanged();
            watcher->deleteLater();
            return;
        }

        beginResetModel();
        m_data = result.data;
        endResetModel();

        m_status = Status::Ok;
        emit statusChanged();
        watcher->deleteLater();
    });

    watcher->setFuture(QtConcurrent::run([]() {
        LoadResult result;
        const QString connectionName = QString("license_load_%1")
                                           .arg(QUuid::createUuid().toString(QUuid::Id128));
        {
            QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE", connectionName);
            db.setDatabaseName(DB_PATH);
            if (!db.open())
            {
                result.status = LicenseModel::Status::DbExistError;
                result.error = "Database connection failed: " + db.lastError().text();
            }
            else
            {
                QSqlQuery query(db);
                if (!query.exec(
                        "SELECT id, lastName, firstName, patronymic, phone, email, city, yourCompany, hardwareHash, comment "
                        "FROM clients ORDER BY id DESC;"
                    ))
                {
                    result.status = LicenseModel::Status::DbStructError;
                    result.error = query.lastError().text();
                }
                else
                {
                    const int createdAtUtcIndex = query.record().indexOf("createdAtUtc");
                    while (query.next())
                    {
                        LicenseItem item;
                        item.id = query.value("id").toString();
                        item.lastName = query.value("lastName").toString();
                        item.firstName = query.value("firstName").toString();
                        item.patronymic = query.value("patronymic").toString();
                        item.phone = query.value("phone").toString();
                        item.email = query.value("email").toString();
                        item.city = query.value("city").toString();
                        item.yourCompany = query.value("yourCompany").toString();
                        item.hardwareHash = query.value("hardwareHash").toString();
                        item.comment = query.value("comment").toString();
                        if (createdAtUtcIndex >= 0)
                            item.createdAtUtc = query.value(createdAtUtcIndex).toString();
                        result.data.append(item);
                    }
                }
            }
        }
        QSqlDatabase::removeDatabase(connectionName);
        return result;
    }));
}

LicenseModel::LicenseItem LicenseModel::getItem(int index) const
{
    if (index < 0 || index >= m_data.count())
        return LicenseItem();
    return m_data[index];
}
