#include "GameObjectLoader.h"
#include <fstream>
#include <iostream>
#include <cmath>
#include "render/Renderer.h"
#include "render/TextureManager.h"
#include "utils/Utils.h"
#include <Eigen/Geometry>

namespace ZL {

	using json = nlohmann::json;

	std::vector<GameObjectData> GameObjectLoader::loadFromJson(const std::string& jsonPath, const std::string& zipPath)
	{
		std::vector<GameObjectData> objects;
		json j;

		try {
			std::ifstream file(jsonPath);
			if (!file.is_open()) {
				throw std::runtime_error("Could not open file: " + jsonPath);
			}

			if (file.peek() == std::ifstream::traits_type::eof()) {
				throw std::runtime_error("JSON file is empty: " + jsonPath);
			}

			file >> j;

			if (!j.contains("objects") || !j["objects"].is_array()) {
				std::cerr << "Warning: 'objects' array not found in " << jsonPath << std::endl;
				return objects;
			}

			for (const auto& item : j["objects"]) {
				GameObjectData data;

				data.name = item.value("name", "Unknown");
				data.texturePath = item.value("texturePath", "");
				data.meshPath = item.value("meshPath", "");

				data.rotationX = item.value("rotationX", 0.0f);
				data.rotationY = item.value("rotationY", 0.0f);
				data.rotationZ = item.value("rotationZ", 0.0f);

				data.positionX = item.value("positionX", 0.0f);
				data.positionY = item.value("positionY", 0.0f);
				data.positionZ = item.value("positionZ", 0.0f);

				data.scale = item.value("scale", 1.0f);

				// Interactive object properties
				data.interactive = item.value("interactive", false);
				if (data.interactive && item.contains("item") && item["item"].is_object()) {
					const auto& itemData = item["item"];
					data.itemId = itemData.value("id", "");
					data.itemName = itemData.value("name", "Unknown Item");
					data.itemDescription = itemData.value("description", "");
					data.itemIcon = itemData.value("icon", "");
					data.interactionRadius = itemData.value("radius", 2.0f);
				}

				data.activateFunctionName = item.value("activateFunction", "");

				if (!data.meshPath.empty()) {
					objects.push_back(data);
				}
			}

			std::cout << "Successfully loaded " << objects.size() << " game objects from " << jsonPath << std::endl;

		} catch (const std::exception& e) {
			std::cerr << "Error loading JSON: " << e.what() << std::endl;
		}

		return objects;
	}

	std::unordered_map<std::string, LoadedGameObject> GameObjectLoader::loadAndCreateGameObjects(
		const std::string& jsonPath,
		Renderer& renderer,
		const std::string& zipPath)
	{
		std::unordered_map<std::string, LoadedGameObject> gameObjects;
		std::vector<GameObjectData> objectsData = loadFromJson(jsonPath, zipPath);

		for (const auto& objData : objectsData) {
			if (objData.interactive) continue; // Skip interactive objects, they're handled separately

			std::cout << "Loading game object: " << objData.name << std::endl;

			LoadedGameObject gameObj;
			gameObj.name = objData.name;

			// Load texture
			try {
				gameObj.texture = std::make_shared<Texture>(CreateTextureDataFromPng(objData.texturePath, zipPath.c_str()));
			}
			catch (const std::exception& e) {
				std::cerr << "GameObjectLoader: Failed to load texture for '" << objData.name << "': " << e.what() << std::endl;
				continue;
			}

			// Load mesh
			try {
				gameObj.mesh.data = LoadFromTextFile02(objData.meshPath, zipPath);
			}
			catch (const std::exception& e) {
				std::cerr << "GameObjectLoader: Failed to load mesh for '" << objData.name << "': " << e.what() << std::endl;
				continue;
			}

			// Apply rotation
			Eigen::Quaternionf rotationQuat = Eigen::Quaternionf::Identity();

			if (objData.rotationX != 0.0f) {
				rotationQuat = Eigen::Quaternionf(Eigen::AngleAxisf(objData.rotationX, Eigen::Vector3f::UnitX())) * rotationQuat;
			}
			if (objData.rotationY != 0.0f) {
				rotationQuat = Eigen::Quaternionf(Eigen::AngleAxisf(objData.rotationY, Eigen::Vector3f::UnitY())) * rotationQuat;
			}
			if (objData.rotationZ != 0.0f) {
				rotationQuat = Eigen::Quaternionf(Eigen::AngleAxisf(objData.rotationZ, Eigen::Vector3f::UnitZ())) * rotationQuat;
			}

			gameObj.mesh.data.RotateByMatrix(rotationQuat.toRotationMatrix());

			// Apply scale
			if (objData.scale != 1.0f) {
				gameObj.mesh.data.Scale(objData.scale);
			}

			// Apply position
			if (objData.positionX != 0.0f || objData.positionY != 0.0f || objData.positionZ != 0.0f) {
				gameObj.mesh.data.Move({ objData.positionX, objData.positionY, objData.positionZ });
			}

			gameObj.mesh.RefreshVBO();
			gameObjects[objData.name] = gameObj;

			std::cout << "Successfully loaded: " << objData.name << std::endl;
		}

		std::cout << "Total game objects loaded: " << gameObjects.size() << std::endl;
		return gameObjects;
	}

	std::vector<InteractiveObject> GameObjectLoader::loadAndCreateInteractiveObjects(
		const std::string& jsonPath,
		Renderer& renderer,
		const std::string& zipPath)
	{
		std::vector<InteractiveObject> interactiveObjects;
		std::vector<GameObjectData> objectsData = loadFromJson(jsonPath, zipPath);

		for (const auto& objData : objectsData) {
			if (!objData.interactive) continue;

			std::cout << "Loading interactive object: " << objData.name << std::endl;

			InteractiveObject intObj;
			//intObj.id = objData.name;
			intObj.id = !objData.itemId.empty() ? objData.itemId : objData.name;
			intObj.name = objData.name;
			intObj.interactionRadius = objData.interactionRadius;
			intObj.activateFunctionName = objData.activateFunctionName;

			// Load texture
			try {
				intObj.texture = std::make_shared<Texture>(CreateTextureDataFromPng(objData.texturePath, zipPath.c_str()));
			}
			catch (const std::exception& e) {
				std::cerr << "GameObjectLoader: Failed to load texture for interactive '" << objData.name << "': " << e.what() << std::endl;
				continue;
			}

			// Load mesh
			try {
				intObj.mesh.data = LoadFromTextFile02(objData.meshPath, zipPath);
			}
			catch (const std::exception& e) {
				std::cerr << "GameObjectLoader: Failed to load mesh for interactive '" << objData.name << "': " << e.what() << std::endl;
				continue;
			}

			// Apply rotation
			Eigen::Quaternionf rotationQuat = Eigen::Quaternionf::Identity();

			if (objData.rotationX != 0.0f) {
				rotationQuat = Eigen::Quaternionf(Eigen::AngleAxisf(objData.rotationX, Eigen::Vector3f::UnitX())) * rotationQuat;
			}
			if (objData.rotationY != 0.0f) {
				rotationQuat = Eigen::Quaternionf(Eigen::AngleAxisf(objData.rotationY, Eigen::Vector3f::UnitY())) * rotationQuat;
			}
			if (objData.rotationZ != 0.0f) {
				rotationQuat = Eigen::Quaternionf(Eigen::AngleAxisf(objData.rotationZ, Eigen::Vector3f::UnitZ())) * rotationQuat;
			}

			intObj.mesh.data.RotateByMatrix(rotationQuat.toRotationMatrix());

			// Apply scale
			if (objData.scale != 1.0f) {
				intObj.mesh.data.Scale(objData.scale);
			}

			intObj.mesh.RefreshVBO();

			// Calculate mesh bounds to properly offset position
			if (!intObj.mesh.data.PositionData.empty()) {
				Eigen::Vector3f meshMin = intObj.mesh.data.PositionData[0];
				Eigen::Vector3f meshMax = intObj.mesh.data.PositionData[0];
				
				for (const auto& vert : intObj.mesh.data.PositionData) {
					meshMin = meshMin.cwiseMin(vert);
					meshMax = meshMax.cwiseMax(vert);
				}
				
				Eigen::Vector3f meshCenter = (meshMin + meshMax) * 0.5f;
				std::cout << "[LOADER] Mesh bounds:" << std::endl;
				std::cout << "  Min: (" << meshMin.x() << ", " << meshMin.y() << ", " << meshMin.z() << ")" << std::endl;
				std::cout << "  Max: (" << meshMax.x() << ", " << meshMax.y() << ", " << meshMax.z() << ")" << std::endl;
				std::cout << "  Center: (" << meshCenter.x() << ", " << meshCenter.y() << ", " << meshCenter.z() << ")" << std::endl;
				
				// Translate mesh so it's centered at origin
				intObj.mesh.data.Move(-meshCenter);
				intObj.mesh.RefreshVBO();
				
				// Adjust position to account for mesh offset
				intObj.position = Eigen::Vector3f(objData.positionX, objData.positionY, objData.positionZ) + meshCenter;
				
				std::cout << "[LOADER] Corrected position: (" << intObj.position.x() << ", " 
					      << intObj.position.y() << ", " << intObj.position.z() << ")" << std::endl;
			} else {
				// Fallback if no vertices
				intObj.position = Eigen::Vector3f(objData.positionX, objData.positionY, objData.positionZ);
			}

			// Create item
			intObj.dropItem = Item(objData.itemId, objData.itemName, objData.itemDescription, objData.itemIcon);

			interactiveObjects.push_back(intObj);

			std::cout << "Successfully loaded interactive: " << objData.name << " (item: " << objData.itemName;
			if (!objData.activateFunctionName.empty()) {
				std::cout << ", function: " << objData.activateFunctionName;
			}
			std::cout << ")" << std::endl;
		}

		std::cout << "Total interactive objects loaded: " << interactiveObjects.size() << std::endl;
		return interactiveObjects;
	}

	std::vector<NpcData> GameObjectLoader::loadNpcsFromJson(const std::string& jsonPath, const std::string& zipPath)
	{
		std::vector<NpcData> npcs;
		json j;

		try {
			std::ifstream file(jsonPath);
			if (!file.is_open()) {
				throw std::runtime_error("Could not open file: " + jsonPath);
			}

			if (file.peek() == std::ifstream::traits_type::eof()) {
				throw std::runtime_error("JSON file is empty: " + jsonPath);
			}

			file >> j;

			if (!j.contains("npcs") || !j["npcs"].is_array()) {
				std::cerr << "Warning: 'npcs' array not found in " << jsonPath << std::endl;
				return npcs;
			}

			for (const auto& item : j["npcs"]) {
				NpcData data;

				data.id = item.value("id", "npc_unknown");
				data.name = item.value("name", "Unknown NPC");
				data.texturePath = item.value("texturePath", "");
				data.interactionRadius = item.value("interactionRadius", 0.0f);
				data.animationIdlePath = item.value("animationIdlePath", "");
				data.animationWalkPath = item.value("animationWalkPath", "");

				data.positionX = item.value("positionX", 0.0f);
				data.positionY = item.value("positionY", 0.0f);
				data.positionZ = item.value("positionZ", 0.0f);

				data.walkSpeed = item.value("walkSpeed", 1.5f);
				data.rotationSpeed = item.value("rotationSpeed", 8.0f);
				data.modelScale = item.value("modelScale", 0.01f);

				// Model correction rotation (in degrees, will convert to radians)
				data.modelCorrectionRotX = item.value("modelCorrectionRotX", 0.0f) * static_cast<float>(M_PI) / 180.0f;
				data.modelCorrectionRotY = item.value("modelCorrectionRotY", 0.0f) * static_cast<float>(M_PI) / 180.0f;
				data.modelCorrectionRotZ = item.value("modelCorrectionRotZ", 0.0f) * static_cast<float>(M_PI) / 180.0f;

				// Load gift data if available
				if (item.contains("gift") && item["gift"].is_object()) {
					const auto& giftData = item["gift"];
					data.gift.id = giftData.value("id", "");
					data.gift.name = giftData.value("name", "Gift");
					data.gift.description = giftData.value("description", "A gift from an NPC");
					data.gift.icon = giftData.value("icon", "");
				}

				npcs.push_back(data);
			}

			std::cout << "Successfully loaded " << npcs.size() << " NPCs from " << jsonPath << std::endl;

		} catch (const std::exception& e) {
			std::cerr << "Error loading NPCs from JSON: " << e.what() << std::endl;
		}

		return npcs;
	}

	std::vector<std::unique_ptr<Character>> GameObjectLoader::loadAndCreateNpcs(
		const std::string& jsonPath,
		const std::string& zipPath)
	{
		std::vector<std::unique_ptr<Character>> npcs;
		std::vector<NpcData> npcsData = loadNpcsFromJson(jsonPath, zipPath);

		for (const auto& npcData : npcsData) {
			std::cout << "Loading NPC: " << npcData.name << std::endl;

			auto npc = std::make_unique<Character>();

			// Load animations
			try {
				if (npcData.animationIdlePath.substr(npcData.animationIdlePath.size() - 5) == ".anim")
				{
					npc->loadBinaryAnimation(AnimationState::STAND, npcData.animationIdlePath, zipPath);
				}
				else
				{
					npc->loadAnimation(AnimationState::STAND, npcData.animationIdlePath, zipPath);
				}
				std::cout << "  Loaded IDLE animation: " << npcData.animationIdlePath << std::endl;
			}
			catch (const std::exception& e) {
				std::cerr << "GameObjectLoader: Failed to load IDLE animation for '" << npcData.name << "': " << e.what() << std::endl;
				continue;
			}

			try {
				if (npcData.animationIdlePath.substr(npcData.animationIdlePath.size() - 5) == ".anim")
				{
					npc->loadBinaryAnimation(AnimationState::WALK, npcData.animationIdlePath, zipPath);
				}
				else
				{
					npc->loadAnimation(AnimationState::WALK, npcData.animationWalkPath, zipPath);
				}
				
				std::cout << "  Loaded WALK animation: " << npcData.animationWalkPath << std::endl;
			}
			catch (const std::exception& e) {
				std::cerr << "GameObjectLoader: Failed to load WALK animation for '" << npcData.name << "': " << e.what() << std::endl;
				continue;
			}

			// Load texture
			try {
				auto texture = std::make_shared<Texture>(CreateTextureDataFromPng(npcData.texturePath, zipPath.c_str()));
				npc->setTexture(texture);
				std::cout << "  Loaded texture: " << npcData.texturePath << std::endl;
			}
			catch (const std::exception& e) {
				std::cerr << "GameObjectLoader: Failed to load texture for '" << npcData.name << "': " << e.what() << std::endl;
				continue;
			}

			// Set NPC properties
			npc->walkSpeed = npcData.walkSpeed;
			npc->rotationSpeed = npcData.rotationSpeed;
			npc->modelScale = npcData.modelScale;
			npc->position = Eigen::Vector3f(npcData.positionX, npcData.positionY, npcData.positionZ);
			npc->interactionRadius = npcData.interactionRadius;

			// Set NPC metadata
			npc->npcId = npcData.id;
			npc->npcName = npcData.name;

			// Set gift
			if (!npcData.gift.id.empty()) {
				npc->giftItem = Item(npcData.gift.id, npcData.gift.name, npcData.gift.description, npcData.gift.icon);
				std::cout << "  Gift: " << npcData.gift.name << std::endl;
			}

			Eigen::Quaternionf corrRotQuat = Eigen::Quaternionf::Identity();
			if (npcData.modelCorrectionRotX != 0.0f) {
				corrRotQuat = Eigen::Quaternionf(Eigen::AngleAxisf(npcData.modelCorrectionRotX, Eigen::Vector3f::UnitX())) * corrRotQuat;
			}
			if (npcData.modelCorrectionRotY != 0.0f) {
				corrRotQuat = Eigen::Quaternionf(Eigen::AngleAxisf(npcData.modelCorrectionRotY, Eigen::Vector3f::UnitY())) * corrRotQuat;
			}
			if (npcData.modelCorrectionRotZ != 0.0f) {
				corrRotQuat = Eigen::Quaternionf(Eigen::AngleAxisf(npcData.modelCorrectionRotZ, Eigen::Vector3f::UnitZ())) * corrRotQuat;
			}
			npc->modelCorrectionRotation = corrRotQuat;

			// Set initial target to current position
			npc->setTarget(npc->position);

			npcs.push_back(std::move(npc));

			std::cout << "Successfully loaded NPC: " << npcData.name << " at position (" 
				      << npcData.positionX << ", " << npcData.positionY << ", " << npcData.positionZ << ")" << std::endl;
		}

		std::cout << "Total NPCs loaded: " << npcs.size() << std::endl;
		return npcs;
	}

} // namespace ZL