/*
 * Copyright © 2020-2026 Pierrick Caillon <pierrick.caillon+zipdirfs@megami.fr>
 */
#include "NativeFactory.h"
#include "../../fusekit/Fixtures/EntryMock.h"
#include "../Utilities/Fixtures/FileSystem.h"
#include "../Zip/Fixtures/Lib.h"
#include "Fixtures/LibInstance.h"
#include "ZipDirFs/Components/NativeFactory.h"
#include "ZipDirFs/Zip/Exception.h"
#include "test/gtest.h"
#include <filesystem>
#include <gtest/gtest.h>

namespace Test::ZipDirFs::Components
{
	namespace fs = std::filesystem;
	using ::Test::fusekit::Fixtures::EntryMock;
	using ::Test::ZipDirFs::Utilities::Fixtures::FileSystem;
	using ::Test::ZipDirFs::Zip::Fixtures::Lib;
	using ::testing::_;
	using ::testing::ByRef;
	using ::testing::Eq;
	using ::testing::Return;
	using ::testing::Throw;
	using ::ZipDirFs::Components::NativeFactory;

	struct NativeDirectoryEntryMock : EntryMock
	{
		NativeDirectoryEntryMock(const fs::path& p) : path(p){};
		const fs::path path;
	};

	struct NativeSymlinkEntryMock : EntryMock
	{
		NativeSymlinkEntryMock(const fs::path& p) : path(p){};
		const fs::path path;
	};

	struct NativeZipEntryMock : EntryMock
	{
		NativeZipEntryMock(const fs::path& p) : path(p){};
		NativeZipEntryMock(const fs::path& p, const std::string& item) : path(p), item(item){};
		const fs::path path;
		const std::string item;
	};

	typedef NativeFactory<NativeDirectoryEntryMock, NativeSymlinkEntryMock, NativeZipEntryMock>
		TestedFactory;

	TEST(NativeFactory, Directory)
	{
		FileSystem fs;
		const std::string item(std::to_string(::Test::rand(UINT32_MAX)));
		const fs::path parent(fs::path("path") / std::to_string(::Test::rand(UINT32_MAX)));
		EXPECT_CALL(fs, status(parent / item))
			.WillOnce(Return(fs::file_status(fs::file_type::directory)));
		TestedFactory factory(parent);
		std::unique_ptr<::fusekit::entry, std::function<void(::fusekit::entry*)>> created(
			factory.create(item), [&factory](::fusekit::entry* p) { factory.destroy(p); });
		auto result = dynamic_cast<NativeDirectoryEntryMock*>(created.get());
		ASSERT_NE(result, nullptr);
		ASSERT_EQ(result->path, parent / item);
	}

	TEST(NativeFactory, File)
	{
		FileSystem fs;
		Lib lib;
		const std::string item(std::to_string(::Test::rand(UINT32_MAX)));
		const fs::path parent(fs::path("path") / std::to_string(::Test::rand(UINT32_MAX)));
		EXPECT_CALL(fs, status(parent / item))
			.WillOnce(Return(fs::file_status(fs::file_type::regular)));
		EXPECT_CALL(lib, open(parent / item))
			.WillOnce(Throw(::ZipDirFs::Zip::Exception("example", "throw")));
		TestedFactory factory(parent);
		std::unique_ptr<::fusekit::entry, std::function<void(::fusekit::entry*)>> created(
			factory.create(item), [&factory](::fusekit::entry* p) { factory.destroy(p); });
		auto result = dynamic_cast<NativeSymlinkEntryMock*>(created.get());
		ASSERT_NE(result, nullptr);
		ASSERT_EQ(result->path, parent / item);
	}

	TEST(NativeFactory, ZipRoot)
	{
		FileSystem fs;
		Lib lib;
		Fixtures::LibInstance libInstance;
		const std::string item(std::to_string(::Test::rand(UINT32_MAX))),
			inner{std::to_string(::Test::rand(UINT32_MAX))};
		const fs::path parent(fs::path("path") / std::to_string(::Test::rand(UINT32_MAX)));
		EXPECT_CALL(fs, status(parent / item))
			.WillOnce(Return(fs::file_status(fs::file_type::regular)));
		EXPECT_CALL(lib, open(parent / item)).WillOnce(Return(&libInstance));
		EXPECT_CALL(lib, get_num_entries(&libInstance)).WillOnce(Return(1));
		EXPECT_CALL(lib, get_name(&libInstance, 0)).WillOnce(Return(inner));
		EXPECT_CALL(lib, close(&libInstance));
		TestedFactory factory(parent);
		std::unique_ptr<::fusekit::entry, std::function<void(::fusekit::entry*)>> created(
			factory.create(item), [&factory](::fusekit::entry* p) { factory.destroy(p); });
		auto result = dynamic_cast<NativeZipEntryMock*>(created.get());
		ASSERT_NE(result, nullptr);
		ASSERT_EQ(result->path, parent / item);
		ASSERT_EQ("", result->item);
	}

	TEST(NativeFactory, ZipDir)
	{
		FileSystem fs;
		Lib lib;
		Fixtures::LibInstance libInstance;
		const std::string item(std::to_string(::Test::rand(UINT32_MAX))),
			innerParent{std::to_string(::Test::rand(UINT32_MAX)) + '/'},
			inner{innerParent + std::to_string(::Test::rand(UINT32_MAX))};
		const fs::path parent(fs::path("path") / std::to_string(::Test::rand(UINT32_MAX)));
		EXPECT_CALL(fs, status(parent / item))
			.WillOnce(Return(fs::file_status(fs::file_type::regular)));
		EXPECT_CALL(lib, open(parent / item)).WillOnce(Return(&libInstance));
		EXPECT_CALL(lib, get_num_entries(&libInstance)).WillOnce(Return(1));
		EXPECT_CALL(lib, get_name(&libInstance, 0)).WillOnce(Return(inner));
		EXPECT_CALL(lib, close(&libInstance));
		TestedFactory factory(parent);
		std::unique_ptr<::fusekit::entry, std::function<void(::fusekit::entry*)>> created(
			factory.create(item), [&factory](::fusekit::entry* p) { factory.destroy(p); });
		auto result = dynamic_cast<NativeZipEntryMock*>(created.get());
		ASSERT_NE(result, nullptr);
		ASSERT_EQ(parent / item, result->path);
		ASSERT_EQ(innerParent, result->item);
	}

	TEST(NativeFactory, Symlink)
	{
		FileSystem fs;
		const std::string item(std::to_string(::Test::rand(UINT32_MAX)));
		const fs::path parent(fs::path("path") / std::to_string(::Test::rand(UINT32_MAX)));
		EXPECT_CALL(fs, status(parent / item))
			.WillOnce(Return(fs::file_status(fs::file_type::symlink)));
		TestedFactory factory(parent);
		std::unique_ptr<::fusekit::entry, std::function<void(::fusekit::entry*)>> created(
			factory.create(item), [&factory](::fusekit::entry* p) { factory.destroy(p); });
		auto result = dynamic_cast<NativeSymlinkEntryMock*>(created.get());
		ASSERT_NE(result, nullptr);
		ASSERT_EQ(result->path, parent / item);
	}

} // namespace Test::ZipDirFs::Components
