#include <boost/beast/core.hpp>
#include <boost/beast/websocket.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <iostream>
#include <string>
#include <memory>
#include <vector>
#include <mutex>
#include <map>
#include <queue>
#include <sstream>
#include <Eigen/Dense>
#define _USE_MATH_DEFINES
#include <math.h>
#include "../src/network/ClientState.h"
#include <random>
#include <algorithm>
#include <chrono>
#include <unordered_set>

namespace beast = boost::beast;
namespace http = beast::http;
namespace websocket = beast::websocket;
namespace net = boost::asio;
using tcp = net::ip::tcp;

struct DeathInfo {
	int targetId = -1;
	uint64_t serverTime = 0;
	Eigen::Vector3f position = Eigen::Vector3f::Zero();
	int killerId = -1;
};

std::vector<std::string> split(const std::string& s, char delimiter) {
	std::vector<std::string> tokens;
	std::string token;
	std::istringstream tokenStream(s);
	while (std::getline(tokenStream, token, delimiter)) {
		tokens.push_back(token);
	}
	return tokens;
}

struct ServerBox {
	Eigen::Vector3f position;
	Eigen::Matrix3f rotation;
	float collisionRadius = 2.0f;
	bool destroyed = false;
};

struct Projectile {
	int shooterId = -1;
	uint64_t spawnMs = 0;
	Eigen::Vector3f pos;
	Eigen::Vector3f vel;
	float lifeMs = 5000.0f;
};

struct BoxDestroyedInfo {
	int boxIndex = -1;
	uint64_t serverTime = 0;
	Eigen::Vector3f position = Eigen::Vector3f::Zero();
	int destroyedBy = -1;
};

std::vector<BoxDestroyedInfo> g_boxDestructions;
std::mutex g_boxDestructions_mutex;

std::vector<ServerBox> g_serverBoxes;
std::mutex g_boxes_mutex;

std::vector<std::shared_ptr<class Session>> g_sessions;
std::mutex g_sessions_mutex;

std::vector<Projectile> g_projectiles;
std::mutex g_projectiles_mutex;

std::unordered_set<int> g_dead_players;
std::mutex g_dead_mutex;

class Session;

void broadcastToAll(const std::string& message);

class Session : public std::enable_shared_from_this<Session> {
	websocket::stream<beast::tcp_stream> ws_;
	beast::flat_buffer buffer_;
	int id_;
	bool is_writing_ = false;
	std::queue<std::shared_ptr<std::string>> writeQueue_;
	std::mutex writeMutex_;

public:
	ClientStateInterval timedClientStates;

	explicit Session(tcp::socket&& socket, int id)
		: ws_(std::move(socket)), id_(id) {
	}

	int get_id() const { return id_; }

	bool fetchStateAtTime(std::chrono::system_clock::time_point targetTime, ClientState& outState) const {
		if (timedClientStates.canFetchClientStateAtTime(targetTime)) {
			outState = timedClientStates.fetchClientStateAtTime(targetTime);
			return true;
		}
		return false;
	}

	void send_message(const std::string& msg) {
		auto ss = std::make_shared<std::string>(msg);
		{
			std::lock_guard<std::mutex> lock(writeMutex_);
			writeQueue_.push(ss);
		}
		doWrite();
	}

	void run() {
		{
			std::lock_guard<std::mutex> lock(g_sessions_mutex);
			g_sessions.push_back(shared_from_this());
		}

		ws_.async_accept([self = shared_from_this()](beast::error_code ec) {
			if (ec) return;
			std::cout << "Client " << self->id_ << " connected\n";
			self->init();
			});
	}

private:
	/*
	void init() {
		sendBoxesToClient();

		auto timer = std::make_shared<net::steady_timer>(ws_.get_executor());
		timer->expires_after(std::chrono::milliseconds(100));
		timer->async_wait([self = shared_from_this(), timer](const boost::system::error_code& ec) {
			if (!ec) {
				self->send_message("ID:" + std::to_string(self->id_));
				self->do_read();
			}
			});
	}
	*/

	void sendBoxesToClient() {
		std::lock_guard<std::mutex> lock(g_boxes_mutex);

		std::string boxMsg = "BOXES:";
		for (const auto& box : g_serverBoxes) {
			Eigen::Quaternionf q(box.rotation);
			boxMsg += std::to_string(box.position.x()) + ":" +
				std::to_string(box.position.y()) + ":" +
				std::to_string(box.position.z()) + ":" +
				std::to_string(q.w()) + ":" +
				std::to_string(q.x()) + ":" +
				std::to_string(q.y()) + ":" +
				std::to_string(q.z()) + "|";
		}

		if (!boxMsg.empty() && boxMsg.back() == '|') boxMsg.pop_back();

		send_message(boxMsg);
	}


public:
	/*
	explicit Session(tcp::socket&& socket, int id)
		: ws_(std::move(socket)), id_(id) {
	}*/

	void init()
	{
		sendBoxesToClient();

		auto timer = std::make_shared<net::steady_timer>(ws_.get_executor());
		timer->expires_after(std::chrono::milliseconds(100));
		timer->async_wait([self = shared_from_this(), timer](const boost::system::error_code& ec) {
			if (!ec) {
				self->send_message("ID:" + std::to_string(self->id_));
				self->do_read();
			}
			});
	}
	/*
	void run() {

		{
			std::lock_guard<std::mutex> lock(g_sessions_mutex);
			g_sessions.push_back(shared_from_this());
		}

		ws_.async_accept([self = shared_from_this()](beast::error_code ec) {
			if (ec) return;
			std::cout << "Client " << self->id_ << " connected\n";
			self->init();
			//  self->send_message("ID:" + std::to_string(self->id_));
			 // self->do_read();
			});
	}*/

	/*void send_message(std::string msg) {
		auto ss = std::make_shared<std::string>(std::move(msg));
		ws_.async_write(net::buffer(*ss), [ss](beast::error_code, std::size_t) {});
	}

	int get_id() const {
		return id_;
	}*/

	ClientState get_latest_state(std::chrono::system_clock::time_point now) {
		if (timedClientStates.timedStates.empty()) {
			return {};
		}

		// 1. Берем самое последнее известное состояние
		ClientState latest = timedClientStates.timedStates.back();

		// 3. Применяем компенсацию лага (экстраполяцию).
		// Функция внутри использует simulate_physics, чтобы переместить объект 
		// из точки lastUpdateServerTime в точку now.
		latest.apply_lag_compensation(now);

		return latest;
	}
	/*
	void send_message(std::string msg) {
		auto ss = std::make_shared<std::string>(std::move(msg));

		if (is_writing_) {

			ws_.async_write(net::buffer(*ss),
				[self = shared_from_this(), ss](beast::error_code ec, std::size_t) {
					if (ec) {
						std::cerr << "Write error: " << ec.message() << std::endl;
					}
				});
		}
		else {
			is_writing_ = true;
			ws_.async_write(net::buffer(*ss),
				[self = shared_from_this(), ss](beast::error_code ec, std::size_t) {
					self->is_writing_ = false;
					if (ec) {
						std::cerr << "Write error: " << ec.message() << std::endl;
					}
				});
		}
	}*/

	void doWrite() {
		std::lock_guard<std::mutex> lock(writeMutex_);
		if (is_writing_ || writeQueue_.empty()) {
			return;
		}

		is_writing_ = true;
		auto message = writeQueue_.front();

		ws_.async_write(net::buffer(*message),
			[self = shared_from_this(), message](beast::error_code ec, std::size_t) {
				if (ec) {
					std::cerr << "Write error: " << ec.message() << std::endl;
					return;
				}
				{
					std::lock_guard<std::mutex> lock(self->writeMutex_);
					self->writeQueue_.pop();
					self->is_writing_ = false;
				}
				self->doWrite();
			});
	}
private:

	void do_read() {
		ws_.async_read(buffer_, [self = shared_from_this()](beast::error_code ec, std::size_t) {
			if (ec) {
				std::lock_guard<std::mutex> lock(g_sessions_mutex);
				g_sessions.erase(std::remove_if(g_sessions.begin(), g_sessions.end(),
					[self](const std::shared_ptr<Session>& session) {
						return session.get() == self.get();
					}), g_sessions.end());
				std::cout << "Client " << self->id_ << " disconnected\n";
				return;
			}

			std::string msg = beast::buffers_to_string(self->buffer_.data());
			self->process_message(msg);

			self->buffer_.consume(self->buffer_.size());
			self->do_read();
			});
	}

	void process_message(const std::string& msg) {
		auto parts = split(msg, ':');

		if (parts.empty()) return;

		std::string type = parts[0];

		if (type == "UPD") {
			{
				std::lock_guard<std::mutex> gd(g_dead_mutex);
				if (g_dead_players.find(id_) != g_dead_players.end()) {
					std::cout << "Server: Ignoring UPD from dead player " << id_ << std::endl;
					return;
				}
			}

			if (parts.size() < 16) return;
			uint64_t clientTimestamp = std::stoull(parts[1]);
			ClientState receivedState;
			receivedState.id = id_;
			std::chrono::system_clock::time_point uptime_timepoint{
				std::chrono::milliseconds(clientTimestamp)
			};
			receivedState.lastUpdateServerTime = uptime_timepoint;
			receivedState.handle_full_sync(parts, 2);
			timedClientStates.add_state(receivedState);

			retranslateMessage(msg);
		}
		else if (parts[0] == "RESPAWN") {
			{
				std::lock_guard<std::mutex> gd(g_dead_mutex);
				g_dead_players.erase(id_);
			}

			std::string respawnMsg = "RESPAWN_ACK:" + std::to_string(id_);
			broadcastToAll(respawnMsg);

			std::cout << "Server: Player " << id_ << " respawned\n";
		}
		else if (parts[0] == "FIRE") {
			if (parts.size() < 10) return;

			uint64_t clientTime = std::stoull(parts[1]);
			Eigen::Vector3f pos{
				std::stof(parts[2]), std::stof(parts[3]), std::stof(parts[4])
			};
			Eigen::Quaternionf dir(
				std::stof(parts[5]), std::stof(parts[6]), std::stof(parts[7]), std::stof(parts[8])
			);
			float velocity = std::stof(parts[9]);

			int shotCount = 2;
			if (parts.size() >= 11) {
				try { shotCount = std::stoi(parts[10]); }
				catch (...) { shotCount = 2; }
			}

			std::string broadcast = "PROJECTILE:" +
				std::to_string(id_) + ":" +
				std::to_string(clientTime) + ":" +
				std::to_string(pos.x()) + ":" +
				std::to_string(pos.y()) + ":" +
				std::to_string(pos.z()) + ":" +
				std::to_string(dir.w()) + ":" +
				std::to_string(dir.x()) + ":" +
				std::to_string(dir.y()) + ":" +
				std::to_string(dir.z()) + ":" +
				std::to_string(velocity);

			{
				std::lock_guard<std::mutex> lock(g_sessions_mutex);
				for (auto& session : g_sessions) {
					if (session->get_id() != id_) {
						session->send_message(broadcast);
					}
				}
			}

			{
				const std::vector<Eigen::Vector3f> localOffsets = {
					Eigen::Vector3f(-1.5f, 0.9f, 5.0f),
					Eigen::Vector3f(1.5f, 0.9f, 5.0f)
				};

				uint64_t now_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
					std::chrono::system_clock::now().time_since_epoch()).count();

				std::lock_guard<std::mutex> pl(g_projectiles_mutex);
				for (int i = 0; i < std::min(shotCount, (int)localOffsets.size()); ++i) {
					Projectile pr;
					pr.shooterId = id_;
					pr.spawnMs = now_ms;
					Eigen::Vector3f shotPos = pos + dir.toRotationMatrix() * localOffsets[i];
					pr.pos = shotPos;
					Eigen::Vector3f localForward(0.0f, 0.0f, -1.0f);
					Eigen::Vector3f worldForward = dir.toRotationMatrix() * localForward;
					float len = worldForward.norm();
					if (len > 1e-6f) worldForward /= len;
					pr.vel = worldForward * velocity;
					pr.lifeMs = 5000.0f;
					g_projectiles.push_back(pr);

					std::cout << "Server: Created projectile from player " << id_
						<< " at pos (" << shotPos.x() << ", " << shotPos.y() << ", " << shotPos.z()
						<< ") vel (" << pr.vel.x() << ", " << pr.vel.y() << ", " << pr.vel.z() << ")" << std::endl;
				}
			}
		}
	}

	void retranslateMessage(const std::string& msg) {
		std::string event_msg = "EVENT:" + std::to_string(id_) + ":" + msg;

		std::lock_guard<std::mutex> lock(g_sessions_mutex);
		for (auto& session : g_sessions) {
			if (session->get_id() != id_) {
				session->send_message(event_msg);
			}
		}
	}
};

void broadcastToAll(const std::string& message) {
	std::lock_guard<std::mutex> lock(g_sessions_mutex);
	for (const auto& session : g_sessions) {
		session->send_message(message);
	}
}

void update_world(net::steady_timer& timer, net::io_context& ioc) {

	static auto last_snapshot_time = std::chrono::steady_clock::now();
	auto now = std::chrono::steady_clock::now();
	/*static uint64_t lastTickCount = 0;

	if (lastTickCount == 0) {
		//lastTickCount = SDL_GetTicks64();
		lastTickCount = std::chrono::duration_cast<std::chrono::milliseconds>(
			std::chrono::system_clock::now().time_since_epoch()
		).count();

		lastTickCount = (lastTickCount / 50) * 50;

		return;
	}


	auto newTickCount = std::chrono::duration_cast<std::chrono::milliseconds>(
		std::chrono::system_clock::now().time_since_epoch()
	).count();

	newTickCount = (newTickCount / 50) * 50;

	int64_t deltaMs = static_cast<int64_t>(newTickCount - lastTickCount);

	std::chrono::system_clock::time_point nowRounded = std::chrono::system_clock::time_point(std::chrono::milliseconds(newTickCount));
	*/
	// For each player
	// Get letest state + add time (until newTickCount)
	// Calculate if collisions with boxes




	// Рассылка Snapshot раз в 1000мс
	/*
	if (std::chrono::duration_cast<std::chrono::milliseconds>(now - last_snapshot_time).count() >= 1000) {
		last_snapshot_time = now;

		auto system_now = std::chrono::system_clock::now();

		std::string snapshot_msg = "SNAPSHOT:" + std::to_string(
			std::chrono::duration_cast<std::chrono::milliseconds>(
				system_now.time_since_epoch()).count()
		);

		std::lock_guard<std::mutex> lock(g_sessions_mutex);

		// Формируем общую строку состояний всех игроков
		for (auto& session : g_sessions) {
			ClientState st = session->get_latest_state(system_now);
			snapshot_msg += "|" + std::to_string(session->get_id()) + ":" + st.formPingMessageContent();
		}

		for (auto& session : g_sessions) {
			session->send_message(snapshot_msg);
		}
	}*/

	const std::chrono::milliseconds interval(50);
	timer.expires_after(interval);

	timer.async_wait([&](const boost::system::error_code& ec) {
		if (ec) return;

		auto now = std::chrono::system_clock::now();
		uint64_t now_ms = static_cast<uint64_t>(std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count());

		std::vector<DeathInfo> deathEvents;

		{
			std::lock_guard<std::mutex> pl(g_projectiles_mutex);
			std::vector<int> indicesToRemove;

			float dt = 50.0f / 1000.0f;

			for (size_t i = 0; i < g_projectiles.size(); ++i) {
				auto& pr = g_projectiles[i];

				pr.pos += pr.vel * dt;

				if (now_ms > pr.spawnMs + static_cast<uint64_t>(pr.lifeMs)) {
					indicesToRemove.push_back(static_cast<int>(i));
					continue;
				}

				bool hitDetected = false;

				{
					std::lock_guard<std::mutex> lm(g_sessions_mutex);
					std::lock_guard<std::mutex> gd(g_dead_mutex);

					for (auto& session : g_sessions) {
						int targetId = session->get_id();

						if (targetId == pr.shooterId) continue;
						if (g_dead_players.find(targetId) != g_dead_players.end()) continue;

						ClientState targetState;
						if (!session->fetchStateAtTime(now, targetState)) continue;

						Eigen::Vector3f diff = pr.pos - targetState.position;
						const float shipRadius = 15.0f;
						const float projectileRadius = 1.5f;
						float combinedRadius = shipRadius + projectileRadius;

						if (diff.squaredNorm() <= combinedRadius * combinedRadius) {
							DeathInfo death;
							death.targetId = targetId;
							death.serverTime = now_ms;
							death.position = pr.pos;
							death.killerId = pr.shooterId;

							deathEvents.push_back(death);
							g_dead_players.insert(targetId);
							indicesToRemove.push_back(static_cast<int>(i));
							hitDetected = true;

							std::cout << "Server: *** HIT DETECTED! ***" << std::endl;
							std::cout << "Server: Projectile at ("
								<< pr.pos.x() << ", " << pr.pos.y() << ", " << pr.pos.z()
								<< ") hit player " << targetId << std::endl;
							break;
						}
					}
				}

				if (hitDetected) continue;
			}

			if (!indicesToRemove.empty()) {
				std::sort(indicesToRemove.rbegin(), indicesToRemove.rend());
				for (int idx : indicesToRemove) {
					if (idx >= 0 && idx < (int)g_projectiles.size()) {
						g_projectiles.erase(g_projectiles.begin() + idx);
					}
				}
			}
		}

		{
			std::lock_guard<std::mutex> bm(g_boxes_mutex);
			const float projectileHitRadius = 1.5f;
			const float boxCollisionRadius = 2.0f;

			std::vector<std::pair<size_t, size_t>> boxProjectileCollisions;

			for (size_t bi = 0; bi < g_serverBoxes.size(); ++bi) {
				if (g_serverBoxes[bi].destroyed) continue;

				Eigen::Vector3f boxWorld = g_serverBoxes[bi].position + Eigen::Vector3f(0.0f, 6.0f, 45000.0f);

				for (size_t pi = 0; pi < g_projectiles.size(); ++pi) {
					const auto& pr = g_projectiles[pi];
					Eigen::Vector3f diff = pr.pos - boxWorld;
					float thresh = boxCollisionRadius + projectileHitRadius;

					if (diff.squaredNorm() <= thresh * thresh) {
						boxProjectileCollisions.push_back({ bi, pi });
					}
				}
			}

			for (const auto& [boxIdx, projIdx] : boxProjectileCollisions) {
				g_serverBoxes[boxIdx].destroyed = true;

				Eigen::Vector3f boxWorld = g_serverBoxes[boxIdx].position + Eigen::Vector3f(0.0f, 0.0f, 45000.0f);

				BoxDestroyedInfo destruction;
				destruction.boxIndex = static_cast<int>(boxIdx);
				destruction.serverTime = now_ms;
				destruction.position = boxWorld;
				destruction.destroyedBy = g_projectiles[projIdx].shooterId;

				{
					std::lock_guard<std::mutex> dm(g_boxDestructions_mutex);
					g_boxDestructions.push_back(destruction);
				}

				std::cout << "Server: Box " << boxIdx << " destroyed by projectile from player "
					<< g_projectiles[projIdx].shooterId << std::endl;
			}
		}

		{
			std::lock_guard<std::mutex> bm(g_boxes_mutex);
			std::lock_guard<std::mutex> lm(g_sessions_mutex);

			const float shipCollisionRadius = 15.0f;
			const float boxCollisionRadius = 2.0f;

			for (size_t bi = 0; bi < g_serverBoxes.size(); ++bi) {
				if (g_serverBoxes[bi].destroyed) continue;

				Eigen::Vector3f boxWorld = g_serverBoxes[bi].position + Eigen::Vector3f(0.0f, 0.0f, 45000.0f);

				for (auto& session : g_sessions) {
					{
						std::lock_guard<std::mutex> gd(g_dead_mutex);
						if (g_dead_players.find(session->get_id()) != g_dead_players.end()) {
							continue;
						}
					}

					ClientState shipState;
					if (!session->fetchStateAtTime(now, shipState)) continue;

					Eigen::Vector3f diff = shipState.position - boxWorld;
					float thresh = shipCollisionRadius + boxCollisionRadius;

					if (diff.squaredNorm() <= thresh * thresh) {
						g_serverBoxes[bi].destroyed = true;

						BoxDestroyedInfo destruction;
						destruction.boxIndex = static_cast<int>(bi);
						destruction.serverTime = now_ms;
						destruction.position = boxWorld;
						destruction.destroyedBy = session->get_id();

						{
							std::lock_guard<std::mutex> dm(g_boxDestructions_mutex);
							g_boxDestructions.push_back(destruction);
						}

						std::cout << "Server: Box " << bi << " destroyed by ship collision with player "
							<< session->get_id() << std::endl;
						break;
					}
				}
			}
		}

		if (!deathEvents.empty()) {
			for (const auto& death : deathEvents) {
				std::string deadMsg = "DEAD:" +
					std::to_string(death.serverTime) + ":" +
					std::to_string(death.targetId) + ":" +
					std::to_string(death.position.x()) + ":" +
					std::to_string(death.position.y()) + ":" +
					std::to_string(death.position.z()) + ":" +
					std::to_string(death.killerId);

				broadcastToAll(deadMsg);

				std::cout << "Server: Sent DEAD event - Player " << death.targetId
					<< " killed by " << death.killerId << std::endl;
			}
		}

		{
			std::lock_guard<std::mutex> dm(g_boxDestructions_mutex);
			for (const auto& destruction : g_boxDestructions) {
				std::string boxMsg = "BOX_DESTROYED:" +
					std::to_string(destruction.boxIndex) + ":" +
					std::to_string(destruction.serverTime) + ":" +
					std::to_string(destruction.position.x()) + ":" +
					std::to_string(destruction.position.y()) + ":" +
					std::to_string(destruction.position.z()) + ":" +
					std::to_string(destruction.destroyedBy);

				broadcastToAll(boxMsg);
				std::cout << "Server: Broadcasted BOX_DESTROYED for box " << destruction.boxIndex << std::endl;
			}
			g_boxDestructions.clear();
		}

		update_world(timer, ioc);
		});
}

std::vector<ServerBox> generateServerBoxes(int count) {
	std::vector<ServerBox> boxes;
	std::random_device rd;
	std::mt19937 gen(rd());

	const float MIN_COORD = -100.0f;
	const float MAX_COORD = 100.0f;
	const float MIN_DISTANCE = 3.0f;
	const float MIN_DISTANCE_SQUARED = MIN_DISTANCE * MIN_DISTANCE;
	const int MAX_ATTEMPTS = 1000;

	std::uniform_real_distribution<> posDistrib(MIN_COORD, MAX_COORD);
	std::uniform_real_distribution<> angleDistrib(0.0, M_PI * 2.0);

	for (int i = 0; i < count; i++) {
		bool accepted = false;
		int attempts = 0;

		while (!accepted && attempts < MAX_ATTEMPTS) {
			ServerBox box;
			box.position = Eigen::Vector3f(
				(float)posDistrib(gen),
				(float)posDistrib(gen),
				(float)posDistrib(gen)
			);

			accepted = true;
			for (const auto& existingBox : boxes) {
				Eigen::Vector3f diff = box.position - existingBox.position;
				if (diff.squaredNorm() < MIN_DISTANCE_SQUARED) {
					accepted = false;
					break;
				}
			}

			if (accepted) {
				float randomAngle = (float)angleDistrib(gen);
				Eigen::Vector3f axis = Eigen::Vector3f::Random().normalized();
				box.rotation = Eigen::AngleAxisf(randomAngle, axis).toRotationMatrix();
				boxes.push_back(box);
			}

			attempts++;
		}
	}

	return boxes;
}

int main() {
	try {
		{
			std::lock_guard<std::mutex> lock(g_boxes_mutex);
			g_serverBoxes = generateServerBoxes(50);
			std::cout << "Generated " << g_serverBoxes.size() << " boxes on server\n";
		}
		net::io_context ioc;
		tcp::acceptor acceptor{ ioc, {tcp::v4(), 8080} };

		int next_id = 1000;

		std::cout << "Server started on port 8080...\n";

		auto do_accept = [&](auto& self_fn) -> void {
			acceptor.async_accept([&, self_fn](beast::error_code ec, tcp::socket socket) {
				if (!ec) {
					std::make_shared<Session>(std::move(socket), next_id++)->run();
				}
				self_fn(self_fn);
				});
			};

		net::steady_timer timer(ioc);
		update_world(timer, ioc);

		do_accept(do_accept);
		ioc.run();
	}
	catch (std::exception const& e) {
		std::cerr << "Error: " << e.what() << std::endl;
	}
	return 0;
}