#include "sqlscript.h"

#include <type_traits>

#include <QVariant>

#include <QMetaType>

#include <QDebug>

int SqlScript::specifyProductType(int productTypeId,
                                  const QString &productTypeName) {
  auto query = QSqlQuery(QSqlDatabase::database());
  query.prepare("EXECUTE [Procedures].[specify_product_type]"
                ":productTypeId, :productTypeName;");
  query.bindValue(":productTypeId", productTypeId);
  query.bindValue(":productTypeName", productTypeName);
  query.exec();
  return query.lastInsertId().toInt();
}
int SqlScript::insertProductType(int productTypeId,
                                 const QString &productTypeName) {
  auto query = QSqlQuery(QSqlDatabase::database());
  query.prepare("EXECUTE [Procedures].[insert_product_type]"
                ":productTypeId, :productTypeName;");
  query.bindValue(":productTypeId", productTypeId);
  query.bindValue(":productTypeName", productTypeName);
  query.exec();
  return query.lastInsertId().toInt();
}

void SqlScript::deleteProductType(int productTypeId) {
  auto query = QSqlQuery(QSqlDatabase::database());
  query.prepare("EXECUTE [Procedures].[delete_product_type] :productTypeId;");
  query.bindValue(":productTypeId", productTypeId);
  query.exec();
}

bool SqlScript::doesBarcodeExist(long long barcode) {
  auto query = QSqlQuery(QSqlDatabase::database());
  query.prepare("SELECT [Functions].[does_barcode_exist](:value);");
  query.bindValue(":value", barcode);
  query.exec();
  query.next();
  return query.value(0).toBool();
}

bool SqlScript::doesProductTypeExist(const QString &typeName) {
  auto query = QSqlQuery(QSqlDatabase::database());
  query.prepare(
      "SELECT [Functions].[does_product_exist_with_name](:typeName);");
  query.bindValue(":typeName", typeName);
  query.exec();
  query.next();
  return query.value(0).toBool();
}

void SqlScript::renameProductType(int productTypeId,
                                  const QString &productTypeName) {
  auto query = QSqlQuery(QSqlDatabase::database());
  query.prepare("EXECUTE [Procedures].[rename_product_type]"
                ":productTypeId, :productTypeName;");
  query.bindValue(":productTypeId", productTypeId);
  query.bindValue(":productTypeName", productTypeName);
  query.exec();
}

bool SqlScript::doesProductExist(int productTypeId) {
  auto query = QSqlQuery(QSqlDatabase::database());
  query.prepare(
      "SELECT"
      "[Functions].[does_product_exist_with_product_type_id](:productTypeId);");
  query.bindValue(":productTypeId", productTypeId);
  query.exec();
  query.next();
  return query.value(0).toBool();
}

bool SqlScript::doesProductExist(const QString &productName) {
  auto query = QSqlQuery(QSqlDatabase::database());
  query.prepare(
      "SELECT [Functions].[does_product_exist_with_name](:productName);");
  query.bindValue(":productName", productName);
  query.exec();
  query.next();
  return query.value(0).toBool();
}

QSqlQuery SqlScript::subgroupsProductsJoinProductTypes(int productTypeId) {
  auto query = QSqlQuery(QSqlDatabase::database());
  query.prepare(
      "SELECT * FROM"
      "[Functions].[subgroup_products_join_product_types](:productTypeId);");
  query.bindValue(":productTypeId", productTypeId);
  query.exec();
  return query;
}

QSqlQuery SqlScript::sameProductsJoinProductTypes(int productTypeId) {
  auto query = QSqlQuery(QSqlDatabase::database());
  query.prepare(
      "SELECT * FROM "
      "[Functions].[same_products_join_product_types](:productTypeId);");
  query.bindValue(":productTypeId", productTypeId);
  query.exec();
  return query;
}

QSqlQuery SqlScript::productsJoinProductTypes() {
  auto queryText =
      QString("EXECUTE [Procedures].[products_join_product_types];");
  return QSqlQuery(queryText, QSqlDatabase::database());
}

int SqlScript::productTypeId(int productId) {
  auto query = QSqlQuery(QSqlDatabase::database());
  query.prepare("SELECT [Functions].[product_type_id](:productId);");
  query.bindValue(":productId", productId);
  query.exec();
  query.next();
  return query.value(0).toInt();
}

void SqlScript::updateBarcode(int productId, long long barcode) {
  auto query = QSqlQuery(QSqlDatabase::database());
  query.prepare("EXECUTE [Procedures].[update_barcode] :productId, :barcode;");
  query.bindValue(":productId", productId);
  query.bindValue(":barcode", barcode);
  query.exec();
}

void SqlScript::renameProduct(int productId, const QString &productName) {
  auto query = QSqlQuery(QSqlDatabase::database());
  query.prepare(
      "EXECUTE [Procedures].[rename_product] :productId, :productName;");
  query.bindValue(":productId", productId);
  query.bindValue(":productName", productName);
  query.exec();
}

int SqlScript::insertProduct(const QString &name, int productTypId,
                             long long barcode) {
  auto query = QSqlQuery(QSqlDatabase::database());
  query.prepare("EXECUTE [Procedures].[insert_product]"
                ":name, :productTypeId, :barcode, :insertedId;");
  query.bindValue(":name", name);
  query.bindValue(":productTypeId", productTypId);
  query.bindValue(":barcode", barcode);
  query.bindValue(":insertedId", 0, QSql::Out);
  query.exec();
  return query.boundValue(":insertedId").toInt();
}

void SqlScript::insertProductAttribute(int productId, int attributeId) {
  auto query = QSqlQuery(QSqlDatabase::database());
  query.prepare("EXECUTE [Procedures].[insert_product_attribute]"
                ":productId, :attributeId;");
  query.bindValue(":productId", productId);
  query.bindValue(":attributeId", attributeId);
  query.exec();
}

void SqlScript::insertProductValue(int productId, int attributeId,
                                   const QVariant &value) {
  auto query = QSqlQuery(QSqlDatabase::database());
  query.prepare("EXECUTE [Procedures].[insert_product_value] :productId, "
                ":attributeId, :value;");
  query.bindValue(":productId", productId);
  query.bindValue(":attributeId", attributeId);
  query.bindValue(":value", value);
  query.exec();
}

QSqlQuery SqlScript::deleteProduct(int productId) {
  auto query = QSqlQuery(QSqlDatabase::database());
  query.prepare("EXECUTE [Procedures].[delete_product] :productId;");
  query.bindValue(":productId", productId);
  query.exec();
  return query;
}

QSqlQuery SqlScript::select(TableType table) {
  auto tableName = NAME(table);
  ASSERT_NAME(tableName);

  auto queryText = QString("SELECT * FROM [Tables].[%1];").arg(tableName);

  return QSqlQuery(queryText, QSqlDatabase::database());
}

QSqlQuery SqlScript::selectValue(int recordId, ColumnType columnId,
                                 ColumnType columnValue, TableType table) {
  auto tableName = NAME(table);
  ASSERT_NAME(tableName);

  auto columnIdName = NAME(columnId);
  ASSERT_NAME(columnIdName);

  auto columnValueName = NAME(columnValue);
  ASSERT_NAME(columnValueName);

  auto queryText =
      QString("SELECT [%1].[%2] FROM [Tables].[%1] WHERE [%3] = %4;")
          .arg(tableName, columnValueName, columnIdName)
          .arg(recordId);

  return QSqlQuery(queryText, QSqlDatabase::database());
}

template <typename T, typename Container>
QSqlQuery SqlScript::selectOrderBy(const QString &tableName,
                                   const Container &columns) {
  auto queryText =
      QString("SELECT * FROM [Tables].[%1] ORDER BY").arg(tableName);
  constexpr const char *modification =
      std::is_integral_v<T> ? " %1," : " [%1],";

  for (const auto &column : columns)
    queryText = queryText.append(modification).arg(column);
  queryText.chop(1); // last comma
  return QSqlQuery(queryText, QSqlDatabase::database());
}

QSqlQuery SqlScript::selectOrderBy(TableType table, ColumnType column) {
  auto tableName = NAME(table);
  ASSERT_NAME(tableName);

  auto columnIndex = INDEX(column);
  ASSERT_INDEX(columnIndex);

  return selectOrderBy<unsigned>(tableName, {columnIndex});
}

QSqlQuery SqlScript::selectOrderBy(TableType table, ColumnType column1,
                                   ColumnType column2) {
  auto tableName = NAME(table);
  ASSERT_NAME(tableName);

  auto column1Index = INDEX(column1);
  ASSERT_INDEX(column1Index);

  auto column2Index = INDEX(column2);
  ASSERT_INDEX(column2Index);

  return selectOrderBy<unsigned>(tableName, {column1Index, column2Index});
}
