/**
 * Command-line interface for running the interactive fiction game
 */

import * as readline from 'readline';
import * as path from 'path';
import * as dotenv from 'dotenv';
import { TextAdventureEngine } from '../engine/game-engine';
import { OpenRouterProvider } from '../llm/openrouter-provider';
import { ActionRequest, NarrativeRequest } from '../interfaces/llm';

// Load environment variables
dotenv.config();

export class GameRunner {
  private engine: TextAdventureEngine;
  private llmProvider: OpenRouterProvider;
  private rl: readline.Interface | null = null;
  private gameContext: string = '';
  private gameHistory: string[] = [];
  private suggestedCommands: string[] = [];

  constructor() {
    this.engine = new TextAdventureEngine();
    this.llmProvider = new OpenRouterProvider();
  }

  /**
   * Initialize the game
   */
  public async initialize(worldPath: string): Promise<void> {
    console.log('Initializing game...');
    
    // Initialize LLM provider
    const apiKey = process.env.OPENROUTER_API_KEY;
    const model = process.env.OPENROUTER_MODEL;
    
    if (!apiKey || !model) {
      throw new Error('Missing required environment variables: OPENROUTER_API_KEY and/or OPENROUTER_MODEL');
    }
    
    await this.llmProvider.initialize({
      apiKey,
      model,
      temperature: 0.7,
      maxTokens: 800
    });
    
    // Load the world
    const resolvedPath = path.resolve(worldPath);
    console.log(`Loading world from ${resolvedPath}...`);
    await this.engine.loadWorld(resolvedPath);
    
    console.log('Game initialized successfully!');
  }

  /**
   * Start the game in CLI mode
   */
  public async start(): Promise<void> {
    // Create readline interface for CLI mode
    this.rl = readline.createInterface({
      input: process.stdin,
      output: process.stdout
    });
    
    try {
      // Display introduction
      const introText = await this.engine.start();
      console.log('\n' + introText + '\n');
      
      // Look at initial room
      const initialLook = this.engine.processAction({ action: 'look', confidence: 1 });
      
      // Generate narrative description
      const narrativeRequest: NarrativeRequest = {
        action: 'look',
        result: initialLook.message,
        roomDescription: this.engine.getCurrentRoomDescription(),
        visibleObjects: this.engine.getVisibleObjects(),
        visibleCharacters: this.engine.getVisibleCharacters(),
        tone: 'descriptive'
      };
      
      const narrative = await this.llmProvider.generateNarrative(narrativeRequest);
      console.log('\n' + narrative.text + '\n');
      
      // Store suggestions if available
      if (narrative.suggestions && narrative.suggestions.length > 0) {
        this.suggestedCommands = narrative.suggestions;
      }
      
      // Update game context
      this.updateGameContext(narrative.text);
      
      // Start the game loop
      this.gameLoop();
    } catch (error) {
      console.error('Error starting game:', error);
      this.end();
    }
  }

  /**
   * The main game loop for CLI mode
   */
  private gameLoop(): void {
    if (!this.rl) return;
    
    this.rl.question('> ', async (input) => {
      if (input.toLowerCase() === 'quit' || input.toLowerCase() === 'exit') {
        this.end();
        return;
      }
      
      const response = await this.processCommand(input);
      console.log('\n' + response + '\n');
      
      // Continue the game loop
      this.gameLoop();
    });
  }

  /**
   * Process a player command and return the narrative response
   * Used by both CLI and web interfaces
   */
  public async processCommand(input: string): Promise<string> {
    try {
      // Process player input
      const actionRequest: ActionRequest = {
        playerInput: input,
        currentRoom: this.engine.getWorldModel().rooms[this.engine.getCurrentState().currentRoomId].name,
        visibleObjects: this.engine.getVisibleObjects().map(id => this.engine.getWorldModel().objects[id].name),
        visibleCharacters: this.engine.getVisibleCharacters().map(id => this.engine.getWorldModel().characters[id].name),
        possibleActions: this.engine.getAvailableActions(),
        inventory: this.engine.getCurrentState().inventory.map(id => this.engine.getWorldModel().objects[id].name),
        gameContext: this.gameContext
      };
      
      if (this.rl) {
        console.log('Thinking...');
      }
      
      // Translate player input to action
      const action = await this.llmProvider.translateAction(actionRequest);
      
      // Process the action in the game engine
      const actionResult = this.engine.processAction(action);
      
      // If state changed, update it
      if (actionResult.stateChanged && actionResult.newState) {
        this.engine.getCurrentState().currentRoomId = actionResult.newState.currentRoomId;
        this.engine.getCurrentState().inventory = actionResult.newState.inventory;
        this.engine.getCurrentState().visitedRooms = actionResult.newState.visitedRooms;
        this.engine.getCurrentState().flags = actionResult.newState.flags;
        this.engine.getCurrentState().counters = actionResult.newState.counters;
      }
      
      // Generate narrative description
      const narrativeRequest: NarrativeRequest = {
        action: `${action.action}${action.object ? ' ' + action.object : ''}${action.target ? ' on ' + action.target : ''}`,
        result: actionResult.message,
        roomDescription: this.engine.getCurrentRoomDescription(),
        visibleObjects: this.engine.getVisibleObjects().map(id => this.engine.getWorldModel().objects[id].name),
        visibleCharacters: this.engine.getVisibleCharacters().map(id => this.engine.getWorldModel().characters[id].name),
        previousContext: this.gameHistory.slice(-3).join('\n'),
        tone: 'descriptive'
      };
      
      const narrative = await this.llmProvider.generateNarrative(narrativeRequest);
      
      // Store suggestions if available
      if (narrative.suggestions && narrative.suggestions.length > 0) {
        this.suggestedCommands = narrative.suggestions;
      }
      
      // Update game context with the new narrative
      this.updateGameContext(narrative.text);
      
      // Return the narrative text
      return narrative.text;
      
    } catch (error) {
      console.error('Error processing input:', error);
      return 'Something went wrong. Please try again.';
    }
  }

  /**
   * End the game
   */
  public end(): void {
    console.log('\nThanks for playing!');
    if (this.rl) {
      this.rl.close();
      this.rl = null;
    }
    this.engine.end();
    if (process.env.NODE_ENV !== 'production') {
      process.exit(0);
    }
  }

  /**
   * Update the game context with new narrative
   */
  private updateGameContext(narrative: string): void {
    // Add to history
    this.gameHistory.push(narrative);
    
    // Keep history limited to last 10 entries
    if (this.gameHistory.length > 10) {
      this.gameHistory.shift();
    }
    
    // Update current context (last 5 entries)
    this.gameContext = this.gameHistory.slice(-5).join('\n');
  }
  
  /**
   * Get the current game state
   * Used by web interface
   */
  public getGameState() {
    return {
      world: this.engine.getWorldModel(),
      currentRoomId: this.engine.getCurrentState().currentRoomId,
      inventory: this.engine.getCurrentState().inventory,
      visitedRooms: this.engine.getCurrentState().visitedRooms,
      flags: this.engine.getCurrentState().flags,
      counters: this.engine.getCurrentState().counters
    };
  }
  
  /**
   * Get the current room description
   * Used by web interface
   */
  public getCurrentRoomDescription(): string {
    const roomId = this.engine.getCurrentState().currentRoomId;
    return this.engine.getWorldModel().rooms[roomId].description;
  }
  
  /**
   * Get suggested actions for the current game state
   * Used by web interface
   */
  public getSuggestions(): string[] {
    return this.suggestedCommands;
  }
  
  /**
   * Load a saved game state
   * Used by web interface
   */
  public loadGameState(savedState: any): void {
    // Set the current state to match the saved state
    this.engine.getCurrentState().currentRoomId = savedState.currentRoomId;
    this.engine.getCurrentState().inventory = savedState.inventory;
    this.engine.getCurrentState().visitedRooms = savedState.visitedRooms;
    this.engine.getCurrentState().flags = savedState.flags;
    this.engine.getCurrentState().counters = savedState.counters;
  }
}