/**
 * HeadlessGame - Runs game simulation without WebSocket
 *
 * Main class for testing full player lifecycles. Uses GameEngine
 * with OutputCollector for capturing events and AutoResponder for
 * automatic question answering.
 */

import { GameEngine, type IGameStorage, type GameEvent } from '../game/engine/GameEngine.js';
import type { Player } from '../models/index.js';
import { config } from '../config.js';
import { OutputCollector, type CollectedEvent, type CollectedQuestion } from './OutputCollector.js';
import { AutoResponder, type ResponseStrategy, type ResponseLogEntry } from './AutoResponder.js';
import { PlayerFactory, type LifeStage, type PlayerFactoryOptions } from './PlayerFactory.js';
import { RomanceSimulator, type RomanceSimulatorOptions, type RomanceLogEntry } from './RomanceSimulator.js';
import { allEvents } from '../events/index.js';
import type { EventResult } from '../events/base.js';

export interface HeadlessGameOptions {
  autoRespond?: boolean;
  responseStrategy?: ResponseStrategy;
  logResponses?: boolean;
  maxTicksPerAdvance?: number;
  player?: Player;
  playerOptions?: PlayerFactoryOptions;
  startAge?: number;
  startStage?: LifeStage;
  /** Enable romance simulation (default: true) */
  simulateRomance?: boolean;
  /** Romance simulator options */
  romanceOptions?: RomanceSimulatorOptions;
  /** Enable happiness recovery (default: true) - slowly restores happiness toward baseline */
  enableHappinessRecovery?: boolean;
  /** Target happiness baseline for recovery (default: 50) */
  happinessBaseline?: number;
}

export interface GameSummary {
  playerId: string;
  playerName: string;
  status: string;
  startAge: number;
  endAge: number;
  totalDays: number;
  totalTicks: number;
  eventsCount: number;
  questionsCount: number;
  questionsAnswered: number;
  hasPartner: boolean;
  relationshipStatus?: string;
  romanceEventsCount: number;
  causeOfDeath?: string;
}

/**
 * In-memory storage implementation (no database)
 */
class InMemoryStorage implements IGameStorage {
  private games: Map<string, Player> = new Map();

  async saveGame(player: Player): Promise<void> {
    this.games.set(player.userId, player);
  }

  async loadGame(playerId: string): Promise<Player | null> {
    return this.games.get(playerId) ?? null;
  }
}

/**
 * HeadlessGame runs a game simulation without WebSocket
 */
export class HeadlessGame {
  readonly player: Player;
  readonly output: OutputCollector;
  private engine: GameEngine;
  private autoResponder: AutoResponder | null = null;
  private romanceSimulator: RomanceSimulator | null = null;
  private autoRespond: boolean;
  private simulateRomance: boolean;
  private enableHappinessRecovery: boolean;
  private happinessBaseline: number;
  private maxTicksPerAdvance: number;
  private startAge: number;
  private lastRomanceCheckAge: number = 0;
  private lastHappinessRecoveryDay: number = 0;

  constructor(options: HeadlessGameOptions = {}) {
    // Create player
    if (options.player) {
      this.player = options.player;
    } else if (options.startStage) {
      this.player = PlayerFactory.createAtStage(options.startStage, options.playerOptions);
    } else if (options.startAge !== undefined) {
      this.player = PlayerFactory.createAtAge(options.startAge, options.playerOptions);
    } else {
      this.player = PlayerFactory.createNewborn(options.playerOptions);
    }

    this.startAge = this.player.c.ageYears;

    // Create output collector
    this.output = new OutputCollector();

    // Create game engine with in-memory storage and output collector
    const storage = new InMemoryStorage();
    this.engine = new GameEngine(storage, this.output);

    // Setup auto-responder if enabled
    this.autoRespond = options.autoRespond ?? false;
    if (this.autoRespond) {
      this.autoResponder = new AutoResponder({
        strategy: options.responseStrategy ?? 'random',
        logResponses: options.logResponses ?? false,
      });
    }

    // Setup romance simulator (enabled by default)
    this.simulateRomance = options.simulateRomance ?? true;
    if (this.simulateRomance) {
      this.romanceSimulator = new RomanceSimulator(options.romanceOptions ?? {});
    }

    // Happiness recovery settings (enabled by default)
    this.enableHappinessRecovery = options.enableHappinessRecovery ?? true;
    this.happinessBaseline = options.happinessBaseline ?? 50;

    // Default to 50 million ticks (enough for ~95 years of game time)
    // 1 year = 365 * 24 * 60 = 525,600 ticks
    this.maxTicksPerAdvance = options.maxTicksPerAdvance ?? 50_000_000;

    // Set player to active state
    this.player.status = 'playing';
    this.player.controller = 'active';

    // Use a fast game speed for testing (1 tick = 1 game minute)
    this.player.gameSpeed = 1;
    this.player.previousGameSpeed = 1;
  }

  /**
   * Run N game ticks
   */
  async tick(count: number = 1): Promise<void> {
    for (let i = 0; i < count; i++) {
      if (this.player.c.status === 'dead') {
        break;
      }

      await this.engine.runGameTick(this.player, true);

      // Parse events every hour (when minuteOfHour resets to 0)
      if (this.player.minuteOfHour === 0) {
        await this.parseEvents();
      }

      // Romance simulation - check weekly (approximately every 10080 ticks = 7 days)
      // Simplified: check once per year of age
      if (this.romanceSimulator && this.player.c.ageYears > this.lastRomanceCheckAge) {
        this.lastRomanceCheckAge = this.player.c.ageYears;
        // Run romance tick multiple times per year to increase chances
        for (let i = 0; i < 12; i++) {
          this.romanceSimulator.tick(this.player);
        }
      }

      // Happiness recovery - weekly gradual return toward baseline
      // This models psychological adaptation over time
      if (this.enableHappinessRecovery) {
        const currentDay = this.player.c.ageDays;
        if (currentDay >= this.lastHappinessRecoveryDay + 7) {
          this.lastHappinessRecoveryDay = currentDay;
          this.applyHappinessRecovery();
        }
      }

      // Check for pending questions and auto-respond
      if (this.autoRespond) {
        await this.processPendingQuestions();
      }
    }
  }

  /**
   * Parse all registered events and trigger any that match conditions
   */
  private async parseEvents(): Promise<void> {
    for (const [eventId, eventFn] of Object.entries(allEvents)) {
      if (typeof eventFn !== 'function') continue;

      try {
        // Call the event function - it will check its own conditions
        const result = (eventFn as (player: Player, type?: string) => EventResult)(
          this.player,
          'message'
        );

        // If event returned a result, send it to output
        if (result && result.type) {
          const gameEvent: GameEvent = {
            ...result,
            type: result.type as 'messageEvent' | 'questionEvent',
            id: (result as { id?: string }).id ?? eventId,
            message: (result as { message?: string }).message ?? '',
          };
          await this.output.sendEventMessage(gameEvent);
        }
      } catch (error) {
        // Log but continue - some events may not be triggerable in headless mode
        console.debug(`Event ${eventId} skipped:`, error instanceof Error ? error.message : error);
      }
    }
  }

  /**
   * Process any pending questions with auto-responder
   */
  private async processPendingQuestions(): Promise<void> {
    if (!this.autoResponder) return;

    const pending = this.output.getPendingQuestions();
    for (const question of pending) {
      this.autoResponder.processResponse(this.player, question);
      this.output.markQuestionAnswered(question.id);
    }
  }

  /**
   * Apply weekly happiness recovery toward baseline
   * Models psychological adaptation - people naturally adapt to circumstances
   * Recovery rate: ~2-5 points per week toward baseline
   */
  private applyHappinessRecovery(): void {
    const currentHappiness = this.player.c.happiness ?? 50;
    const diff = this.happinessBaseline - currentHappiness;

    // Recovery rate based on how far from baseline
    // Faster recovery when very unhappy, slower when near baseline
    let recovery: number;
    if (Math.abs(diff) <= 5) {
      // Near baseline - minimal adjustment
      recovery = Math.sign(diff) * 1;
    } else if (Math.abs(diff) <= 20) {
      // Moderate distance - gradual recovery
      recovery = Math.sign(diff) * 2;
    } else {
      // Very far from baseline - faster recovery
      recovery = Math.sign(diff) * Math.min(5, Math.abs(diff) * 0.15);
    }

    // Apply recovery
    this.player.c.happiness = Math.max(0, Math.min(100, currentHappiness + recovery));
  }

  /**
   * Advance game time by N minutes
   */
  async advanceMinutes(minutes: number): Promise<void> {
    await this.tick(minutes);
  }

  /**
   * Advance game time by N hours
   */
  async advanceHours(hours: number): Promise<void> {
    await this.tick(hours * 60);
  }

  /**
   * Advance game time by N days
   */
  async advanceDays(days: number): Promise<void> {
    await this.tick(days * 24 * 60);
  }

  /**
   * Advance game time by N years
   */
  async advanceYears(years: number): Promise<void> {
    await this.tick(years * 365 * 24 * 60);
  }

  /**
   * Advance until player reaches target age
   */
  async advanceToAge(targetAge: number): Promise<void> {
    let ticks = 0;
    const maxTicks = this.maxTicksPerAdvance;

    while (this.player.c.ageYears < targetAge && this.player.c.status !== 'dead') {
      await this.tick(60); // Advance 1 hour at a time for efficiency
      ticks += 60;

      if (ticks >= maxTicks) {
        console.warn(`Max ticks (${maxTicks}) reached before target age ${targetAge}`);
        break;
      }
    }
  }

  /**
   * Advance until player dies or reaches maximum age
   */
  async advanceToEnd(maxAge: number = 150): Promise<void> {
    let ticks = 0;
    const maxTicks = this.maxTicksPerAdvance;

    while (this.player.c.status !== 'dead' && this.player.c.ageYears < maxAge) {
      await this.tick(60); // Advance 1 hour at a time
      ticks += 60;

      if (ticks >= maxTicks) {
        console.warn(`Max ticks (${maxTicks}) reached before death`);
        break;
      }
    }
  }

  /**
   * Get all collected events
   */
  getEvents(): CollectedEvent[] {
    return this.output.getEvents();
  }

  /**
   * Get all collected questions
   */
  getQuestions(): CollectedQuestion[] {
    return this.output.getQuestions();
  }

  /**
   * Get response log from auto-responder
   */
  getResponseLog(): ResponseLogEntry[] {
    return this.autoResponder?.getResponseLog() ?? [];
  }

  /**
   * Get romance event log
   */
  getRomanceLog(): RomanceLogEntry[] {
    return this.romanceSimulator?.getEventLog() ?? [];
  }

  /**
   * Get game statistics
   */
  getStats(): Record<string, number> {
    const outputStats = this.output.getStats();
    return {
      ...outputStats,
      startAge: this.startAge,
      endAge: this.player.c.ageYears,
      totalDays: this.player.c.ageDays,
      totalTicks: this.player.ticks,
      questionsAnswered: this.getResponseLog().length,
    };
  }

  /**
   * Get game summary
   */
  getSummary(): GameSummary {
    // Get relationship status if any
    let relationshipStatus: string | undefined;
    const hasPartner = !!this.player.c.partner;
    if (this.player.relData.length > 0) {
      const rel = this.player.relData[0] as unknown as { relationshipStatus?: string };
      relationshipStatus = rel.relationshipStatus;
    }

    return {
      playerId: this.player.userId,
      playerName: this.player.c.fullName,
      status: this.player.c.status,
      startAge: this.startAge,
      endAge: this.player.c.ageYears,
      totalDays: this.player.c.ageDays,
      totalTicks: this.player.ticks,
      eventsCount: this.output.getEvents().length,
      questionsCount: this.output.getQuestions().length,
      questionsAnswered: this.getResponseLog().length,
      hasPartner,
      relationshipStatus,
      romanceEventsCount: this.getRomanceLog().length,
      causeOfDeath: this.player.c.status === 'dead' ? 'natural causes' : undefined,
    };
  }

  /**
   * Print summary to console
   */
  printSummary(): void {
    const summary = this.getSummary();
    console.log('\n==============================================');
    console.log('HeadlessGame Summary');
    console.log('==============================================');
    console.log(`Player: ${summary.playerName} (${summary.playerId})`);
    console.log(`Status: ${summary.status}`);
    console.log(`Age: ${summary.startAge} -> ${summary.endAge} years`);
    console.log(`Total Days: ${summary.totalDays}`);
    console.log(`Total Ticks: ${summary.totalTicks}`);
    console.log(`Events: ${summary.eventsCount}`);
    console.log(`Questions Answered: ${summary.questionsAnswered}`);
    console.log(`Has Partner: ${summary.hasPartner}`);
    if (summary.relationshipStatus) {
      console.log(`Relationship Status: ${summary.relationshipStatus}`);
    }
    console.log(`Romance Events: ${summary.romanceEventsCount}`);
    if (summary.causeOfDeath) {
      console.log(`Cause of Death: ${summary.causeOfDeath}`);
    }
    console.log('==============================================\n');
  }

  /**
   * Check if player has an event with specific ID
   * Checks both output collector and player's internal event/question tracking
   */
  hasEvent(eventId: string): boolean {
    // Check output collector
    if (this.output.hasEventWithId(eventId)) return true;
    // Check player's internal tracking (some events use events Set, some use askedQuestions)
    if (this.player.events.has(eventId)) return true;
    if (this.player.askedQuestions.has(eventId)) return true;
    return false;
  }

  /**
   * Check if player has an event containing specific text
   */
  hasEventWithText(text: string): boolean {
    return this.output.hasEventWithText(text);
  }

  /**
   * Clear all collected output
   */
  clearOutput(): void {
    this.output.clear();
    this.autoResponder?.clearLog();
    this.romanceSimulator?.clearLog();
  }
}
