/*
 * This file is a part of the CaosDB Project.
 *
 * Copyright (C) 2021 IndiScale GmbH <info@indiscale.com>
 * Copyright (C) 2021 Daniel Hornung <d.hornung@indiscale.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
 */
#include "caosdb/connection.h"         // for Connection, Connec...
#include "caosdb/data_type.h"          // for AtomicDataType
#include "caosdb/entity.h"             // for Entity, Parent, Role
#include "caosdb/transaction.h"        // for Transaction, Entity
#include "caosdb/transaction_status.h" // for TransactionStatus
#include <cstdint>                     // for int32_t
#include <gtest/gtest-message.h>       // for Message
#include <gtest/gtest-spi.h>           // for EXPECT_NONFATAL_FA...
#include <gtest/gtest-test-part.h>     // for TestPartResult
#include <gtest/gtest_pred_impl.h>     // for AssertionResult
#include <iostream>                    // for operator<<, endl
#include <memory>                      // for unique_ptr, allocator
#include <string>                      // for operator+, operator<<
#include <vector>                      // for vector

namespace caosdb::transaction {
using caosdb::entity::AtomicDataType;
using caosdb::entity::Entity;
using caosdb::entity::Parent;
using caosdb::entity::Role;

class test_issues : public ::testing::Test {
public:
  // public utility functions
  // ////////////////////////////////////////////////////////

  static void DeleteEntities() {
    // delete all entities
    const auto &connection =
      caosdb::connection::ConnectionManager::GetDefaultConnection();
    auto query_transaction(connection->CreateTransaction());
    query_transaction->Query("FIND ENTITY WITH id > 99");
    query_transaction->Execute();
    if (query_transaction->GetResultSet().size() > 0) {
      std::cout << "Cleanup: Deleting "
                << query_transaction->GetResultSet().size() << " entities."
                << std::endl;
      auto delete_transaction(connection->CreateTransaction());
      for (const Entity &entity : query_transaction->GetResultSet()) {
        delete_transaction->DeleteById(entity.GetId());
      }
      delete_transaction->Execute();
    }
  }

protected:
  // Fixture methods //////////////////////////////////////////////////////////

  void SetUp() override { DeleteEntities(); }

  void TearDown() override { DeleteEntities(); }
};

/*
 * test error-prone updates
 */
TEST_F(test_issues, server_issue_170) {
  const auto &connection =
    caosdb::connection::ConnectionManager::GetDefaultConnection();

  // Insert original
  auto insert_transaction(connection->CreateTransaction());

  Entity original;
  original.SetRole(Role::PROPERTY);
  original.SetName("Prop 1");
  original.SetDataType(AtomicDataType::DOUBLE);
  insert_transaction->InsertEntity(&original);
  insert_transaction->ExecuteAsynchronously();
  auto insert_status = insert_transaction->WaitForIt();
  ASSERT_TRUE(insert_status.IsTerminated());
  ASSERT_FALSE(insert_status.IsError());

  const auto &insert_result_set = insert_transaction->GetResultSet();
  auto id = insert_result_set.at(0).GetId();
  EXPECT_FALSE(id.empty());

  // Retrieve original with ID
  auto retrieve_transaction(connection->CreateTransaction());
  retrieve_transaction->RetrieveById(id);
  retrieve_transaction->Execute();
  auto update_entity(retrieve_transaction->GetResultSet().at(0));

  // UPDATE
  auto update_transaction(connection->CreateTransaction());
  update_entity.SetDataType(AtomicDataType::INTEGER, true);
  update_entity.SetValue(std::vector<int32_t>{1, 1, 2, 3, 5, 8, 13});

  update_transaction->UpdateEntity(&update_entity);
  update_transaction->ExecuteAsynchronously();

  auto update_status = update_transaction->WaitForIt();
  EXPECT_TRUE(update_status.IsTerminated());
  EXPECT_FALSE(update_status.IsError());
}

/*
 * Insert a Record with a parent, which has a wrong name.
 *
 * This must result in a server error.
 */
TEST_F(test_issues, server_issue_171) {
  const auto &connection =
    caosdb::connection::ConnectionManager::GetDefaultConnection();

  auto insert_transaction(connection->CreateTransaction());

  // insert RT
  Entity rt;
  rt.SetRole(Role::RECORD_TYPE);
  rt.SetName("Test_RT_Name");
  insert_transaction->InsertEntity(&rt);
  insert_transaction->ExecuteAsynchronously();

  auto insert_status = insert_transaction->WaitForIt();
  ASSERT_TRUE(insert_status.IsTerminated());
  ASSERT_FALSE(insert_status.IsError());

  const auto &insert_result_set = insert_transaction->GetResultSet();
  const auto &inserted_rt = insert_result_set.at(0);

  // create Record with parent
  Entity rec;
  rec.SetRole(Role::RECORD);
  rec.SetName("TestRec");

  Parent parent;
  parent.SetId(inserted_rt.GetId());
  parent.SetName(rt.GetName() + "_wrong");
  rec.AppendParent(parent);

  // insert Record
  auto rec_transaction(connection->CreateTransaction());
  rec_transaction->InsertEntity(&rec);
  rec_transaction->ExecuteAsynchronously();

  auto rec_insert_status = rec_transaction->WaitForIt();
  ASSERT_TRUE(rec_insert_status.IsTerminated());
  EXPECT_NONFATAL_FAILURE({ EXPECT_TRUE(rec_insert_status.IsError()); },
                          "rec_insert_status.IsError");

  const auto &rec_result_set = rec_transaction->GetResultSet();
  const auto &inserted_rec = rec_result_set.at(0);

  std::cout << inserted_rec.ToString() << std::endl;
}

/*
 * Inserting a non-file property with an empty file path should work.
 *
 * The file attributes should be ignored by the server.
 */
TEST_F(test_issues, server_issue_174) {
  const auto &connection =
    caosdb::connection::ConnectionManager::GetDefaultConnection();

  auto insert_transaction(connection->CreateTransaction());

  // Create and insert RT
  Entity rt;
  rt.SetRole(Role::RECORD_TYPE);
  rt.SetName("Not a FILE");
  rt.SetFilePath("");
  insert_transaction->InsertEntity(&rt);
  insert_transaction->ExecuteAsynchronously();

  auto insert_status = insert_transaction->WaitForIt();
  ASSERT_TRUE(insert_status.IsTerminated());
  // TODO(tf) Remove the EXPECT_NONFATAL_FAILURE after fixing #174.
  EXPECT_NONFATAL_FAILURE(EXPECT_FALSE(insert_status.IsError()), "");
}

} // namespace caosdb::transaction