/**
 * Time Skip System
 *
 * Handles time skip purchases and simulation of game events during skipped periods.
 */

import { deductDiamonds, canAfford } from './diamondEconomy.js';
import type { Player } from '../models/Player.js';

const clamp = (v: number, lo: number, hi: number): number => Math.max(lo, Math.min(hi, v));

/**
 * Advance the player's in-game clock by `hours`, rolling age and the calendar
 * exactly the way the live PlayerSession / offline GameEngine loops do, so a
 * time-skip leaves the player in a consistent time state the loops can resume
 * from. Hour-by-hour (bounded; the largest skip is one week = 168h).
 */
export function advancePlayerClock(player: Player, hours: number): void {
  const c = player.c;
  for (let i = 0; i < hours; i++) {
    c.ageHours = (c.ageHours ?? 0) + 1;
    if ((c.ageHours ?? 0) % 24 === 0) {
      c.ageDays = (c.ageDays ?? 0) + 1;
      if ((c.ageDays ?? 0) % 365 === 0) {
        c.ageYears = (c.ageYears ?? 0) + 1;
      }
    }

    player.hourOfDay = (player.hourOfDay ?? 0) + 1;
    if ((player.hourOfDay ?? 0) >= 24) {
      player.hourOfDay = 0;
      player.dayOfYear = (player.dayOfYear ?? 1) >= 365 ? 1 : (player.dayOfYear ?? 0) + 1;
      player.dayOfWeek = (player.dayOfWeek ?? 1) >= 7 ? 1 : (player.dayOfWeek ?? 0) + 1;

      const baseDate = new Date(2022, 0, 1);
      baseDate.setDate(baseDate.getDate() + (player.dayOfYear ?? 1) - 1);
      const month = baseDate.getMonth() + 1;
      const day = baseDate.getDate();
      player.date = `${month.toString().padStart(2, '0')}-${day.toString().padStart(2, '0')}`;
      player.monthOfYear = month;
    }
  }
  player.time = `${player.hourOfDay}:00`;
}

/** Build the time-skip simulation input from authoritative server state. */
function buildPlayerState(player: Player): PlayerState {
  // Construct a start Date whose hour + weekday match the player's in-game clock
  // (the simulation only reads getHours()/getDay()). 2022-01-03 was a Monday, and
  // the loops use dayOfWeek 1=Monday..7=Sunday.
  const dow = player.dayOfWeek ?? 1;
  const start = new Date(2022, 0, 3 + (dow - 1), player.hourOfDay ?? 0);
  return {
    occupation: player.c.occupation,
    salary: player.c.salary,
    educationLevel: player.c.education,
    age: player.c.ageYears,
    dead: player.c.status === 'dead',
    currentTime: start,
    energy: player.c.energy ?? 0,
    health: player.c.health ?? 100,
    happiness: player.c.happiness ?? 50,
    money: player.c.money ?? 0,
  };
}

export interface SkipTier {
  durationHours: number | null; // null for variable duration
  diamonds: number;
}

export interface SimulatedEvent {
  type: string;
  description: string;
  moneyEarned?: number;
  smartsGained?: number;
  statChanges?: Record<string, number>;
}

export interface SkipSummary {
  diamonds: number;
  newTime: string;
  durationHours: number;
  events: SimulatedEvent[];
  statChanges: {
    money: number;
    energy: number;
    health: number;
    happiness: number;
  };
}

export interface SkipResult {
  success: boolean;
  message: string;
  summary?: SkipSummary;
}

export interface PlayerState {
  occupation?: string;
  salary?: number;
  educationLevel?: string;
  age?: number;
  dead?: boolean;
  currentTime: Date;
  energy: number;
  health: number;
  happiness: number;
  money: number;
}

// Time skip tier configurations
export const SKIP_TIERS: Record<string, SkipTier> = {
  '1hour': { durationHours: 1, diamonds: 5 },
  '1day': { durationHours: 24, diamonds: 25 },
  '1week': { durationHours: 168, diamonds: 100 },
  next_event: { durationHours: null, diamonds: 50 }, // Variable
};

// Rate limiting
const skipTimestamps: Map<string, number[]> = new Map();
const RATE_LIMIT_CALLS = 5;
const RATE_LIMIT_WINDOW_MS = 60000;

/**
 * Check rate limit for a player
 */
function checkRateLimit(playerId: string): boolean {
  const now = Date.now();
  const timestamps = skipTimestamps.get(playerId) ?? [];

  const recentTimestamps = timestamps.filter(t => now - t < RATE_LIMIT_WINDOW_MS);

  if (recentTimestamps.length >= RATE_LIMIT_CALLS) {
    return false;
  }

  recentTimestamps.push(now);
  skipTimestamps.set(playerId, recentTimestamps);

  return true;
}

/**
 * Simulate events during time skip period
 */
function simulateTimePeriod(
  playerState: PlayerState,
  startTime: Date,
  endTime: Date
): {
  events: SimulatedEvent[];
  moneyEarned: number;
  energyChange: number;
  healthChange: number;
  happinessChange: number;
} {
  const events: SimulatedEvent[] = [];
  let moneyEarned = 0;
  let energyChange = 0;
  let healthChange = 0;
  let happinessChange = 0;

  // Calculate hours elapsed
  const hoursElapsed = Math.floor((endTime.getTime() - startTime.getTime()) / 3600000);
  const maxHours = Math.min(hoursElapsed, 168); // Cap at 1 week

  let workHours = 0;
  let schoolHours = 0;
  let sleepHours = 0;

  // Simulate hour by hour
  let currentTime = new Date(startTime);
  for (let i = 0; i < maxHours; i++) {
    const hourOfDay = currentTime.getHours();
    const dayOfWeek = currentTime.getDay(); // 0 = Sunday
    const isWeekday = dayOfWeek >= 1 && dayOfWeek <= 5;

    // Work hours (9am-5pm on weekdays)
    if (playerState.occupation && hourOfDay >= 9 && hourOfDay < 17 && isWeekday) {
      if (playerState.salary) {
        const hourlyWage = playerState.salary / (40 * 4); // Monthly to hourly
        moneyEarned += hourlyWage;
        workHours++;
      }
    }

    // School hours
    if (playerState.educationLevel && hourOfDay >= 8 && hourOfDay < 15 && isWeekday) {
      schoolHours++;
    }

    // Sleep hours (recover energy)
    if (hourOfDay >= 22 || hourOfDay < 6) {
      energyChange += 2;
      sleepHours++;
    }

    // Natural stat changes
    energyChange -= 0.5;
    healthChange -= 0.1;
    happinessChange -= 0.2;

    currentTime = new Date(currentTime.getTime() + 3600000);
  }

  // Create event summaries
  if (workHours > 0) {
    events.push({
      type: 'work',
      description: `Worked ${workHours} hours`,
      moneyEarned: Math.floor(moneyEarned),
    });
  }

  if (schoolHours > 0) {
    events.push({
      type: 'school',
      description: `Attended ${schoolHours} hours of classes`,
      smartsGained: Math.floor(schoolHours / 2),
    });
  }

  if (sleepHours > 0) {
    events.push({
      type: 'rest',
      description: `Slept ${sleepHours} hours`,
    });
  }

  // Cap stat changes
  energyChange = Math.max(-50, Math.min(50, energyChange));
  healthChange = Math.max(-20, Math.min(10, healthChange));
  happinessChange = Math.max(-30, Math.min(20, happinessChange));

  events.push({
    type: 'summary',
    description: `Time advanced ${maxHours} hours`,
    statChanges: {
      energy: Math.floor(energyChange),
      health: Math.floor(healthChange),
      happiness: Math.floor(happinessChange),
    },
  });

  return {
    events,
    moneyEarned: Math.floor(moneyEarned),
    energyChange: Math.floor(energyChange),
    healthChange: Math.floor(healthChange),
    happinessChange: Math.floor(happinessChange),
  };
}

/**
 * Handle time skip purchase and simulation
 */
export function purchaseTimeSkip(
  player: Player,
  skipType: string
): SkipResult {
  const playerId = player.userId;
  // Check rate limit
  if (!checkRateLimit(playerId)) {
    return {
      success: false,
      message: 'Rate limited. Please wait before making another purchase.',
    };
  }

  // Validate skip type
  if (!(skipType in SKIP_TIERS)) {
    return {
      success: false,
      message: `Invalid skip type: ${skipType}`,
    };
  }

  // Check if player is dead
  if (player.c.status === 'dead') {
    return {
      success: false,
      message: 'Cannot skip time after death',
    };
  }

  const tier = SKIP_TIERS[skipType];
  const diamondCost = tier.diamonds;

  // Check if player can afford (reads player.c.diamonds)
  if (!canAfford(player as never, diamondCost)) {
    return {
      success: false,
      message: `Not enough diamonds. Need ${diamondCost}.`,
    };
  }

  // Build the simulation input from authoritative server state (the client no
  // longer needs to — and never did — send a trustworthy playerState).
  const playerState = buildPlayerState(player);
  const startTime = playerState.currentTime;
  // next_event is a simplified 1-day skip.
  const durationHours = skipType === 'next_event' ? 24 : (tier.durationHours ?? 1);
  const endTime = new Date(startTime.getTime() + durationHours * 3600000);

  // Simulate events during skipped period
  const simulation = simulateTimePeriod(playerState, startTime, endTime);

  // Deduct diamonds from the real player.
  const deductResult = deductDiamonds(player as never, `time_skip_${skipType}`, diamondCost);

  if (!deductResult.success) {
    return {
      success: false,
      message: deductResult.message,
    };
  }

  // APPLY the simulated outcome to authoritative state and ADVANCE the clock so
  // the skip is real (previously the summary was cosmetic and nothing moved).
  player.c.money = (player.c.money ?? 0) + simulation.moneyEarned;
  player.c.energy = clamp((player.c.energy ?? 0) + simulation.energyChange, 0, 100);
  player.c.calcEnergy = Math.max(0, (player.c.energy ?? 0) - (player.c.peakEnergy ?? 0));
  player.c.health = clamp((player.c.health ?? 100) + simulation.healthChange, 0, 100);
  player.c.happiness = clamp((player.c.happiness ?? 50) + simulation.happinessChange, 0, 100);
  advancePlayerClock(player, durationHours);

  console.log(`Player ${playerId} skipped ${skipType} for ${diamondCost} diamonds`);

  return {
    success: true,
    message: 'Time skipped successfully',
    summary: {
      diamonds: deductResult.newBalance ?? 0,
      newTime: endTime.toISOString(),
      durationHours,
      events: simulation.events.slice(0, 10), // First 10 events
      statChanges: {
        money: simulation.moneyEarned,
        energy: simulation.energyChange,
        health: simulation.healthChange,
        happiness: simulation.happinessChange,
      },
    },
  };
}

/**
 * WebSocket message handler for purchaseTimeSkip
 */
export function handlePurchaseTimeSkip(
  player: Player,
  messageData: { skipType?: string },
  sendToClient: (playerId: string, message: Record<string, unknown>) => void
): void {
  const playerId = player.userId;
  const { skipType } = messageData;

  if (!skipType) {
    sendToClient(playerId, {
      type: 'error',
      errorCode: 'INVALID_REQUEST',
      message: 'Missing skipType',
    });
    return;
  }

  // playerState is built server-side from authoritative state (the client used
  // to be required to send it — and never did — so this path always failed).
  const result = purchaseTimeSkip(player, skipType);

  if (result.success) {
    sendToClient(playerId, {
      type: 'timeSkipComplete',
      success: true,
      summary: result.summary,
    });

    sendToClient(playerId, {
      type: 'requestFullUpdate',
    });
  } else {
    sendToClient(playerId, {
      type: 'error',
      errorCode: 'TIME_SKIP_FAILED',
      message: result.message,
    });
  }
}

/**
 * Get all available time skip tiers
 */
export function getSkipTiers(): Record<string, { durationSeconds: number | null; diamonds: number }> {
  const result: Record<string, { durationSeconds: number | null; diamonds: number }> = {};

  for (const [key, value] of Object.entries(SKIP_TIERS)) {
    result[key] = {
      durationSeconds: value.durationHours ? value.durationHours * 3600 : null,
      diamonds: value.diamonds,
    };
  }

  return result;
}

/**
 * Clear all time skip data (for testing)
 */
export function clearAllTimeSkipData(): void {
  skipTimestamps.clear();
}
