/**
 * Test Server for AI Interactive Fiction
 * Simplified version that sends test paragraphs instead of using LLM
 */

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 { existsSync, mkdirSync, copyFileSync } from 'fs';
import { textToParagraphs } from './interfaces/turn-result';

// 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 = 300; // Try enough ports to skip OS-excluded ranges.

// Serve static files from the public directory. Keep browser modules uncached
// during local development so fixes are visible without a hard cache clear.
app.use(express.static(path.join(__dirname, '../public'), {
  etag: false,
  lastModified: false,
  setHeaders: (res) => {
    res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate');
    res.setHeader('Pragma', 'no-cache');
    res.setHeader('Expires', '0');
  }
}));

// Test paragraphs to send to the client
const TEST_PARAGRAPHS = [
  "You stand at the entrance of a mysterious cave. The air is cool and damp, carrying the scent of earth and ancient stone. Shadows dance on the walls as your torch flickers in the gentle breeze.",
  "As you venture deeper, the passage narrows. Stalactites hang from the ceiling like stone daggers, their surfaces glistening with moisture. The sound of dripping water echoes through the silence.",
  "Suddenly, the passage opens into a vast chamber. Crystal formations catch the light of your torch, sending rainbow reflections across the walls. In the center of the room stands an ancient stone pedestal, its surface carved with symbols from a forgotten language."
];

// Handle socket connections
io.on('connection', (socket) => {
  console.log(`New client connected: ${socket.id}`);
  let currentParagraphIndex = 0;
  let gameRunning = false;
  let nextTurnId = 1;
  const saveGames = new Set<number>();

  const startDemoGame = () => {
    gameRunning = true;
    nextTurnId = 1;
    currentParagraphIndex = 0;
    socket.emit('narrativeResponse', {
      turnId: nextTurnId++,
      paragraphs: [
        ...textToParagraphs("#chapter[Interactive Fiction Test]\n\nWelcome to the Interactive Fiction Test. This is a simplified version that sends predefined paragraphs instead of using an LLM."),
        ...textToParagraphs(TEST_PARAGRAPHS[0]),
      ],
      choices: [],
      inputMode: 'text',
      gameState: {
        currentRoomId: 'test-room',
      },
    });
  };

  const normalizeSaveSlot = (slot: unknown): number => {
    const value = Number(slot);
    return Number.isInteger(value) && value > 0 ? value : 1;
  };

  socket.on('gameApi', (request, respond) => {
    try {
      const method = String(request?.method || '');
      const args = Array.isArray(request?.args) ? request.args : [];
      let response: any;

      switch (method) {
        case 'newGame':
        case 'newGame()':
          startDemoGame();
          response = { success: true, result: true, running: true, canLoad: saveGames.size > 0 };
          break;
        case 'loadGame':
        case 'loadGame()': {
          const slot = normalizeSaveSlot(args[0]);
          if (!saveGames.has(slot)) {
            response = { success: false, error: 'missing_save', result: false };
            break;
          }
          startDemoGame();
          socket.emit('gameLoaded', { slot });
          response = { success: true, result: true, running: true, slot };
          break;
        }
        case 'saveGame':
        case 'saveGame()': {
          if (!gameRunning) {
            response = { success: false, error: 'game_not_running', result: false };
            break;
          }
          const slot = normalizeSaveSlot(args[0]);
          saveGames.add(slot);
          socket.emit('gameSaved', { slot });
          response = { success: true, result: true, slot };
          break;
        }
        case 'hasSaveGame':
        case 'hasSaveGame()': {
          const slot = normalizeSaveSlot(args[0]);
          response = { success: true, result: saveGames.has(slot), slot };
          break;
        }
        case 'getSaveGames':
        case 'getSaveGames()':
          response = { success: true, result: Array.from(saveGames).sort((a, b) => a - b) };
          break;
        case 'isGameRunning':
        case 'isGameRunning()':
          response = { success: true, result: gameRunning };
          break;
        default:
          response = { success: false, error: `unknown_method:${method}` };
      }

      if (typeof respond === 'function') respond(response);
    } catch (error) {
      if (typeof respond === 'function') {
        respond({ success: false, error: error instanceof Error ? error.message : String(error) });
      }
    }
  });
  
  // Process player command
  socket.on('playerCommand', async (data) => {
    try {
      console.log(`Received command: ${data.command}`);
      
      // Send narrative response to client
      socket.emit('narrativeResponse', {
        turnId: nextTurnId++,
        paragraphs: textToParagraphs(String(data.command || '')),
        choices: [],
        inputMode: 'text',
        gameState: {
          currentRoomId: "test-room"
        },
        suggestions: ["look around", "examine pedestal", "touch crystals"]
      });
      
    } catch (error) {
      console.error('Error processing command:', error);
      socket.emit('error', { message: 'Failed to process command. Please try again.' });
    }
  });
  
  // Handle disconnection
  socket.on('disconnect', () => {
    console.log(`Client disconnected: ${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/music'),
    path.join(__dirname, '../public/sounds'),
    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
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.removeAllListeners('error');
        server.removeAllListeners('listening');
        server.once('listening', () => {
          console.log(`AI Interactive Fiction TEST SERVER running on http://localhost:${currentPort}`);
          console.log('This server is sending predefined test paragraphs instead of using an LLM');
          resolve();
        });
        server.once('error', (error: NodeJS.ErrnoException) => {
          // If port is in use, try next port
          if (error.code === 'EADDRINUSE' || error.code === 'EACCES') {
            console.log(`Port ${currentPort} is unavailable (${error.code}), trying next port...`);
            server.close();
            currentPort++;
            reject();
          } else {
            // For other errors, log and reject
            console.error('Server error:', error);
            reject(error);
          }
        });
        server.listen(currentPort);
      });
      
      // 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
    }
  }
}

// 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 };
