import jwt from 'jsonwebtoken';
import chatService from './global_chat.js';
import friendsService from './friends.js';
import gameRoomService from './game_room.js';
import playerStatsService from './player_stats.js';

// Store game state per room
const gameRooms = new Map();

// Store tetris duel rooms  { roomCode → Map<socketId, socket> }
const tetrisRooms = new Map();

// Store io instance globally for use in routes
let ioInstance = null;

export function getIO() {
	return ioInstance;
}

// Broadcast rooms list to all connected clients
async function broadcastRoomsList(io) {
	try {
		const rooms = await gameRoomService.listActiveRooms();
		io.emit('game-rooms-updated', { rooms });
	} catch (err) {
		console.error('Error broadcasting rooms list:', err);
	}
}

// Save round points to database (only the difference from round start)
async function saveRoundPoints(currentScores, roundStartScores) {
	for (const [username, currentPoints] of Object.entries(currentScores)) {
		const startPoints = roundStartScores[username] || 0;
		const pointsEarned = currentPoints - startPoints;
		if (pointsEarned !== 0) {
			try {
				await playerStatsService.addPointsByUsername(username, pointsEarned);
				console.log(`Saved ${pointsEarned} points for ${username}`);
			} catch (err) {
				console.error(`Error saving points for ${username}:`, err);
			}
		}
	}
}

function setupSocketIO(io)
{
	ioInstance = io;
	io.use((socket, next) =>
	{
		const token = socket.handshake.auth.token;
		if (!token)
			return (next(new Error('Authentication error')));

		try
		{
			const decoded = jwt.verify(token, process.env.JWT_SECRET);
			socket.user = decoded;
			next();
		}
		catch(err)
		{
			next(new Error('Authentication error'));
		}
	});

	io.on('connection', async (socket) =>
	{
		console.log(`User connected: ${socket.user.username}`);

		socket.join('general-chat');

		// Send recent messages and friend IDs on connection
		try {
			const [recentMessages, friendIds] = await Promise.all([
				chatService.getRecentMessages(50),
				friendsService.getFriendIds(socket.user.userId)
			]);

			socket.emit('chat-init', {
				messages: recentMessages,
				friendIds: friendIds
			});
		} catch (err) {
			console.error('Error fetching initial data:', err);
		}

		socket.on('chat-message', async(data) =>
		{
			try
			{
				const message = await chatService.saveMessage(socket.user.userId, data.content);
				socket.broadcast.to('general-chat').emit('chat-message',
				{
					id: message.id,
					sender_id: socket.user.userId,
					username: socket.user.username,
					content: message.content,
					created_at: message.created_at
				});
			}
			catch (err)
			{
				console.error('Error saving message:', err);
				socket.emit('error', {message: 'Failed to send message'});
			}
		});

		// ============================================
		// GAME ROOM EVENTS
		// ============================================

		// Join a game room
		socket.on('game-join-room', async (data) => {
			console.log('Received game-join-room from', socket.user.username, 'data:', data);
			const roomId = `game-room-${data.roomId}`;
			socket.join(roomId);
			socket.gameRoomId = roomId;
			socket.gameRoomDbId = data.roomId;
			console.log(`${socket.user.username} joined ${roomId}, socket.gameRoomId set to:`, socket.gameRoomId);

			// Send confirmation to the socket that joined
			socket.emit('game-room-joined', {
				roomId: data.roomId,
				success: true
			});

			// Get updated player list from DB
			try {
				const players = await gameRoomService.getRoomPlayers(data.roomId);
				// Notify ALL players in the room (including the one who joined) with updated player list
				io.to(roomId).emit('game-players-updated', { players });
			} catch (err) {
				console.error('Error getting room players:', err);
			}

			// Notify others in the room that someone joined
			socket.to(roomId).emit('game-player-joined', {
				username: socket.user.username,
				userId: socket.user.userId
			});

			// Broadcast rooms list update to everyone
			broadcastRoomsList(io);

			// Send current game state if game is in progress
			const gameState = gameRooms.get(roomId);
			if (gameState && gameState.isPlaying) {
				socket.emit('game-state-sync', {
					isPlaying: gameState.isPlaying,
					drawer: gameState.drawer,
					wordLength: gameState.currentWord ? gameState.currentWord.length : 0,
					revealedLetters: gameState.revealedLetters,
					revealedWord: gameState.revealedWord || [],
					guessedLetters: gameState.guessedLetters,
					players: gameState.players
				});
			}
		});

		// Leave a game room
		socket.on('game-leave-room', async () => {
			if (socket.gameRoomId) {
				const roomId = socket.gameRoomId;
				const dbRoomId = socket.gameRoomDbId;

				socket.to(roomId).emit('game-player-left', {
					username: socket.user.username,
					userId: socket.user.userId
				});
				socket.leave(roomId);
				console.log(`${socket.user.username} left ${roomId}`);

				// Get updated player list and broadcast to remaining players
				if (dbRoomId) {
					try {
						const players = await gameRoomService.getRoomPlayers(dbRoomId);
						io.to(roomId).emit('game-players-updated', { players });
					} catch (err) {
						// Room may have been deleted
						console.log('Room may have been deleted:', err.message);
					}
				}

				socket.gameRoomId = null;
				socket.gameRoomDbId = null;

				// Broadcast updated rooms list
				broadcastRoomsList(io);
			}
		});

		// Start the game
		socket.on('game-start', (data) => {
			console.log('Received game-start event from', socket.user.username);
			console.log('socket.gameRoomId:', socket.gameRoomId);

			const gameStartedData = {
				drawer: data.drawer,
				players: data.players
			};

			const roomId = socket.gameRoomId;

			// If no roomId, still start the game for this socket only
			if (!roomId) {
				console.log('WARNING: No roomId for socket, starting game for this socket only');
				socket.emit('game-started', gameStartedData);
				return;
			}

			// Initialize scores for all players
			const scores = {};
			data.players.forEach(p => scores[p] = 0);

			const gameState = {
				isPlaying: true,
				currentWord: '',
				revealedLetters: [],
				drawer: data.drawer,
				players: data.players,
				currentPlayerIndex: 0,
				guessedLetters: [],
				scores: scores,
				roundStartScores: { ...scores }
			};
			gameRooms.set(roomId, gameState);

			// Emit to OTHER players in the room
			socket.to(roomId).emit('game-started', gameStartedData);

			// Emit directly to this socket (the one who started the game)
			socket.emit('game-started', gameStartedData);

			console.log(`Game started in ${roomId} by ${socket.user.username}`);
		});

		// Drawer sets the word
		socket.on('game-set-word', (data) => {
			const roomId = socket.gameRoomId;
			if (!roomId) return;

			const gameState = gameRooms.get(roomId);
			if (!gameState) return;

			gameState.currentWord = data.word.toLowerCase();
			gameState.revealedLetters = new Array(data.word.length).fill(false);
			gameState.revealedWord = new Array(data.word.length).fill('_');
			gameState.guessedLetters = [];
			gameState.wrongGuesses = 0;

			// Initialize scores if not already done
			if (!gameState.scores) {
				gameState.scores = {};
				gameState.players.forEach(p => gameState.scores[p] = 0);
			}

			// Notify all players (without revealing the word)
			io.to(roomId).emit('game-word-set', {
				wordLength: data.word.length,
				drawer: socket.user.username,
				revealedWord: gameState.revealedWord,
				scores: gameState.scores
			});
		});

		// Drawing data (real-time)
		socket.on('game-draw', (data) => {
			const roomId = socket.gameRoomId;
			if (!roomId) return;

			// Broadcast drawing to all other players in the room
			socket.to(roomId).emit('game-draw', {
				x1: data.x1,
				y1: data.y1,
				x2: data.x2,
				y2: data.y2,
				color: data.color,
				lineWidth: data.lineWidth
			});
		});

		// Clear canvas
		socket.on('game-clear-canvas', () => {
			const roomId = socket.gameRoomId;
			if (!roomId) return;

			socket.to(roomId).emit('game-clear-canvas');
		});

		// Player makes a guess
		socket.on('game-guess', (data) => {
			const roomId = socket.gameRoomId;
			if (!roomId) return;

			const gameState = gameRooms.get(roomId);
			if (!gameState || !gameState.currentWord) return;

			const guess = data.guess.toLowerCase();
			const isLetter = guess.length === 1;
			let success = false;
			let points = 0;
			const username = socket.user.username;

			// Initialize scores if needed
			if (!gameState.scores) {
				gameState.scores = {};
				gameState.players.forEach(p => gameState.scores[p] = 0);
			}
			if (!gameState.scores[username]) {
				gameState.scores[username] = 0;
			}

			if (isLetter) {
				// Check if letter was already guessed
				if (gameState.guessedLetters.includes(guess)) {
					socket.emit('game-guess-result', {
						guess,
						success: false,
						type: 'letter',
						message: 'Lettre deja proposee',
						username: username,
						scores: gameState.scores
					});
					return;
				}

				gameState.guessedLetters.push(guess);

				// Check each position and reveal the actual letter
				let lettersFound = 0;
				for (let i = 0; i < gameState.currentWord.length; i++) {
					if (gameState.currentWord[i] === guess) {
						gameState.revealedLetters[i] = true;
						gameState.revealedWord[i] = guess;
						success = true;
						lettersFound++;
					}
				}

				// Points: 10 per letter found, -5 for wrong guess
				if (success) {
					points = lettersFound * 10;
					gameState.scores[username] += points;
				} else {
					points = -5;
					gameState.scores[username] += points;
					gameState.wrongGuesses++;
				}
			} else {
				// Full word guess
				success = guess === gameState.currentWord;
				if (success) {
					gameState.revealedLetters = gameState.revealedLetters.map(() => true);
					gameState.revealedWord = gameState.currentWord.split('');
					// Bonus points for guessing the whole word
					const remainingLetters = gameState.revealedLetters.filter(r => !r).length;
					points = 50 + (remainingLetters * 5);
					gameState.scores[username] += points;
				} else {
					points = -10;
					gameState.scores[username] += points;
					gameState.wrongGuesses++;
				}
			}

			// Broadcast result to all players with the revealed word (actual letters)
			io.to(roomId).emit('game-guess-result', {
				guess,
				success,
				type: isLetter ? 'letter' : 'word',
				username: username,
				revealedLetters: gameState.revealedLetters,
				revealedWord: gameState.revealedWord,
				points: points,
				scores: gameState.scores
			});

			// Check if word is complete
			if (gameState.revealedLetters.every(r => r)) {
				// Bonus points for the drawer
				const drawerBonus = Math.max(0, 30 - (gameState.wrongGuesses * 5));
				if (gameState.scores[gameState.drawer]) {
					gameState.scores[gameState.drawer] += drawerBonus;
				}

				// Save points to database for all players
				saveRoundPoints(gameState.scores, gameState.roundStartScores || {});
				// Update round start scores for next round
				gameState.roundStartScores = { ...gameState.scores };

				io.to(roomId).emit('game-word-found', {
					word: gameState.currentWord,
					winner: username,
					scores: gameState.scores,
					drawerBonus: drawerBonus
				});
			}
		});

		// Next round
		socket.on('game-next-round', (data) => {
			const roomId = socket.gameRoomId;
			if (!roomId) return;

			const gameState = gameRooms.get(roomId);
			if (!gameState) return;

			gameState.currentWord = '';
			gameState.revealedLetters = [];
			gameState.guessedLetters = [];
			gameState.drawer = data.drawer;

			io.to(roomId).emit('game-new-round', {
				drawer: data.drawer
			});
		});

		// End game
		socket.on('game-end', () => {
			const roomId = socket.gameRoomId;
			if (!roomId) return;

			gameRooms.delete(roomId);
			io.to(roomId).emit('game-ended');
		});

		// ============================================
		// TETRIS DUEL EVENTS
		// ============================================

		socket.on('tetris:join', ({ roomCode }) => {
			const code = String(roomCode).toUpperCase().slice(0, 8);

			// Quitter l'ancienne room tetris si besoin
			if (socket.tetrisRoomCode) {
				_tetrisLeave(socket);
			}

			if (!tetrisRooms.has(code)) {
				tetrisRooms.set(code, new Map());
			}
			const room = tetrisRooms.get(code);

			if (room.size >= 2) {
				socket.emit('tetris:room-status', { status: 'full', players: [] });
				return;
			}

			room.set(socket.id, socket);
			socket.tetrisRoomCode = code;

			const players = [...room.values()].map(s => s.user.username);

			if (room.size === 1) {
				socket.emit('tetris:room-status', { status: 'waiting', players });
			} else {
				// Notifier les deux joueurs
				for (const s of room.values()) {
					s.emit('tetris:room-status', { status: 'ready', players });
				}
				// Notifier l'adversaire qu'un nouveau joueur a rejoint
				for (const [id, s] of room) {
					if (id !== socket.id) {
						s.emit('tetris:opponent-joined', { username: socket.user.username });
					}
				}
			}
		});

		socket.on('tetris:leave', () => {
			_tetrisLeave(socket);
		});

		// Relay pur : grid-update → adversaire uniquement
		socket.on('tetris:grid-update', (data) => {
			_tetrisRelayToOpponent(socket, 'tetris:grid-update', data);
		});

		// Relay pur : lines-cleared → adversaire uniquement
		socket.on('tetris:lines-cleared', (data) => {
			_tetrisRelayToOpponent(socket, 'tetris:lines-cleared', data);
		});

		// start-duel → relayé aux DEUX joueurs de la room (inclut l'émetteur)
		socket.on('tetris:start-duel', () => {
			const code = socket.tetrisRoomCode;
			if (!code) return;
			const room = tetrisRooms.get(code);
			if (!room || room.size < 2) return;
			for (const s of room.values()) {
				s.emit('tetris:start-duel');
			}
		});

		// pause → relayé aux DEUX joueurs de la room
		socket.on('tetris:pause', () => {
			const code = socket.tetrisRoomCode;
			if (!code) return;
			const room = tetrisRooms.get(code);
			if (!room) return;
			for (const s of room.values()) {
				s.emit('tetris:pause');
			}
		});

		// stop → relayé aux DEUX joueurs de la room
		socket.on('tetris:stop', () => {
			const code = socket.tetrisRoomCode;
			if (!code) return;
			const room = tetrisRooms.get(code);
			if (!room) return;
			for (const s of room.values()) {
				s.emit('tetris:stop');
			}
		});

		// settings → relayé aux DEUX joueurs de la room
		socket.on('tetris:settings', (data) => {
			const code = socket.tetrisRoomCode;
			if (!code) return;
			const room = tetrisRooms.get(code);
			if (!room) return;
			for (const s of room.values()) {
				s.emit('tetris:settings', data);
			}
		});

		// game-over → relayé en opponent-game-over chez l'adversaire
		socket.on('tetris:game-over', (data) => {
			_tetrisRelayToOpponent(socket, 'tetris:opponent-game-over', data);
		});

		socket.on('disconnect', async () =>
		{
			// Nettoyage room tetris
			if (socket.tetrisRoomCode) {
				_tetrisLeave(socket);
			}

			console.log(`User disconnected: ${socket.user.username}`);

			// Notify game room if player was in one
			if (socket.gameRoomId) {
				const roomId = socket.gameRoomId;
				const dbRoomId = socket.gameRoomDbId;

				socket.to(roomId).emit('game-player-left', {
					username: socket.user.username,
					userId: socket.user.userId
				});

				// Get updated player list and broadcast
				if (dbRoomId) {
					try {
						const players = await gameRoomService.getRoomPlayers(dbRoomId);
						io.to(roomId).emit('game-players-updated', { players });
					} catch (err) {
						console.log('Room may have been deleted on disconnect:', err.message);
					}
				}

				// Broadcast updated rooms list
				broadcastRoomsList(io);
			}
		});
	});
}

// ── Helpers tetris duel ──────────────────────────────────────────────────

function _tetrisLeave(socket) {
	const code = socket.tetrisRoomCode;
	if (!code) return;
	const room = tetrisRooms.get(code);
	if (room) {
		room.delete(socket.id);
		// Notifier l'adversaire restant
		for (const s of room.values()) {
			s.emit('tetris:opponent-left');
			s.emit('tetris:room-status', { status: 'waiting', players: [s.user.username] });
		}
		if (room.size === 0) tetrisRooms.delete(code);
	}
	socket.tetrisRoomCode = null;
}

function _tetrisRelayToOpponent(socket, event, data) {
	const code = socket.tetrisRoomCode;
	if (!code) return;
	const room = tetrisRooms.get(code);
	if (!room) return;
	for (const [id, s] of room) {
		if (id !== socket.id) s.emit(event, data);
	}
}

export { broadcastRoomsList };
export default setupSocketIO;