/*
 * HeroRecruitmentTest.cpp, part of VCMI engine
 *
 * Authors: listed in file AUTHORS in main folder
 *
 * License: GNU General Public License v2.0 or later
 * Full text of license available in license.txt file, in main folder
 *
 */
#include "StdInc.h"

#include "GameStateTest.h"

#include "../../lib/gameState/TavernHeroesPool.h"
#include "../../lib/networkPacks/PacksForClient.h"
#include "../../lib/mapObjects/CGHeroInstance.h"
#include "../../lib/mapObjects/CGTownInstance.h"
#include "../../lib/CPlayerState.h"

/**
 * Test fixture for hero recruitment netpack tests.
 * Inherits game setup and state management from GameStateTest.
 */
class HeroRecruitmentTest : public GameStateTest
{
};

// Test that hero recruitment properly assigns an ID to the recruited hero
TEST_F(HeroRecruitmentTest, DISABLED_recruitedHeroGetsId)
{
	startTestGame();

	// Get the first player and find a town
	PlayerColor playerColor = PlayerColor(0);
	auto * playerState = gameState->getPlayerState(playerColor);
	ASSERT_NE(playerState, nullptr);

	// Get initial state
	auto initialHeroes = playerState->getHeroes();
	size_t initialHeroCount = initialHeroes.size();

	// Find a town for recruitment
	auto towns = playerState->getTowns();
	ASSERT_FALSE(towns.empty()) << "Player must have at least one town for hero recruitment";
	auto * town = towns.front();
	ASSERT_NE(town, nullptr);

	// Get a hero type from the pool
	auto poolHeroes = gameState->heroesPool->getHeroesFor(playerColor);
	ASSERT_FALSE(poolHeroes.empty()) << "Hero pool must have heroes available";

	const auto * poolHero = poolHeroes.front();
	ASSERT_NE(poolHero, nullptr);
	HeroTypeID heroType = poolHero->getHeroTypeID();

	// Create and apply HeroRecruited pack
	HeroRecruited pack;
	pack.tid = town->id;
	pack.hid = heroType;
	pack.player = playerColor;
	pack.tile = town->visitablePos();

	gameEventCallback->sendAndApply(pack);

	// Verify player now has one more hero
	auto finalHeroes = playerState->getHeroes();
	EXPECT_EQ(finalHeroes.size(), initialHeroCount + 1)
		<< "Player should have exactly one more hero after recruitment";

	// Find the newly recruited hero
	CGHeroInstance * recruitedHero = nullptr;
	for (auto * hero : finalHeroes)
	{
		if (std::find(initialHeroes.begin(), initialHeroes.end(), hero) == initialHeroes.end())
		{
			recruitedHero = hero;
			break;
		}
	}

	ASSERT_NE(recruitedHero, nullptr) << "Recruited hero must exist in player's hero list";

	// This is the key assertion that validates our fix:
	// The hero must have an ID assigned after being added to the map
	EXPECT_TRUE(recruitedHero->id.hasValue())
		<< "Recruited hero must have an ID assigned (validates fix for assertion crash)";

	// Verify other properties
	EXPECT_EQ(recruitedHero->getOwner(), playerColor)
		<< "Recruited hero must be owned by the recruiting player";
	EXPECT_EQ(recruitedHero->visitablePos(), town->visitablePos())
		<< "Recruited hero must be at town's position";
	EXPECT_EQ(recruitedHero->getHeroTypeID(), heroType)
		<< "Recruited hero must have the correct hero type";

	// Verify the hero is on the map
	auto * heroOnMap = dynamic_cast<CGHeroInstance *>(map->getObject(recruitedHero->id));
	EXPECT_EQ(heroOnMap, recruitedHero)
		<< "Recruited hero must be retrievable from map by ID";
}

// Test hero recruitment without a town (should still assign ID)
TEST_F(HeroRecruitmentTest, DISABLED_recruitedHeroWithoutTownGetsId)
{
	startTestGame();

	PlayerColor playerColor = PlayerColor(0);
	auto * playerState = gameState->getPlayerState(playerColor);
	ASSERT_NE(playerState, nullptr);

	auto poolHeroes = gameState->heroesPool->getHeroesFor(playerColor);
	ASSERT_FALSE(poolHeroes.empty());

	const auto * poolHero = poolHeroes.front();
	HeroTypeID heroType = poolHero->getHeroTypeID();

	// Recruit hero without a town (tavern can be in random locations)
	HeroRecruited pack;
	pack.tid = ObjectInstanceID(); // No town
	pack.hid = heroType;
	pack.player = playerColor;
	pack.tile = int3(5, 5, 0); // Random valid position

	gameEventCallback->sendAndApply(pack);

	// Find the recruited hero
	auto finalHeroes = playerState->getHeroes();
	ASSERT_FALSE(finalHeroes.empty());

	auto * recruitedHero = finalHeroes.back();
	ASSERT_NE(recruitedHero, nullptr);

	// Key assertion: ID must be assigned even without a town
	EXPECT_TRUE(recruitedHero->id.hasValue())
		<< "Hero recruited without town must still have ID assigned";

	EXPECT_EQ(recruitedHero->getOwner(), playerColor);
	EXPECT_EQ(recruitedHero->getHeroTypeID(), heroType);
}
