/*********************************************************************
 * \file   BigFile.cpp
 *
 * \author Romain BOULLARD
 * \date   December 2025
 *********************************************************************/
#include <Engine/BigFile/BigFile.hpp>

#include <Engine/EngineLogger_generated.hpp>

#include <System/Log/Log.hpp>
#include <System/Time/Time.hpp>
#include <System/UUID/UUID.hpp>

#include <Utils/Singleton.hpp>
#include <Utils/TargetMacros.h>

#include <EngineTests/BigFile/Asset/AssetA.hpp>
#include <EngineTests/BigFileInfo_generated.hpp>

#include <ankerl/unordered_dense.h>
#include <flatbuffers/reflection.h>

#include <gtest/gtest.h>

namespace Bigfoot
{
class BigFileFixture: public ::testing::Test
{
  protected:
    void SetUp() override
    {
        BigFile::Request deleteHeader {m_bigFile, "DELETE FROM AssetHeader"};
        BigFile::Request deleteAsset {m_bigFile, "DELETE FROM Asset"};
        BigFile::Request deleteDependencies {m_bigFile, "DELETE FROM AssetDependency"};

        m_bigFile.BeginTransaction();
        deleteHeader.Execute();
        deleteAsset.Execute();
        deleteDependencies.Execute();
        m_bigFile.CommitTransaction();

        BIGFOOT_NOT_OPTIMIZED_ONLY(std::ignore = Singleton<Log>::Instance().RegisterLogger(ENGINE_LOGGER);)
    }

    BIGFOOT_NOT_OPTIMIZED_ONLY(Singleton<Log>::Lifetime m_loggerLifetime;)

    BigFile m_bigFile {File {BIGFILE_ENGINETESTS_LOCATION}};
};

/****************************************************************************************/

TEST_F(BigFileFixture, Asset)
{
    AssetA assetA;
    assetA.SetName("Hello");
    assetA.SetVersion(42);

    const eastl::vector<std::byte> test = assetA.Pack();

    AssetA assetB {test};
}

/****************************************************************************************/

TEST_F(BigFileFixture, Lol)
{
    UUID uuid;
    UUID uuid2;
    UUID uuid3;

    eastl::array<std::byte, 4> blob {std::byte {1}, std::byte {2}, std::byte {3}, std::byte {4}};
    eastl::array<std::byte, 4> blob2 {std::byte {1}, std::byte {2}, std::byte {3}, std::byte {5}};
    eastl::array<std::byte, 4> blob3 {std::byte {1}, std::byte {2}, std::byte {3}, std::byte {6}};
    eastl::array<std::byte, 4> blob4 {std::byte {10}, std::byte {11}, std::byte {12}, std::byte {13}};

    {
        BigFile::Request assetHeaderRequest {
          m_bigFile,
          "INSERT INTO AssetHeader (UUID, Name, TypeID, TypeName) VALUES(?, ?, ?, ?)"};
        assetHeaderRequest.Bind(1, static_cast<std::span<const std::byte, UUID::UUID_BYTE_SIZE>>(uuid));
        assetHeaderRequest.Bind(2, "Test");
        assetHeaderRequest.Bind(3, 42);
        assetHeaderRequest.Bind(4, "TypeTest");

        BigFile::Request assetRequest {m_bigFile, "INSERT INTO Asset (UUID, Asset) VALUES(?, ?)"};
        assetRequest.Bind(1, static_cast<std::span<const std::byte, UUID::UUID_BYTE_SIZE>>(uuid));
        assetRequest.Bind(2, blob);

        BigFile::Request assetHeaderRequest2 {
          m_bigFile,
          "INSERT INTO AssetHeader (UUID, Name, TypeID, TypeName) VALUES(?, ?, ?, ?)"};
        assetHeaderRequest2.Bind(1, static_cast<std::span<const std::byte, UUID::UUID_BYTE_SIZE>>(uuid2));
        assetHeaderRequest2.Bind(2, "Test2");
        assetHeaderRequest2.Bind(3, 42);
        assetHeaderRequest2.Bind(4, "TypeTest");

        BigFile::Request assetRequest2 {m_bigFile, "INSERT INTO Asset (UUID, Asset) VALUES(?, ?)"};
        assetRequest2.Bind(1, static_cast<std::span<const std::byte, UUID::UUID_BYTE_SIZE>>(uuid2));
        assetRequest2.Bind(2, blob3);

        BigFile::Request assetHeaderRequest3 {
          m_bigFile,
          "INSERT INTO AssetHeader (UUID, Name, TypeID, TypeName) VALUES(?, ?, ?, ?)"};
        assetHeaderRequest3.Bind(1, static_cast<std::span<const std::byte, UUID::UUID_BYTE_SIZE>>(uuid3));
        assetHeaderRequest3.Bind(2, "Test3");
        assetHeaderRequest3.Bind(3, 42);
        assetHeaderRequest3.Bind(4, "TypeTest");

        BigFile::Request assetRequest3 {m_bigFile, "INSERT INTO Asset (UUID, Asset) VALUES(?, ?)"};
        assetRequest3.Bind(1, static_cast<std::span<const std::byte, UUID::UUID_BYTE_SIZE>>(uuid3));
        assetRequest3.Bind(2, blob4);

        BigFile::Request assetDependency {m_bigFile,
                                          "INSERT INTO AssetDependency (AssetUUID, DependsOnUUID) VALUES(?, ?)"};
        assetDependency.Bind(1, static_cast<std::span<const std::byte, UUID::UUID_BYTE_SIZE>>(uuid));
        assetDependency.Bind(2, static_cast<std::span<const std::byte, UUID::UUID_BYTE_SIZE>>(uuid2));

        BigFile::Request assetDependency2 {m_bigFile,
                                           "INSERT INTO AssetDependency (AssetUUID, DependsOnUUID) VALUES(?, ?)"};
        assetDependency2.Bind(1, static_cast<std::span<const std::byte, UUID::UUID_BYTE_SIZE>>(uuid));
        assetDependency2.Bind(2, static_cast<std::span<const std::byte, UUID::UUID_BYTE_SIZE>>(uuid3));

        m_bigFile.BeginTransaction();
        [[maybe_unused]]
        std::uint32_t assetHeaderChangedCount = assetHeaderRequest.Execute();
        [[maybe_unused]]
        std::uint32_t assetChangedCount = assetRequest.Execute();

        [[maybe_unused]]
        std::uint32_t assetHeaderChangedCount2 = assetHeaderRequest2.Execute();
        [[maybe_unused]]
        std::uint32_t assetChangedCount2 = assetRequest2.Execute();

        [[maybe_unused]]
        std::uint32_t assetHeaderChangedCount3 = assetHeaderRequest3.Execute();
        [[maybe_unused]]
        std::uint32_t assetChangedCount3 = assetRequest3.Execute();

        [[maybe_unused]]
        std::uint32_t assetDependencyChangedCount = assetDependency.Execute();
        [[maybe_unused]]
        std::uint32_t assetDependencyChangedCount2 = assetDependency2.Execute();

        m_bigFile.CommitTransaction();
    }

    {
        BigFile::Request updateAsset {m_bigFile, "UPDATE Asset SET Asset = ? WHERE UUID = ?"};
        updateAsset.Bind(1, blob2);
        updateAsset.Bind(2, static_cast<std::span<const std::byte, UUID::UUID_BYTE_SIZE>>(uuid));

        m_bigFile.BeginTransaction();
        [[maybe_unused]]
        std::uint32_t updateAssetChangedCount = updateAsset.Execute();
        m_bigFile.CommitTransaction();
    }

    {
        BigFile::Request request {
          m_bigFile,
          "SELECT Name, TypeID, TypeName, CreateTime, ModificationTime FROM AssetHeader WHERE UUID = ?"};
        request.Bind(1, static_cast<std::span<const std::byte, UUID::UUID_BYTE_SIZE>>(uuid));

        [[maybe_unused]]
        const bool get = request.Step();

        [[maybe_unused]]
        const eastl::string name {static_cast<eastl::string_view>(request.Get(0))};
        [[maybe_unused]]
        const std::uint32_t typeId = request.Get(1);
        [[maybe_unused]]
        const eastl::string typeName {static_cast<eastl::string_view>(request.Get(2))};
        [[maybe_unused]]
        const Time createTime = static_cast<std::int64_t>(request.Get(3));
        [[maybe_unused]]
        const Time modificationTime = static_cast<std::int64_t>(request.Get(4));

        {
            [[maybe_unused]]
            const std::uint32_t year = createTime.Year();
            [[maybe_unused]]
            const std::uint32_t month = createTime.Month();
            [[maybe_unused]]
            const std::uint32_t day = createTime.Day();
            [[maybe_unused]]
            const std::uint32_t hour = createTime.Hour();
            [[maybe_unused]]
            const std::uint32_t minute = createTime.Minute();
            [[maybe_unused]]
            const std::uint32_t second = createTime.Second();
            [[maybe_unused]]
            const std::uint32_t microsecond = createTime.Microsecond();
        }

        {
            [[maybe_unused]]
            const std::uint32_t year = modificationTime.Year();
            [[maybe_unused]]
            const std::uint32_t month = modificationTime.Month();
            [[maybe_unused]]
            const std::uint32_t day = modificationTime.Day();
            [[maybe_unused]]
            const std::uint32_t hour = modificationTime.Hour();
            [[maybe_unused]]
            const std::uint32_t minute = modificationTime.Minute();
            [[maybe_unused]]
            const std::uint32_t second = modificationTime.Second();
            [[maybe_unused]]
            const std::uint32_t microsecond = modificationTime.Microsecond();
        }
    }

    {
        BigFile::Request getDependency {
          m_bigFile,
          "SELECT h.* FROM  AssetHeader h JOIN AssetDependency d ON h.UUID = d.DependsOnUUID WHERE d.AssetUUID = ?"};
        getDependency.Bind(1, static_cast<std::span<const std::byte, UUID::UUID_BYTE_SIZE>>(uuid));

        ankerl::unordered_dense::segmented_set<UUID> dependencies;

        [[maybe_unused]]
        const bool get = getDependency.Step();

        auto test = static_cast<eastl::span<const std::byte>>(getDependency.Get(0));
        dependencies.insert(UUID {std::span<const std::byte, UUID::UUID_BYTE_SIZE> {test.begin(), test.end()}});

        [[maybe_unused]]
        const bool get2 = getDependency.Step();

        auto test2 = static_cast<eastl::span<const std::byte>>(getDependency.Get(0));
        dependencies.insert(UUID {std::span<const std::byte, UUID::UUID_BYTE_SIZE> {test2.begin(), test2.end()}});

        EXPECT_TRUE(dependencies.find(uuid3) != dependencies.end());
        EXPECT_TRUE(dependencies.find(uuid2) != dependencies.end());
    }

    {
        BigFile::Request getDependency {
          m_bigFile,
          "SELECT h.* FROM  AssetHeader h JOIN AssetDependency d ON h.UUID = d.DependsOnUUID WHERE d.AssetUUID = ?"};
        getDependency.Bind(1, static_cast<std::span<const std::byte, UUID::UUID_BYTE_SIZE>>(uuid2));

        const bool get = getDependency.Step();
        EXPECT_FALSE(get);
    }
}

/*TEST_F(BigFileFixture, Reflection)
{
    [[maybe_unused]]
    const reflection::Schema* reflection = reflection::GetSchema(AssetA::ReflectionInfo().data());

    auto IsRef = [](const reflection::Object* obj) -> bool
    {
        if (!obj->attributes())
        {
            return false;
        }
        for (const auto attr: *obj->attributes())
        {
            if (std::strcmp(attr->key()->c_str(), "isRef") == 0)
            {
                return true;
            }
        }
        return false;
    };

    for (const auto test: *reflection->root_table()->fields())
    {
        [[maybe_unused]]
        std::string typeName;
        [[maybe_unused]]
        bool isRef = false;

        if (test->type()->base_type() == reflection::BaseType::Obj)
        {
            const reflection::Object* obj = reflection->objects()->Get(test->type()->index());
            typeName = obj->name()->c_str();
            isRef = IsRef(obj);
        }
        else if (test->type()->base_type() == reflection::BaseType::Vector &&
                 test->type()->element() == reflection::BaseType::Obj)
        {
            const reflection::Object* obj = reflection->objects()->Get(test->type()->index());
            typeName = std::string("[") + obj->name()->c_str() + "]";
            isRef = IsRef(obj);
        }
        else
        {
            typeName = reflection::EnumNameBaseType(test->type()->base_type());
        }

        BIGFOOT_LOG_INFO(ENGINE_LOGGER, "{} {} {}", typeName, test->name()->c_str(), isRef ? "(isRef)" : "");
    }
}*/
}   // namespace Bigfoot
