#include "CharacterClassSystem.hpp"
#include "../EditorApp.hpp"
#include "../components/CharacterClassComponent.hpp"
#include "../components/CharacterClassDatabase.hpp"
#include "../components/GoapBlackboard.hpp"
#include "../components/PlayerController.hpp"
#include "../components/Inventory.hpp"
#include <OgreLogManager.h>
#include <imgui.h>
#include <algorithm>

CharacterClassSystem::CharacterClassSystem(flecs::world &world,
					   EditorApp *editorApp)
	: m_world(world)
	, m_editorApp(editorApp)
{
}

CharacterClassSystem::~CharacterClassSystem()
{
}

// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------

bool CharacterClassSystem::addXP(flecs::entity entity, int64_t amount)
{
	if (!entity.is_alive() || !entity.has<CharacterClassComponent>())
		return false;

	auto &cc = entity.get_mut<CharacterClassComponent>();
	cc.currentXP += amount;

	const auto *cls = CharacterClassDatabase::getSingleton().findClass(
		cc.className);
	if (!cls)
		return false;

	int64_t needed = CharacterClassDatabase::getSingleton()
				 .computeXPForLevel(cc.level, *cls);
	if (cc.currentXP >= needed) {
		applyLevelUp(entity);
		return true;
	}
	return false;
}

bool CharacterClassSystem::distributePoint(flecs::entity entity,
					   const Ogre::String &statName)
{
	if (!entity.is_alive() || !entity.has<CharacterClassComponent>())
		return false;

	auto &cc = entity.get_mut<CharacterClassComponent>();
	if (cc.availablePoints <= 0)
		return false;

	const auto *cls = CharacterClassDatabase::getSingleton().findClass(
		cc.className);
	if (!cls)
		return false;

	const auto *statDef = CharacterClassDatabase::getSingleton().findStat(
		statName);
	if (!statDef)
		return false;

	int current = cc.getStat(statName);
	int cost = CharacterClassDatabase::getSingleton().computeStatCost(
		current, *cls);
	if (cc.availablePoints < cost)
		return false;

	cc.availablePoints -= cost;
	cc.stats[statName] = current + 1;
	return true;
}

void CharacterClassSystem::applyLevelUpGrowthAndPoints(flecs::entity entity)
{
	if (!entity.is_alive() || !entity.has<CharacterClassComponent>())
		return;

	auto &cc = entity.get_mut<CharacterClassComponent>();
	const auto *cls = CharacterClassDatabase::getSingleton().findClass(
		cc.className);
	if (!cls)
		return;

	auto &db = CharacterClassDatabase::getSingleton();

	// Auto-grow stats
	for (const auto &pair : cls->statGrowth) {
		int growth = db.computeStatGrowth(pair.first, cc.level, *cls);
		cc.stats[pair.first] += growth;
	}

	// Auto-grow skills
	for (const auto &pair : cls->skillGrowth) {
		int growth = db.computeSkillGrowth(pair.first, cc.level, *cls);
		cc.skills[pair.first] += growth;
		const auto *skillDef = db.findSkill(pair.first);
		if (skillDef && cc.skills[pair.first] > skillDef->maxValue)
			cc.skills[pair.first] = skillDef->maxValue;
	}

	// Grant points
	cc.availablePoints += db.computePointsForLevel(cc.level, *cls);
}

void CharacterClassSystem::initializeFromClass(flecs::entity entity)
{
	if (!entity.is_alive() || !entity.has<CharacterClassComponent>())
		return;

	auto &cc = entity.get_mut<CharacterClassComponent>();
	const auto *cls = CharacterClassDatabase::getSingleton().findClass(
		cc.className);
	if (!cls)
		return;

	int targetLevel = cc.level;
	if (targetLevel < 1)
		targetLevel = 1;

	// Reset to base values at level 1
	cc.level = 1;
	cc.availablePoints = 0;

	// Base stats + overrides
	cc.stats = cls->baseStats;
	if (entity.has<CharacterClassOverrideComponent>()) {
		const auto &ov = entity.get<CharacterClassOverrideComponent>();
		for (const auto &pair : ov.statOffsets) {
			cc.stats[pair.first] += pair.second;
		}
	}

	// Base skills + overrides
	cc.skills = cls->baseSkills;
	if (entity.has<CharacterClassOverrideComponent>()) {
		const auto &ov = entity.get<CharacterClassOverrideComponent>();
		for (const auto &pair : ov.skillOffsets) {
			cc.skills[pair.first] += pair.second;
		}
	}

	// Base needs + offsets
	cc.needs = cls->baseNeeds;
	if (entity.has<CharacterClassOverrideComponent>()) {
		const auto &ov = entity.get<CharacterClassOverrideComponent>();
		for (const auto &pair : ov.needOffsets) {
			cc.needs[pair.first] += pair.second;
		}
	}

	// Ensure every database-defined stat/skill/need exists with a default
	auto &db = CharacterClassDatabase::getSingleton();
	for (const auto &name : db.getStatNames()) {
		if (cc.stats.find(name) == cc.stats.end()) {
			const auto *def = db.findStat(name);
			cc.stats[name] = def ? def->minValue : 1;
		}
	}
	for (const auto &name : db.getSkillNames()) {
		if (cc.skills.find(name) == cc.skills.end())
			cc.skills[name] = 0;
	}
	for (const auto &name : db.getNeedNames()) {
		if (cc.needs.find(name) == cc.needs.end())
			cc.needs[name] = 0;
	}

	// Simulate level-ups from 2 to targetLevel
	for (int lvl = 2; lvl <= targetLevel; ++lvl) {
		cc.level = lvl;
		applyLevelUpGrowthAndPoints(entity);
		distributePointsAI(entity);
	}

	// All points should have been spent during simulation
	cc.availablePoints = 0;

	// Initialize resource pools to full
	for (const auto &name : db.getStatNames()) {
		const auto *def = db.findStat(name);
		if (def && def->kind ==
			    CharacterClassDatabase::StatKind::ResourcePool)
			cc.currentPools[name] = cc.getPoolMax(name);
	}
}

// ---------------------------------------------------------------------------
// Update
// ---------------------------------------------------------------------------

void CharacterClassSystem::update(float deltaTime)
{
	accumulateNeeds(deltaTime);
	checkLevelUps();
}

void CharacterClassSystem::accumulateNeeds(float deltaTime)
{
	auto &db = CharacterClassDatabase::getSingleton();

	m_world.query<CharacterClassComponent>().each(
		[&](flecs::entity e, CharacterClassComponent &cc) {
			const auto *cls = db.findClass(cc.className);
			if (!cls)
				return;

			for (auto &pair : cc.needs) {
				const auto *needDef = db.findNeed(pair.first);
				if (!needDef)
					continue;

				pair.second += static_cast<int>(needDef->accumulationRate *
							deltaTime);
				if (pair.second > needDef->maxValue)
					pair.second = needDef->maxValue;
				if (pair.second < 0)
					pair.second = 0;
			}

			updateNeedBits(e);
		});
}

void CharacterClassSystem::updateNeedBits(flecs::entity entity)
{
	if (!entity.has<CharacterClassComponent>())
		return;

	auto &cc = entity.get_mut<CharacterClassComponent>();
	auto &db = CharacterClassDatabase::getSingleton();

	if (!entity.has<GoapBlackboard>())
		entity.set<GoapBlackboard>({});

	auto &bb = entity.get_mut<GoapBlackboard>();

	for (const auto &pair : cc.needs) {
		const auto *needDef = db.findNeed(pair.first);
		if (!needDef || needDef->bitName.empty())
			continue;

		int bitIdx = GoapBlackboard::findBitByName(needDef->bitName);
		if (bitIdx < 0)
			continue;

		int value = pair.second;
		bool currentlySet = bb.getBit(bitIdx);

		// Hysteresis: set at high threshold, clear at low threshold
		if (value >= needDef->highThreshold)
			bb.setBit(bitIdx, true);
		else if (value <= needDef->lowThreshold)
			bb.setBit(bitIdx, false);
		// else: keep current state (dead zone between thresholds)
	}
}

void CharacterClassSystem::checkLevelUps()
{
	m_world.query<CharacterClassComponent>().each(
		[&](flecs::entity e, CharacterClassComponent &cc) {
			if (cc.levelUpPending)
				return;

			const auto *cls = CharacterClassDatabase::getSingleton()
						  .findClass(cc.className);
			if (!cls)
				return;

			int64_t needed = CharacterClassDatabase::getSingleton()
						 .computeXPForLevel(cc.level, *cls);
			if (cc.currentXP >= needed) {
				applyLevelUp(e);
			}
		});
}

void CharacterClassSystem::applyLevelUp(flecs::entity entity)
{
	if (!entity.is_alive() || !entity.has<CharacterClassComponent>())
		return;

	auto &cc = entity.get_mut<CharacterClassComponent>();
	const auto *cls = CharacterClassDatabase::getSingleton().findClass(
		cc.className);
	if (!cls)
		return;

	int64_t needed = CharacterClassDatabase::getSingleton().computeXPForLevel(
		cc.level, *cls);
	cc.currentXP -= needed;
	cc.level++;

	applyLevelUpGrowthAndPoints(entity);

	// Check if player
	bool isPlayer = entity.has<PlayerControllerComponent>();
	if (isPlayer && m_editorApp &&
	    m_editorApp->getGameMode() == EditorApp::GameMode::Game &&
	    m_editorApp->getGamePlayState() ==
		    EditorApp::GamePlayState::Playing) {
		cc.levelUpPending = true;
		m_levelUpDialogs.insert(entity.id());
	} else {
		// AI: auto-distribute immediately
		distributePointsAI(entity);
	}

	Ogre::LogManager::getSingleton().logMessage(
		Ogre::String("CharacterClassSystem: ") +
		Ogre::String(entity.name()) +
		" reached level " + std::to_string(cc.level) + "!");
}

void CharacterClassSystem::distributePointsAI(flecs::entity entity)
{
	if (!entity.is_alive() || !entity.has<CharacterClassComponent>())
		return;

	auto &cc = entity.get_mut<CharacterClassComponent>();
	const auto *cls = CharacterClassDatabase::getSingleton().findClass(
		cc.className);
	if (!cls)
		return;

	int points = cc.availablePoints;

	// Round-robin primary stats
	int idx = 0;
	int safety = 0;
	while (points > 0 && !cls->primaryStats.empty() &&
	       safety < 1000) {
		safety++;
		const auto &statName =
			cls->primaryStats[idx % cls->primaryStats.size()];
		int current = cc.getStat(statName);
		int cost = CharacterClassDatabase::getSingleton()
				   .computeStatCost(current, *cls);
		if (points >= cost) {
			cc.stats[statName] = current + 1;
			points -= cost;
			idx++;
		} else {
			idx++;
			if (idx >= (int)cls->primaryStats.size() * 2)
				break;
		}
	}

	// Random distribution for remainder
	if (points > 0 && !cc.stats.empty()) {
		std::vector<Ogre::String> statNames;
		for (const auto &pair : cc.stats)
			statNames.push_back(pair.first);

		int randomSafety = 0;
		while (points > 0 && !statNames.empty() &&
		       randomSafety < 1000) {
			randomSafety++;
			size_t r = rand() % statNames.size();
			const auto &statName = statNames[r];
			int current = cc.getStat(statName);
			int cost = CharacterClassDatabase::getSingleton()
					   .computeStatCost(current, *cls);
			if (points >= cost) {
				cc.stats[statName] = current + 1;
				points -= cost;
			} else {
				// Can't afford this stat, remove from pool
				statNames.erase(statNames.begin() + r);
			}
		}
	}

	cc.availablePoints = points;
}

// ---------------------------------------------------------------------------
// Dialog rendering
// ---------------------------------------------------------------------------

void CharacterClassSystem::renderDialogs()
{
	// Level-up dialogs
	std::vector<flecs::entity_t> closedDialogs;
	for (flecs::entity_t id : m_levelUpDialogs) {
		flecs::entity e = m_world.entity(id);
		if (!e.is_alive() || !e.has<CharacterClassComponent>()) {
			closedDialogs.push_back(id);
			continue;
		}
		renderLevelUpDialog(e);
	}
	for (flecs::entity_t id : closedDialogs)
		m_levelUpDialogs.erase(id);

	// Character sheets
	std::vector<flecs::entity_t> closedSheets;
	for (flecs::entity_t id : m_sheets) {
		flecs::entity e = m_world.entity(id);
		if (!e.is_alive() || !e.has<CharacterClassComponent>()) {
			closedSheets.push_back(id);
			continue;
		}
		renderCharacterSheet(e);
	}
	for (flecs::entity_t id : closedSheets)
		m_sheets.erase(id);
}

void CharacterClassSystem::renderLevelUpDialog(flecs::entity entity)
{
	if (!entity.has<CharacterClassComponent>())
		return;

	auto &cc = entity.get_mut<CharacterClassComponent>();
	const auto *cls = CharacterClassDatabase::getSingleton().findClass(
		cc.className);
	if (!cls)
		return;

	ImGui::SetNextWindowPos(ImVec2(200, 200), ImGuiCond_FirstUseEver);
	ImGui::SetNextWindowSize(ImVec2(450, 500),
				 ImGuiCond_FirstUseEver);

	Ogre::String title = "Level Up! (Level " +
			     std::to_string(cc.level) + ")";
	bool open = true;
	if (!ImGui::Begin(title.c_str(), &open)) {
		ImGui::End();
		return;
	}

	ImGui::Text("Available Points: %d", cc.availablePoints);
	ImGui::Separator();

	auto &db = CharacterClassDatabase::getSingleton();

	// Stats
	if (!cc.stats.empty()) {
		ImGui::Text("Stats");
		for (auto &pair : cc.stats) {
			const auto *statDef = db.findStat(pair.first);
			int current = pair.second;
			int cost = db.computeStatCost(current, *cls);
			bool canAfford = cc.availablePoints >= cost;

			ImGui::PushID(pair.first.c_str());
			ImGui::Text("%s: %d", pair.first.c_str(), current);
			ImGui::SameLine(150);
			ImGui::Text("Cost: %d", cost);
			ImGui::SameLine(220);
			if (ImGui::Button("+", ImVec2(30, 0)) && canAfford) {
				cc.availablePoints -= cost;
				pair.second = current + 1;
			}
			ImGui::PopID();
		}
	}

	ImGui::Separator();

	if (ImGui::Button("Confirm", ImVec2(100, 0))) {
		cc.levelUpPending = false;
		open = false;
	}
	ImGui::SameLine();
	if (ImGui::Button("Postpone", ImVec2(100, 0))) {
		open = false;
	}

	ImGui::End();

	if (!open) {
		m_levelUpDialogs.erase(entity.id());
		cc.levelUpPending = false;
	}
}

void CharacterClassSystem::renderCharacterSheet(flecs::entity entity)
{
	if (!entity.has<CharacterClassComponent>())
		return;

	auto &cc = entity.get_mut<CharacterClassComponent>();
	const auto *cls = CharacterClassDatabase::getSingleton().findClass(
		cc.className);

	Ogre::String title = "Character Sheet";
	bool open = true;
	ImGui::SetNextWindowPos(ImVec2(250, 150), ImGuiCond_FirstUseEver);
	ImGui::SetNextWindowSize(ImVec2(400, 500),
				 ImGuiCond_FirstUseEver);

	if (!ImGui::Begin(title.c_str(), &open)) {
		ImGui::End();
		return;
	}

	ImGui::Text("Class: %s",
		    cls ? cls->name.c_str() : cc.className.c_str());
	ImGui::Text("Level: %d", cc.level);
	ImGui::Text("XP: %ld", (long)cc.currentXP);
	ImGui::Text("Available Points: %d", cc.availablePoints);
	ImGui::Separator();

	// Stats
	if (!cc.stats.empty()) {
		ImGui::Text("Stats");
		for (const auto &pair : cc.stats) {
			ImGui::Text("  %s: %d", pair.first.c_str(),
				    pair.second);
		}
	}

	// Skills
	if (!cc.skills.empty()) {
		ImGui::Text("Skills");
		for (const auto &pair : cc.skills) {
			ImGui::Text("  %s: %d", pair.first.c_str(),
				    pair.second);
		}
	}

	// Needs
	if (!cc.needs.empty()) {
		ImGui::Text("Needs");
		for (const auto &pair : cc.needs) {
			ImGui::Text("  %s: %d", pair.first.c_str(),
				    pair.second);
		}
	}

	ImGui::Separator();

	// Inventory placeholder
	if (entity.has<InventoryComponent>()) {
		ImGui::Text("Inventory available (not yet displayed)");
	} else {
		ImGui::Text("No inventory");
	}

	ImGui::End();

	if (!open)
		m_sheets.erase(entity.id());
}
