/*
 * Copyright © 2020-2026 Pierrick Caillon <pierrick.caillon+zipdirfs@megami.fr>
 */
#include "FileSystem.h"
#include "ZipDirFs/Utilities/FileSystem.h"
#include "test/gtest.h"
#include <chrono>
#include <filesystem>
#include <ctime>
#include <fstream>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <list>
#include <map>
#include <vector>

namespace Test::ZipDirFs::Utilities
{
	using ::ZipDirFs::Utilities::FileSystem;
	namespace fs = std::filesystem;
	using ::testing::Contains;

	const fs::directory_iterator& DirectoryIteratorWrapperAccess::getDirIt(
		const ::ZipDirFs::Containers::EntryIterator::Wrapper* w)
	{
		return reinterpret_cast<const DirectoryIteratorWrapperAccess*>(w)->current_it;
	}

	namespace
	{
		fs::path tempItem()
		{
			return "item-" + std::to_string(::Test::rand(UINT32_MAX));
		}

		inline std::time_t getRandTime() { return ::Test::rand(631152000L, 3221225471); }

		struct GeneratedFolder
		{
			GeneratedFolder();
			~GeneratedFolder();
			constexpr const fs::path& getRoot() const { return root; }
			constexpr const std::vector<fs::path>& getItems() const { return items; }
			constexpr const std::map<fs::path, std::time_t>& getTimes() const
			{
				return times;
			}

		protected:
			fs::path root;
			std::vector<fs::path> items;
			std::map<fs::path, std::time_t> times;
		};

		struct Guard
		{
			Guard(std::function<void()> f) : _f(f){};
			~Guard() { _f(); }

		protected:
			std::function<void()> _f;
		};
	} // namespace

	GeneratedFolder::GeneratedFolder()
	{
		root = tempFolderPath();
		fs::create_directory(root);
		std::size_t count = ::Test::rand(2, 5);
		items.reserve(count);
		for (std::size_t i = 0; i < count; i++)
		{
			auto item = tempItem();
			items.push_back(item);
			std::ofstream(root / item);
			times[item] = getRandTime();
			fs::last_write_time(root / item, ::Test::to_file_time(times[item]));
		}
	}

	GeneratedFolder::~GeneratedFolder()
	{
		for (auto item : items)
		{
			fs::remove(root / item);
		}
		EXPECT_NO_THROW(fs::remove(root));
	}

	TEST_F(FileSystemTest, LastWriteTime)
	{
		const auto expectedTime = getRandTime();
		const auto path = tempFolderPath();
		Guard rm([path]() { fs::remove(path); });
		std::ofstream{path};
		fs::last_write_time(path, ::Test::to_file_time(expectedTime));
		EXPECT_EQ(::Test::to_time_t(FileSystem::last_write_time(path)), expectedTime);
	}

	TEST_F(FileSystemTest, DirItFromPath)
	{
		GeneratedFolder gf;
		auto it = FileSystem::directory_iterator_from_path(gf.getRoot());
		std::list<std::string> items;
		for (auto item : gf.getItems())
		{
			items.push_back(item.native());
		}
		std::size_t current = 0;
		while (items.size() != 0)
		{
			ASSERT_NE(DirectoryIteratorWrapperAccess::getDirIt(it.getWrapper()),
				fs::directory_iterator());
			EXPECT_THAT(items, Contains(*it)) << "  Current iteration: " << current;
			items.remove(*it);
			++it;
			++current;
		}
		EXPECT_EQ(DirectoryIteratorWrapperAccess::getDirIt(it.getWrapper()),
			fs::directory_iterator());
	}

	TEST_F(FileSystemTest, DirItEnd)
	{
		EXPECT_EQ(DirectoryIteratorWrapperAccess::getDirIt(
					  FileSystem::directory_iterator_end().getWrapper()),
			fs::directory_iterator());
	}

	TEST_F(FileSystemTest, DirItEquals)
	{
		auto expected = FileSystem::directory_iterator_end(), result = expected;
		EXPECT_EQ(result, expected);
	}

	TEST_F(FileSystemTest, DirItNotEquals)
	{
		GeneratedFolder gf;
		EXPECT_FALSE(FileSystem::directory_iterator_from_path(gf.getRoot())
						 .
					 operator==(FileSystem::directory_iterator_end()));
	}

	TEST_F(FileSystemTest, Status)
	{
		const auto path = ::Test::tempFilePath();
		Guard rm([path]() { fs::remove(path); });
		{
			[[maybe_unused]] std::ofstream tmp{path};
		}
		auto const result = FileSystem::status(path);
		EXPECT_EQ(result.type(), fs::file_type::regular);
		EXPECT_EQ(result.permissions(), fs::perms::owner_read | fs::perms::owner_write);
	}
} // namespace Test::ZipDirFs::Utilities
