import express from 'express';
import { Request, Response } from 'express';
import {
  addPlayerToLobby,
  getLobby,
  getLobbyByCode,
  getPlayerById,
  isPlayerInLobby,
  pool,
} from '../config/db';
import { Lobby } from '../models/Lobby';
import { generateLobbyCode } from '../utils/codeGenerator';
import { LobbyPlayer } from '../models/LobbyPlayers';
import { Player } from '../models/Player';
import { createLogger } from '../utils/logger';
import eventService from '../services/eventService';

const logger = createLogger('LobbyRoutes');
const router = express.Router();

// Gets all Lobbies not started
router.get('/', async (req: Request, res: Response) => {
  logger.info('Fetching lobbies');
  try {
    const result = await pool.query<Lobby>(
      `SELECT code, created_at, creator_id, numberofquestions, secondsperround, pointstowin, started, state, p.name FROM lobby l
       join player p on l.creator_id = p.id
      where started = false ORDER BY created_at DESC`);
    const lobbies = result.rows as Lobby[];
    res.json(lobbies);
  } catch (err) {
    logger.error('Error fetching lobbies', err);
    res.status(500).json({ error: 'Internal Server Error' });
  }
});

// Fetch a lobby by its code
router.get('/:code/lobby', async (req: Request, res: Response): Promise<void> => {
  const { code } = req.params;
  logger.info(`Fetching lobby with code: ${code}`);

  try {
    const result = await pool.query<Lobby>('SELECT * FROM lobby WHERE code = $1', [code]);

    if (result.rows.length === 0) {
      logger.info(`Lobby with code ${code} not found`);
      res.status(404).json({ error: 'Lobby not found' });
      return;
    }

    const lobby = result.rows[0] as Lobby;
    logger.info(`Lobby found:`, lobby);

    res.json(lobby);
  } catch (err) {
    logger.error(`Error fetching lobby with code ${code}:`, err);
    res.status(500).json({ error: 'Internal Server Error' });
  }
});

router.get('/events', (req: Request, res: Response) => {
  res.setHeader('Content-Type', 'text/event-stream');
  res.setHeader('Cache-Control', 'no-cache');
  res.setHeader('Connection', 'keep-alive');
  res.setHeader('X-Accel-Buffering', 'no');

  res.write(
    `data: ${JSON.stringify({ type: 'connected', message: 'Connected to lobby events' })}\n\n`
  );

  const removeClient = eventService.addGlobalClient(res);
  logger.info(`Client connected to lobby events.`);

  const heartbeatInterval = setInterval(() => {
    if (!res.writableEnded) {
      res.write(`:heartbeat\n\n`);
    } else {
      clearInterval(heartbeatInterval);
    }
  }, 30000);

  const sendGlobalEvent = (event: string) => {
    try {
      if (!res.writableEnded) {
        logger.info(`📨 Sending global lobby event to client:`, event);
        res.write(`data: ${event}\n\n`);
      }
    } catch (err) {
      logger.error('❌ Error sending global event to client:', err);
    }
  };

  const removeListener = eventService.onGlobalEvent(sendGlobalEvent);

  req.on('close', () => {
    clearInterval(heartbeatInterval);
    removeClient();
    removeListener();
    logger.info(`Client disconnected from lobby events.`);
  });
});

// creates a new lobby and automatically adds the current user to this lobby as the creator.
router.post('/creatorId/:creatorId/', async (req: Request, res: Response): Promise<void> => {
  const { creatorId } = req.params;

  try {
    const playerCheck = await pool.query('SELECT id FROM player WHERE id = $1', [creatorId]);
    if (playerCheck.rows.length === 0) {
      res.status(404).json({ error: 'Player not found' });
      return;
    }

    const lobbyCode = generateLobbyCode();

    const result = await pool.query<Lobby>(
      'INSERT INTO lobby (code, creator_id, numberOfQuestions, secondsPerRound, pointsToWin) ' +
        'VALUES ($1, $2, $3, $4, $5) RETURNING *',
      [lobbyCode, creatorId, 10, 15, 100]
    );

    const lobby = result.rows[0] as Lobby;
    logger.info('Created lobby:', lobby.code, 'with creator ID:', creatorId);

    const notificationData = {
      type: 'new_lobby',
      lobby,
      timestamp: new Date().toISOString(),
    };

    eventService.emitGlobalEvent(notificationData);
    logger.info('Notification for new lobby sent via event service');

    res.json(lobby);
  } catch (err) {
    logger.error('Error creating lobby', err);
    res.status(500).json({ error: 'Internal Server Error' });
  }
});

// Get the creator of a specific lobby
router.get('/:code/creator', async (req: Request, res: Response): Promise<void> => {
  const { code } = req.params;
  try {
    const result = await pool.query<Player>(
      `
      SELECT player.id, player.name
      FROM player
      JOIN lobby ON player.id = lobby.creator_id
      WHERE lobby.code = $1
    `,
      [code]
    );

    if (result.rows.length === 0) {
      res.status(404).json({ error: 'Lobby or creator not found' });
      return;
    }

    const creator = result.rows[0] as Player;
    logger.info('Creator:', creator);
    res.json(creator);

    eventService.emitLobbyEvent(code, { type: 'creator', creator });
  } catch (err) {
    logger.error('Error fetching lobby creator', err);
    res.status(500).json({ error: 'Internal Server Error' });
  }
});

// Fetch players with scores for a specific lobby - improved version
router.get('/:code/players', async (req: Request, res: Response): Promise<void> => {
  const { code } = req.params;
  try {
    logger.info(`Fetching players for lobby: ${code}`);

    const lobby = await getLobbyByCode(code);
    if (!lobby) {
      res.status(404).json({ error: 'Lobby not found' });
      return;
    }
    logger.info(`Lobby found:`, lobby);

    const lobby_player = await pool.query<LobbyPlayer>(
      `
        SELECT player.id, player.name
        FROM player
        JOIN lobby_players ON player.id = lobby_players.player
        WHERE lobby_players.lobby = $1
      `,
      [code]
    );

    const lobbyPlayers = lobby_player.rows as LobbyPlayer[];
    logger.info(`Found ${lobbyPlayers.length} players in lobby ${code}`);
    logger.info('Players data:', lobbyPlayers);

    res.json(lobbyPlayers);
  } catch (err) {
    logger.error(`Error fetching players for lobby ${code}:`, err);
    res.status(500).json({ error: 'Internal Server Error' });
  }
});

// Update a lobby
router.put('/:code/settings', async (req: Request, res: Response): Promise<void> => {
  const { code } = req.params;
  const { numberOfQuestions, secondsPerRound, pointsToWin, tags } = req.body;

  try {
    logger.info(`Updating lobby with code: ${code}`);
    logger.info(`Update data: numberOfQuestions=${numberOfQuestions}, secondsPerRound=${secondsPerRound}, pointsToWin=${pointsToWin}, tags=${JSON.stringify(tags)}`);

    // Check if the lobby exists
    const lobby = await getLobbyByCode(code);
    if (!lobby) {
      res.status(404).json({ error: 'Lobby not found' });
      return;
    }

    // Update the lobby with the provided fields
    const updatedLobby = await pool.query<Lobby>(
      `
      UPDATE lobby
      SET 
        numberOfQuestions = COALESCE($1, numberOfQuestions),
        secondsPerRound = COALESCE($2, secondsPerRound),
        pointsToWin = COALESCE($3, pointsToWin),
        tags = $4
      WHERE code = $5
      RETURNING *
      `,
      [numberOfQuestions, secondsPerRound, pointsToWin, tags, code]
    );

    logger.info(`Lobby updated:`, updatedLobby.rows[0]);

    eventService.emitLobbyEvent(code, {
      type: 'lobby_updated',
      lobby: updatedLobby.rows[0],
      timestamp: new Date().toISOString(),
    });

    res.json(updatedLobby.rows[0]);
  } catch (err) {
    logger.error(`Error updating lobby with code ${code}:`, err);
    res.status(500).json({ error: 'Internal Server Error' });
  }
});

// Add a player to a lobby
router.post('/:code/players/:playerId', async (req: Request, res: Response): Promise<void> => {
  const { code, playerId } = req.params;
  try {
    const lobby = await getLobbyByCode(code);
    if (!lobby) {
      res.status(404).json({ error: 'Lobby not found' });
      return;
    }

    const player = await getPlayerById(parseInt(playerId));
    if (!player) {
      res.status(409).json({ error: 'Player with this name already exists in the lobby' });
      return;
    }

    if (await isPlayerInLobby(parseInt(playerId), code)) {
      res.status(200).send();
      return;
    }

    await addPlayerToLobby(player.id, code);

    eventService.emitLobbyEvent(code, { type: 'new_player', player });

    res.json(player);
  } catch (err) {
    logger.error('Error adding player', err);
    res.status(500).json({ error: 'Internal Server Error' });
  }
});

// Start the game
router.post('/:code/start', async (req: Request, res: Response) => {
  const { code } = req.params;
  try {
    logger.info(`🎮 Starting game for lobby: ${code}`);
    const lobby = await getLobbyByCode(code);

    if (lobby === null) {
      return;
    }

    const initialState = {
      currentQuestion: null,
      questionsRemaining: lobby.numberOfQuestions,
      currentState: 'waiting',
      timer: lobby.secondsPerRound,
      totalQuestions: lobby.numberOfQuestions,
      currentQuestionIndex: 0,
    };

    await pool.query('UPDATE lobby SET started = TRUE, state = $1 WHERE code = $2', [
      JSON.stringify(initialState),
      code,
    ]);

    const gameStartEvent = {
      type: 'game_started',
      lobbyCode: code,
      timestamp: new Date().toISOString(),
      state: initialState,
    };
    logger.info(`🎲 Game start event created:`, gameStartEvent);

    eventService.emitLobbyEvent(code, gameStartEvent);
    res.json({ message: 'Game started', lobbyCode: code });
  } catch (err) {
    logger.error('Error starting game:', err);
    res.status(500).json({ error: 'Internal Server Error' });
  }
});

// Remove a player from a lobby
router.delete('/:code/players/:playerId', async (req: Request, res: Response): Promise<void> => {
  const { code, playerId } = req.params;

  try {
    logger.info(`Player ${playerId} is leaving lobby ${code}`);

    // 1. Get the lobby and verify it exists
    const lobby = await getLobbyByCode(code);
    const player = await getPlayerById(parseInt(playerId));

    if (lobby === null) {
      res.status(404).json({ error: 'Lobby not found' });
      return;
    } else if (player == null) {
      res.status(404).json({ error: 'Player not found in lobby' });
      return;
    }

    const isCreator = lobby.creator_id === parseInt(playerId);

    // 2. Remove the player from the lobby
    await pool.query('DELETE FROM lobby_players WHERE lobby = $1 AND player = $2', [
      code,
      playerId,
    ]);

    // 3. Check if there are any players left in the lobby
    const remainingPlayersResult = await pool.query<LobbyPlayer[]>(
      'SELECT player.id as player, player.name FROM lobby_players JOIN player ON lobby_players.player = player.id WHERE lobby = $1',
      [code]
    );

    const remainingPlayers = remainingPlayersResult.rows as unknown as LobbyPlayer[];
    logger.info(`Remaining players in lobby ${code}:`, remainingPlayers);

    if (remainingPlayers.length === 0) {
      // Delete the lobby
      await pool.query('DELETE FROM lobby WHERE code = $1', [code]);

      // Notify about lobby deletion
      eventService.emitGlobalEvent({
        type: 'lobby_deleted',
        lobbyCode: code,
        timestamp: new Date().toISOString(),
      });

      res.json({ message: 'Player removed and empty lobby deleted' });
      return;
    }

    // 5. If the leaving player was the creator and there are other players, assign a new creator
    if (isCreator && remainingPlayers.length > 0) {
      // Get the next player to be the creator
      const newCreator = remainingPlayers[0];

      logger.info(`Assigning new creator for lobby ${code}: Player ${newCreator.player}`);

      // Update the lobby with the new creator
      await pool.query('UPDATE lobby SET creator_id = $1 WHERE code = $2', [
        newCreator.player,
        code,
      ]);

      // Notify about creator change - global
      eventService.emitGlobalEvent({
        type: 'creator_changed',
        lobbyCode: code,
      });

      // Notify about creator change - specific lobby
      eventService.emitLobbyEvent(code, {
        type: 'creator_changed',
        lobbyCode: code,
        newCreator: newCreator.player,
        timestamp: new Date().toISOString(),
      });

      res.status(200).json({
        message: 'Player removed and new creator assigned',
        newCreator: newCreator.player,
      });
      return;
    }

    // 6. Player was not the creator, just notify about player leaving
    eventService.emitLobbyEvent(code, {
      type: 'player_left',
      playerId: playerId,
      playerName: player.name,
      timestamp: new Date().toISOString(),
    });

    res.status(200).json({ message: 'Player removed from lobby' });
  } catch (err) {
    logger.error('Error removing player from lobby:', err);
    res.status(500).json({ error: 'Internal Server Error' });
  }
});

// SSE endpoint for specific lobby events
router.get('/:code/events', async (req: Request, res: Response) => {
  try {
    const { code } = req.params;
    logger.info(`📡 New client connecting to events for lobby ${code}`);

    // Check if lobby exists
    const lobby = await getLobby(code);
    if (!lobby) {
      throw new Error('Lobby not found');
    }

    // Set SSE headers
    res.setHeader('Content-Type', 'text/event-stream');
    res.setHeader('Cache-Control', 'no-cache');
    res.setHeader('Connection', 'keep-alive');
    res.setHeader('X-Accel-Buffering', 'no');

    // Register client with event service
    const removeClient = eventService.addLobbyClient(code, res);
    logger.info(`👥 Client connected to lobby ${code}`);

    // Send initial connection message
    res.write(
      `data: ${JSON.stringify({ type: 'connected', message: 'Event stream connected' })}\n\n`
    );

    // Add a heartbeat to keep the connection alive
    const heartbeatInterval = setInterval(() => {
      if (!res.writableEnded) {
        res.write(`:heartbeat\n\n`);
      } else {
        clearInterval(heartbeatInterval);
      }
    }, 30000); // Send heartbeat every 30 seconds

    // Listen for events in this lobby
    const sendEvent = (event: string) => {
      try {
        if (!res.writableEnded) {
          logger.info(`📨 Sending lobby event to client:`, event);
          res.write(`data: ${event}\n\n`);
        }
      } catch (err) {
        logger.error('❌ Error sending event to client:', err);
      }
    };

    // Register with event service
    const removeListener = eventService.onLobbyEvent(code, sendEvent);

    // Clean up on client disconnect
    req.on('close', () => {
      logger.info(`❌ Client disconnected from lobby ${code} events`);
      clearInterval(heartbeatInterval);
      removeClient();
      removeListener();
    });
  } catch (err: unknown) {
    const error = err as Error;
    logger.error('🔥 Error setting up lobby event stream:', error.stack);
    if (!res.headersSent) {
      res.status(500).json({ error: 'Failed to set up event stream' });
    }
  }
});

// Delete a lobby and its players
router.delete('/:code', async (req: Request, res: Response): Promise<void> => {
  const { code } = req.params;

  try {
    logger.info(`Deleting lobby with code: ${code}`);

    const lobby = await getLobbyByCode(code);
    if (!lobby) {
      res.status(404).json({ error: 'Lobby not found' });
      return;
    }

    await pool.query('DELETE FROM lobby_players WHERE lobby = $1', [code]);
    logger.info(`All players removed from lobby ${code}`);

    await pool.query('DELETE FROM lobby WHERE code = $1', [code]);
    logger.info(`Lobby ${code} deleted successfully`);

    eventService.emitGlobalEvent({
      type: 'lobby_removed',
      lobbyCode: code,
      timestamp: new Date().toISOString(),
    });
    logger.info(`Notification sent for lobby deletion: ${code}`);

    res.status(200).json({ message: `Lobby ${code} and its players deleted successfully` });
  } catch (err) {
    logger.error(`Error deleting lobby with code ${code}:`, err);
    res.status(500).json({ error: 'Internal Server Error' });
  }
});

// Update timer for a specific lobby
router.post('/:code/timer', async (req: Request, res: Response): Promise<void> => {
  const { code } = req.params;
  const { time } = req.body;
  
  try {
    logger.info(`Setting timer for lobby ${code} to ${time} seconds`);
    
    // Validate input
    if (time === undefined || isNaN(Number(time))) {
      res.status(400).json({ error: 'Invalid time parameter' });
      return;
    }
    
    const timeValue = Number(time);
    
    // Get the current lobby
    const lobby = await getLobbyByCode(code);
    if (!lobby) {
      res.status(404).json({ error: 'Lobby not found' });
      return;
    }
    
    // Update the state with the new timer value
    //let state = lobby.state ? JSON.parse(lobby.state.toString()) : {};
    //state.timer = timeValue;
    
    // // Save the updated state to the database
    // await pool.query('UPDATE lobby SET state = $1 WHERE code = $2', [JSON.stringify(state), code]);
    // logger.info(`Timer updated in database for lobby ${code}`);
    
    // Emit an event to all clients
    const timerEvent = {
      type: 'timer_updated',
      lobbyCode: code,
      time: timeValue,
      timestamp: new Date().toISOString()
    };
    
    eventService.emitLobbyEvent(code, timerEvent);
    logger.info(`Timer update event emitted for lobby ${code}`);
    
    res.status(200).json({ 
      message: 'Timer updated successfully', 
      time: timeValue 
    });
  } catch (err) {
    logger.error(`Error updating timer for lobby ${code}:`, err);
    res.status(500).json({ error: 'Internal Server Error' });
  }
});

export default router;
