#include "productswidget.h"

#include "../sqltreemodel/sqltreemodel.h"

#include "../addproducttypedialog/addproducttypedialog.h"

#include "../renamedialog/renamedialog.h"

#include "../deletedialog/deletedialog.h"

#include "../editproductdialog/editproductdialog.h"

#include "../addproductdialog/addproductdialog.h"

#include "../sqlqueryproductsmodel/sqlqueryproductsmodel.h"

#include <QVBoxLayout>

#include <QSplitter>

#include <QHeaderView>

#include <QSqlRecord>

#include <QSqlRelationalDelegate>

#include <QMessageBox>

#include <QSqlError>

#include <QDebug>

void ProductsWidget::createTreeWidget() {
  m_treeLabel = new QLabel(tr("Types of products:"));

  m_treeModel = new SqlTreeModel(this);

  m_expandAll = new QPushButton(tr("Expand all product types"));
  m_collapseAll = new QPushButton(tr("Сollapse all product types"));
  m_deleteProductType = new QPushButton(tr("Delete product type..."));
  m_renameProductType = new QPushButton(tr("Rename product type..."));
  m_addProductType = new QPushButton(tr("Add product type..."));

  for (auto &pushButton : {m_expandAll, m_collapseAll, m_deleteProductType,
                           m_renameProductType, m_addProductType})
    pushButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred);

  auto buttonLayout = new QVBoxLayout;
  buttonLayout->addWidget(m_expandAll);
  buttonLayout->addWidget(m_collapseAll);
  buttonLayout->addWidget(m_deleteProductType);
  buttonLayout->addWidget(m_renameProductType);
  buttonLayout->addWidget(m_addProductType);
  buttonLayout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Minimum,
                                              QSizePolicy::MinimumExpanding));

  m_treeView = new QTreeView;
  m_treeView->setModel(m_treeModel);
  m_treeView->setHeaderHidden(true);
  m_treeView->setSelectionMode(
      QAbstractItemView::SelectionMode::SingleSelection);
  m_treeView->setEditTriggers(QAbstractItemView::NoEditTriggers);
  m_treeView->setSizePolicy(QSizePolicy::MinimumExpanding,
                            QSizePolicy::MinimumExpanding);

  auto layout = new QHBoxLayout;
  layout->addWidget(m_treeView);
  layout->addLayout(buttonLayout);

  auto mainLayout = new QVBoxLayout;
  mainLayout->addWidget(m_treeLabel);
  mainLayout->addLayout(layout);

  m_treeWidget = new QWidget;
  m_treeWidget->setLayout(mainLayout);
}

void ProductsWidget::createTableWidget() {
  m_tableLabel = new QLabel(tr("Products:"));

  m_tableModel = new SqlQueryProductsModel(this);

  m_showAll = new QPushButton(tr("Show all products"));
  m_deleteProduct = new QPushButton(tr("Delete product..."));
  m_renameProduct = new QPushButton(tr("Rename product..."));
  m_editProduct = new QPushButton(tr("Edit product..."));
  m_addProduct = new QPushButton(tr("Add product..."));

  for (auto &pushButton : {m_deleteProduct, m_renameProduct, m_addProduct})
    pushButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred);

  auto buttonLayout = new QVBoxLayout;
  buttonLayout->addWidget(m_showAll);
  buttonLayout->addWidget(m_deleteProduct);
  buttonLayout->addWidget(m_renameProduct);
  buttonLayout->addWidget(m_editProduct);
  buttonLayout->addWidget(m_addProduct);
  buttonLayout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Minimum,
                                              QSizePolicy::MinimumExpanding));

  m_tableView = new QTableView;
  m_tableView->setModel(m_tableModel);
  m_tableView->setItemDelegate(new QSqlRelationalDelegate(this));
  m_tableView->setSelectionMode(QAbstractItemView::SingleSelection);
  m_tableView->setSelectionBehavior(QAbstractItemView::SelectRows);
  m_tableView->setEditTriggers(QAbstractItemView::NoEditTriggers);

  auto layout = new QHBoxLayout;
  layout->addWidget(m_tableView);
  layout->addLayout(buttonLayout);

  auto mainLayout = new QVBoxLayout;
  mainLayout->addWidget(m_tableLabel);
  mainLayout->addLayout(layout);

  m_tableWidget = new QWidget;
  m_tableWidget->setLayout(mainLayout);
}

void ProductsWidget::createLayout() {
  createTreeWidget();
  createTableWidget();

  auto horizontalLine = new QFrame;
  horizontalLine->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
  horizontalLine->setLineWidth(2);
  horizontalLine->setFixedHeight(2);
  horizontalLine->setFrameShape(QFrame::HLine);
  horizontalLine->setFrameShadow(QFrame::Sunken);

  auto splitter = new QSplitter;
  splitter->setOrientation(Qt::Orientation::Vertical);
  splitter->addWidget(m_treeWidget);
  splitter->addWidget(horizontalLine);
  splitter->setCollapsible(1, false);
  splitter->addWidget(m_tableWidget);

  auto layout = new QVBoxLayout;
  layout->addWidget(splitter);
  setLayout(layout);
}

void ProductsWidget::configureConnections() {
  // product types
  auto *treeSelectionModel = m_treeView->selectionModel();
  connect(treeSelectionModel, &QItemSelectionModel::currentChanged, this,
          &ProductsWidget::updateTreePushButtonsPropertyEnabled);
  connect(treeSelectionModel, &QItemSelectionModel::currentChanged, this,
          &ProductsWidget::refreshTableModel);
  connect(m_expandAll, &QPushButton::pressed, m_treeView,
          &QTreeView::expandAll);
  connect(m_collapseAll, &QPushButton::pressed, m_treeView,
          &QTreeView::collapseAll);
  connect(m_deleteProductType, &QPushButton::pressed, this,
          &ProductsWidget::showDeleteProductTypeDialogWindow);
  connect(m_renameProductType, &QPushButton::pressed, this,
          &ProductsWidget::showRenameProductTypeDialogWindow);
  connect(m_addProductType, &QPushButton::pressed, this,
          &ProductsWidget::showAddProductTypeDialogWindow);
  // products
  auto *tableSelectionModel = m_tableView->selectionModel();
  connect(tableSelectionModel, &QItemSelectionModel::currentChanged, this,
          &ProductsWidget::selectProductType);
  connect(tableSelectionModel, &QItemSelectionModel::currentChanged, this,
          &ProductsWidget::updateTablePushButtonsPropertyEnabled);
  connect(this, &ProductsWidget::changedTableWidgetModel,
          &ProductsWidget::updateTablePushButtonsPropertyEnabled);
  connect(m_tableView, &QTableView::doubleClicked, this,
          &ProductsWidget::showSameGroup);
  connect(m_showAll, &QPushButton::pressed, this,
          &ProductsWidget::showAllProducts);
  connect(m_renameProduct, &QPushButton::pressed, this,
          &ProductsWidget::showRenameProductDialogWindow);
  connect(m_editProduct, &QPushButton::pressed, this,
          &ProductsWidget::showEditProductDialogWindow);
  connect(m_deleteProduct, &QPushButton::pressed, this,
          &ProductsWidget::showDeleteProductDialogWindow);
  connect(this, &ProductsWidget::changedTableWidgetModel,
          &ProductsWidget::refreshTableView);
  connect(m_addProduct, &QPushButton::pressed, this,
          &ProductsWidget::showAddProductDialogWindow);
}

void ProductsWidget::configureDisplayedData() {
  CREATE_NAME(productTypesName, TABLE::ProductTypes);
  m_treeModel->selectFromTable(productTypesName);
  m_tableModel->setQuery(SqlScript::productsJoinProductTypes());

  CREATE_INDEX(productIdIndex, COLUMN::Products_ProductTypes_ProductId);
  m_tableView->setColumnHidden(productIdIndex, true);

  CREATE_INDEX(productTypeIdIndex, COLUMN::Products_ProductTypes_ProductTypeId);
  m_tableView->setColumnHidden(productTypeIdIndex, true);

  CREATE_INDEX(barcodeIndex, COLUMN::Products_ProductTypes_Barcode);
  m_tableModel->setHeaderData(barcodeIndex, Qt::Orientation::Horizontal,
                              tr("Barcode"));

  CREATE_INDEX(productIndex, COLUMN::Products_ProductTypes_ProductName);
  m_tableModel->setHeaderData(productIndex, Qt::Orientation::Horizontal,
                              tr("Product name"));
  CREATE_INDEX(productTypeNameIndex,
               COLUMN::Products_ProductTypes_ProductTypeName);
  m_tableModel->setHeaderData(productTypeNameIndex, Qt::Orientation::Horizontal,
                              tr("Product type name"));
  emit changedTableWidgetModel();
}

QModelIndex ProductsWidget::treeSelectedItem() {
  auto buffer = m_treeView->selectionModel()->selectedRows();
  return buffer.isEmpty() ? QModelIndex() : buffer.last();
}

ProductsWidget::ProductsWidget(QWidget *parent) : QWidget(parent) {
  createLayout();
  configureConnections();
  configureDisplayedData();
  updateTreePushButtonsPropertyEnabled();
  updateTablePushButtonsPropertyEnabled();
}

ProductsWidget::~ProductsWidget() {}

void ProductsWidget::selectProductType(const QModelIndex &tableModelIndex) {
  CREATE_INDEX(productTypeId, COLUMN::Products_ProductTypes_ProductTypeId);
  auto valueProductTypeId =
      m_tableModel->record(tableModelIndex.row()).value(productTypeId).toInt();
  auto itemIndex =
      m_treeModel->getItemModelIndex(m_treeModel->getNode(valueProductTypeId));
  // Only select
  m_treeView->selectionModel()->select(itemIndex,
                                       QItemSelectionModel::ClearAndSelect);

  SqlTreeNode<QString> *currentNode = m_treeModel->getNode(itemIndex);
  do {
    m_treeView->expand(m_treeModel->getItemModelIndex(currentNode));
    currentNode = currentNode->parent;
  } while (currentNode != nullptr);
  m_treeView->scrollTo(itemIndex);
}

void ProductsWidget::refreshTableModel() {
  auto treeModelIndex = m_treeView->currentIndex();
  if (treeModelIndex.isValid()) {
    int currentNodeId =
        int(m_treeModel->getNode(treeModelIndex)->recordIdentifier);
    m_tableModel->setQuery(
        SqlScript::subgroupsProductsJoinProductTypes(currentNodeId));
  } else {
    m_tableModel->setQuery(SqlScript::productsJoinProductTypes());
  }
  emit changedTableWidgetModel();
}

void ProductsWidget::showSameGroup(const QModelIndex &tableModelIndex) {
  selectProductType(tableModelIndex);
  CREATE_INDEX(productTypeId, COLUMN::Products_ProductTypes_ProductTypeId);
  auto valueProductTypeId =
      m_tableModel->record(tableModelIndex.row()).value(productTypeId).toInt();
  m_tableModel->setQuery(
      SqlScript::sameProductsJoinProductTypes(valueProductTypeId));
  emit changedTableWidgetModel();
}

void ProductsWidget::showDeleteProductTypeDialogWindow() {
  SqlTreeNode<QString> *productTypeNode =
      m_treeModel->getNode(m_treeView->currentIndex());
  QString &productTypeName = productTypeNode->data;
  int recordIdentifier = int(productTypeNode->recordIdentifier);
  // first  check
  if (SqlScript::doesProductExist(recordIdentifier)) {
    QMessageBox::critical(
        nullptr, tr("Removal is not possible"),
        tr("There are products of product type \"%1\".\nIt is necessary to "
           "remove these products to successfully complete the operation.")
            .arg(productTypeName));
    return;
  }
  // second check
  DeleteDialog deleteDialog(productTypeName);
  deleteDialog.setTopText(tr("Product type name: \"%1\"").arg(productTypeName));
  if (deleteDialog.exec() != QDialog::Accepted)
    return;
  SqlScript::deleteProductType(recordIdentifier);
  auto *parent = productTypeNode->parent;
  auto &outputNestedNodes = parent->nestedNodes;
  auto &inputNestedNodes = productTypeNode->nestedNodes;

  m_treeModel->beginUpdate();
  std::for_each( // change parent
      inputNestedNodes.begin(), inputNestedNodes.end(),
      [parent](const std::unique_ptr<SqlTreeNode<QString>> &node) {
        node->parent = parent;
      });
  outputNestedNodes.insert(
      outputNestedNodes.end(), // insert with move new nested nodes
      std::make_move_iterator(inputNestedNodes.begin()),
      std::make_move_iterator(inputNestedNodes.end()));
  parent->remove(productTypeNode);
  m_treeModel->endUpdate();
  // Don't refresh table view
}

void ProductsWidget::showRenameProductTypeDialogWindow() {
  auto currentModelIndex = m_treeView->currentIndex();
  SqlTreeNode<QString> *productTypeNode =
      m_treeModel->getNode(currentModelIndex);
  QString &productTypeName = productTypeNode->data;
  RenameDialog renameDialog(productTypeName);
  renameDialog.setWindowTitle(tr("Renaming process"));
  renameDialog.setTopText(tr("Product type name: \"%1\"").arg(productTypeName));
  if (renameDialog.exec() != QDialog::Accepted)
    return;
  QString newValue = renameDialog.newValue();
  SqlScript::renameProductType(int(productTypeNode->recordIdentifier),
                               newValue);
  m_treeModel->beginUpdate();
  productTypeName = newValue;
  m_treeModel->endUpdate();
  // Don't refresh table view
}

void ProductsWidget::showAddProductTypeDialogWindow() {
  auto currentTreeItem = m_treeView->currentIndex();
  SqlTreeNode<QString> *productTypeNode = m_treeModel->getNode(currentTreeItem);
  AddProductTypeDialog addProductTypeDialog(productTypeNode);
  addProductTypeDialog.setWindowTitle(tr("Creating a new type of product"));
  addProductTypeDialog.setTopText(
      tr("Execution of the operation for the product type \"%1\"")
          .arg(productTypeNode->data));
  if (addProductTypeDialog.exec() != QDialog::Accepted)
    return;
  const QString &futureTypeName = addProductTypeDialog.futureTypeName();
  int productTypeId = int(productTypeNode->recordIdentifier);

  QModelIndex insertedItem;
  if (addProductTypeDialog.lastCase() /*is good case?*/) {
    insertedItem =
        m_treeModel->insertProductType(productTypeId, futureTypeName);
  } else { /*no, it's not*/
    const QString &currentTypeName = addProductTypeDialog.currentTypeName();
    m_treeModel->specifyProductType(productTypeId,
                                    currentTypeName); // first call
    insertedItem =
        m_treeModel->insertProductType(productTypeId,
                                       futureTypeName); // second call
  }
  m_treeView->setCurrentIndex(insertedItem);
  m_treeView->expand(currentTreeItem);
}

void ProductsWidget::showAllProducts() {
  m_treeView->setCurrentIndex(QModelIndex()); // signal sould not emit
  m_tableModel->setQuery(SqlScript::productsJoinProductTypes());
  emit changedTableWidgetModel();
}

void ProductsWidget::showRenameProductDialogWindow() {
  QModelIndex currentTreeItem = m_tableView->currentIndex();
  auto record = m_tableModel->record(currentTreeItem.row());
  CREATE_INDEX(indexProductName, COLUMN::Products_ProductTypes_ProductName);
  CREATE_INDEX(indexProductId, COLUMN::Products_ProductTypes_ProductId);
  const QString &productName = record.value(indexProductName).toString();
  int productId = record.value(indexProductId).toUInt();

  RenameDialog renameDialog(productName);
  renameDialog.setWindowTitle(tr("Renaming process"));
  renameDialog.setTopText(tr("Product name: \"%1\"").arg(productName));
  if (renameDialog.exec() != QDialog::Accepted)
    return;
  const QString &newProductName = renameDialog.newValue();
  if (SqlScript::doesProductTypeExist(newProductName)) {
    QMessageBox::critical(nullptr, tr("Renaming is not possible"),
                          tr("There is already a product \"%1\".\n"
                             "Another name must be specified.")
                              .arg(newProductName));
    return;
  }
  SqlScript::renameProduct(std::move(productId), std::move(newProductName));
  refreshTableModel();
}

void ProductsWidget::showEditProductDialogWindow() {
  QModelIndex currentTreeItem = m_tableView->currentIndex();
  QSqlRecord record = m_tableModel->record(currentTreeItem.row());
  EditProductDialog editDialog(record);
  if (editDialog.exec() == QDialog::Accepted)
    refreshTableModel();
}

void ProductsWidget::showDeleteProductDialogWindow() {
  QModelIndex currentProductIndex = m_tableView->currentIndex();
  auto record = m_tableModel->record(currentProductIndex.row());
  CREATE_INDEX(indexProductName, COLUMN::Products_ProductTypes_ProductName);
  CREATE_INDEX(indexProductId, COLUMN::Products_ProductTypes_ProductId);
  const QString &productName = record.value(indexProductName).toString();
  int productId = record.value(indexProductId).toUInt();

  DeleteDialog deleteDialog(productName);
  deleteDialog.setTopText(tr("Product name: \"%1\"").arg(productName));
  if (deleteDialog.exec() == QDialog::Accepted) {
    auto query = SqlScript::deleteProduct(productId);
    if (query.lastError().isValid()) {
      QMessageBox::critical(
          nullptr, tr("Removal is not possible"),
          tr("The product \"%1\" is referenced by other database entries.\n"
             "Deletion is not possible if these records exist.")
              .arg(productName));
      return;
    }
    refreshTableModel();
  }
}

void ProductsWidget::showAddProductDialogWindow() {
  AddProductDialog addProductDialog;
  if (addProductDialog.exec() == QDialog::Rejected)
    return;
  int productId = addProductDialog.insertedId();
  int productTypeId = SqlScript::productTypeId(productId);
  auto productTypeNode = m_treeModel->getNode(productTypeId);
  auto productTypeIndex = m_treeModel->getItemModelIndex(productTypeNode);
  if (m_treeView->currentIndex().isValid())
    m_treeView->setCurrentIndex(productTypeIndex);
  refreshTableModel();
  CREATE_INDEX(indexProductId, COLUMN::Products_ProductTypes_ProductId);
  QModelIndex firstColumn = m_tableModel->index(0, indexProductId);
  QModelIndexList match =
      m_tableModel->match(firstColumn, Qt::DisplayRole, productId);
  m_tableView->setCurrentIndex(match.back() /*rowIndex*/);
  emit changedTableWidgetModel();
}

void ProductsWidget::updateTreePushButtonsPropertyEnabled() {
  // product types
  QModelIndex currentTreeItem = m_treeView->currentIndex();
  bool enabledTreeFlag = currentTreeItem.isValid();
  m_deleteProductType->setEnabled(enabledTreeFlag);
  m_renameProductType->setEnabled(enabledTreeFlag);
  m_addProductType->setEnabled(enabledTreeFlag);
}

void ProductsWidget::updateTablePushButtonsPropertyEnabled() {
  // products
  QModelIndex currentTabeItem = m_tableView->currentIndex();
  bool enabledTabeFlag = currentTabeItem.isValid();
  m_deleteProduct->setEnabled(enabledTabeFlag);
  m_renameProduct->setEnabled(enabledTabeFlag);
  m_editProduct->setEnabled(enabledTabeFlag);
}

void ProductsWidget::refreshTableView() {
  bool isEmpty = m_tableModel->rowCount() == 0;
  if (isEmpty ^ m_tableView->horizontalHeader()->isHidden())
    m_tableView->horizontalHeader()->setHidden(/*if table model*/ isEmpty);
  if (isEmpty)
    return;

  m_tableView->horizontalHeader()->setStretchLastSection(false);
  m_tableView->resizeColumnsToContents();
  m_tableView->horizontalHeader()->setStretchLastSection(true);
}
