/**
 * AI Interactive Fiction - Web Server
 * Serves the web UI and handles WebSocket communication
 */

import path from 'path';
import express from 'express';
import http from 'http';
import { Server as SocketIOServer } from 'socket.io';
import * as dotenv from 'dotenv';
import { GameRunner } from './cli/game-runner';
import { existsSync, mkdirSync, copyFileSync } from 'fs';

// Load environment variables
dotenv.config();

// Create Express application
const app = express();
const server = http.createServer(app);
const io = new SocketIOServer(server);

// Get port from environment variables or use default
const DEFAULT_PORT = 3001;
const PORT = process.env.PORT ? parseInt(process.env.PORT) : DEFAULT_PORT;
const PORT_RANGE = 10; // Try up to 10 ports starting from the default

// Serve static files from the public directory
app.use(express.static(path.join(__dirname, '../public')));

// Set up game sessions
const gameSessions = new Map<string, GameRunner>();

// Handle socket connections
io.on('connection', (socket) => {
  console.log(`New client connected: ${socket.id}`);
  
  // Start a new game
  socket.on('startGame', async () => {
    try {
      // Initialize game runner
      const gameRunner = new GameRunner();
      const worldFile = process.env.DEFAULT_WORLD_FILE || './data/worlds/example_world.yml';
      
      // Initialize the game
      await gameRunner.initialize(worldFile);
      
      // Store game session
      gameSessions.set(socket.id, gameRunner);
      
      // Send introduction to client
      const gameState = gameRunner.getGameState();
      socket.emit('gameIntroduction', {
        introduction: gameState.world.introduction,
        initialRoomDescription: gameRunner.getCurrentRoomDescription(),
        currentRoomId: gameState.currentRoomId
      });
      
    } catch (error) {
      console.error('Error starting game:', error);
      socket.emit('error', { message: 'Failed to start game. Please try again.' });
    }
  });
  
  // Process player command
  socket.on('playerCommand', async (data) => {
    try {
      const gameRunner = gameSessions.get(socket.id);
      
      if (!gameRunner) {
        socket.emit('error', { message: 'Game session not found. Please start a new game.' });
        return;
      }
      
      // Process command and get response
      const response = await gameRunner.processCommand(data.command);
      
      // Send narrative response to client
      socket.emit('narrativeResponse', {
        text: response,
        gameState: {
          currentRoomId: gameRunner.getGameState().currentRoomId
        },
        suggestions: gameRunner.getSuggestions()
      });
      
    } catch (error) {
      console.error('Error processing command:', error);
      socket.emit('error', { message: 'Failed to process command. Please try again.' });
    }
  });
  
  // Save game state
  socket.on('saveGame', () => {
    try {
      const gameRunner = gameSessions.get(socket.id);
      
      if (!gameRunner) {
        socket.emit('error', { message: 'Game session not found. Please start a new game.' });
        return;
      }
      
      // Store save data in session
      socket.data.savedGame = gameRunner.getGameState();
      
      socket.emit('gameSaved');
      
    } catch (error) {
      console.error('Error saving game:', error);
      socket.emit('error', { message: 'Failed to save game. Please try again.' });
    }
  });
  
  // Load game state
  socket.on('loadGame', () => {
    try {
      const gameRunner = gameSessions.get(socket.id);
      
      if (!gameRunner) {
        socket.emit('error', { message: 'Game session not found. Please start a new game.' });
        return;
      }
      
      // Check if there's a saved game
      if (!socket.data.savedGame) {
        socket.emit('error', { message: 'No saved game found.' });
        return;
      }
      
      // Load saved game
      gameRunner.loadGameState(socket.data.savedGame);
      
      // Send current state to client
      socket.emit('gameLoaded', {
        currentRoomDescription: gameRunner.getCurrentRoomDescription(),
        currentRoomId: gameRunner.getGameState().currentRoomId
      });
      
    } catch (error) {
      console.error('Error loading game:', error);
      socket.emit('error', { message: 'Failed to load game. Please try again.' });
    }
  });
  
  // Handle disconnection
  socket.on('disconnect', () => {
    console.log(`Client disconnected: ${socket.id}`);
    
    // Clean up game session
    if (gameSessions.has(socket.id)) {
      gameSessions.delete(socket.id);
    }
  });
});

// Ensure required asset folders exist
function ensureDirectories() {
  const dirs = [
    path.join(__dirname, '../public'),
    path.join(__dirname, '../public/js'),
    path.join(__dirname, '../public/css'),
    path.join(__dirname, '../public/images'),
    path.join(__dirname, '../public/fonts')
  ];
  
  for (const dir of dirs) {
    if (!existsSync(dir)) {
      mkdirSync(dir, { recursive: true });
    }
  }
}

// Copy kokoro-js library from node_modules if not already present
function ensureKokoroJs() {
  const source = path.join(__dirname, '../node_modules/kokoro-js/dist/index.js');
  const destination = path.join(__dirname, '../public/js/kokoro-js.js');
  
  if (existsSync(source) && !existsSync(destination)) {
    copyFileSync(source, destination);
    console.log(`Copied kokoro-js from ${source} to ${destination}`);
  }
}

// Start the server with port fallback
export async function startServer(initialPort: number, range: number): Promise<void> {
  let currentPort = initialPort;
  const maxPort = initialPort + range;
  
  // Try ports in the specified range
  while (currentPort < maxPort) {
    try {
      // Ensure directories exist
      ensureDirectories();
      
      // Ensure kokoro-js is copied
      try {
        ensureKokoroJs();
      } catch (error) {
        console.error('Error copying kokoro-js:', error);
      }
      
      // Try to start the server on the current port
      await new Promise<void>((resolve, reject) => {
        server.listen(currentPort, () => {
          console.log(`AI Interactive Fiction web server running on http://localhost:${currentPort}`);
          resolve();
        });
        
        server.on('error', (error: NodeJS.ErrnoException) => {
          // If port is in use, try next port
          if (error.code === 'EADDRINUSE') {
            console.log(`Port ${currentPort} is in use, trying next port...`);
            server.close();
            currentPort++;
            reject();
          } else {
            // For other errors, log and reject
            console.error('Server error:', error);
            reject(error);
          }
        });
      });
      
      // If we reach here, server started successfully
      return;
      
    } catch (error) {
      // If we reach the max port and still fail, throw an error
      if (currentPort >= maxPort - 1) {
        throw new Error(`Failed to start server on ports ${initialPort} to ${maxPort - 1}`);
      }
      
      // Otherwise try the next port
      // The loop continues as the rejection above increments currentPort
    }
  }
}

// Start the server when this module is run directly
if (require.main === module) {
  startServer(PORT, PORT_RANGE).catch(error => {
    console.error('Failed to start server:', error);
    process.exit(1);
  });
}

export { app, server, io };