/**
 * Retention System Integration Helpers
 *
 * Provides convenience functions for integrating achievement tracking
 * and quest progress updates throughout the game.
 */

import type { PlayerSession } from '../../game/PlayerSession.js';
import type { Player } from '../../models/index.js';
import {
  checkAchievementsAsync,
  getPlayerStatistics,
  trackJobObtained,
  trackFired,
  trackMarriage,
  trackDating,
  trackChildBorn,
  trackFriendMade,
  trackMoneyEarned,
  trackMoneySpent,
  trackConversation,
  trackActivityCompleted,
  trackAffinity,
  initializePlayerStatistics,
  type UnlockedAchievement,
  type PlayerStats,
} from './index.js';
import { updateQuestProgress, type ActiveQuest } from './dailyQuests.js';
import {
  buildGoalContext,
  evaluateGoals,
  formatActiveGoals,
  formatCompletedGoals,
  getLifeScore,
  type CompletedGoalResult,
} from './lifeGoals.js';
import { queueRealtimeNotification } from '../notifications/notificationManager.js';

/**
 * Send achievement unlock notification to client
 */
export function sendAchievementUnlocked(
  session: PlayerSession,
  achievement: UnlockedAchievement
): void {
  session.send({
    type: 'achievementUnlocked',
    achievement: {
      id: achievement.id,
      key: achievement.key,
      name: achievement.name,
      description: achievement.description,
      icon: achievement.icon,
      reward: achievement.reward,
    },
  });

  // Push notification if app is backgrounded
  queueRealtimeNotification(session.player, {
    title: 'Achievement Unlocked!',
    body: `${achievement.name}${achievement.reward ? ` — ${achievement.reward}` : ''}`,
    type: 'milestone',
    id: achievement.id,
  }).catch(err => console.error('[Push] achievement error:', err));
}

/**
 * Send multiple achievement unlocks to client
 */
export function sendAchievementsUnlocked(
  session: PlayerSession,
  achievements: UnlockedAchievement[]
): void {
  for (const achievement of achievements) {
    sendAchievementUnlocked(session, achievement);
  }
}

/**
 * Send quest progress notification to client
 */
export function sendQuestProgress(
  session: PlayerSession,
  quest: ActiveQuest
): void {
  session.send({
    type: 'questProgress',
    quest: {
      id: quest.id,
      questType: quest.questType,
      description: quest.description,
      progress: quest.progress,
      progressRequired: quest.progressRequired,
      diamondReward: quest.diamondReward,
      difficulty: quest.difficulty,
      iconName: quest.iconName,
      completed: quest.completed,
    },
    justCompleted: quest.justCompleted ?? false,
  });
}

/**
 * Initialize player statistics on session start
 */
export function initializePlayerRetention(playerId: string): void {
  initializePlayerStatistics(playerId);
}

/**
 * Handler for job obtained event
 * Tracks statistics and checks achievements
 */
export async function onJobObtained(session: PlayerSession): Promise<void> {
  const playerId = session.player.userId;

  trackJobObtained(playerId);
  const stats = getPlayerStatistics(playerId);

  const unlocked = await checkAchievementsAsync(
    playerId,
    'get_job',
    {},
    {},
    stats,
    session.player
  );

  sendAchievementsUnlocked(session, unlocked);
  // Note: no separate career push here — the achievement push covers it,
  // and the job event itself goes through messageQueue which has its own push logic.

  // Advance life goals (e.g. "Start Your Career", "Get a Degree").
  updateLifeGoals(session);
}

/**
 * Handler for job promotion event
 */
export async function onPromotion(
  session: PlayerSession,
  newTitle: string
): Promise<void> {
  const playerId = session.player.userId;
  const stats = getPlayerStatistics(playerId);

  const unlocked = await checkAchievementsAsync(
    playerId,
    'promotion',
    { title: newTitle },
    {},
    stats,
    session.player
  );

  sendAchievementsUnlocked(session, unlocked);

  // Advance life goals (e.g. "Climb the Ladder").
  updateLifeGoals(session);

  queueRealtimeNotification(session.player, {
    title: 'Career Update',
    body: `You've been promoted to ${newTitle}!`,
    type: 'milestone',
  }).catch(err => console.error('[Push] promotion error:', err));
}

/**
 * Handler for getting fired event
 */
export async function onFired(session: PlayerSession): Promise<void> {
  const playerId = session.player.userId;

  trackFired(playerId);
  const stats = getPlayerStatistics(playerId);

  const unlocked = await checkAchievementsAsync(
    playerId,
    'fired',
    {},
    {},
    stats,
    session.player
  );

  sendAchievementsUnlocked(session, unlocked);

  queueRealtimeNotification(session.player, {
    title: 'Career Update',
    body: 'You have been fired from your job.',
    type: 'life_event',
  }).catch(err => console.error('[Push] fired error:', err));
}

/**
 * Handler for marriage event
 */
export async function onMarriage(session: PlayerSession): Promise<void> {
  const playerId = session.player.userId;

  trackMarriage(playerId);
  const stats = getPlayerStatistics(playerId);

  const unlocked = await checkAchievementsAsync(
    playerId,
    'marriage',
    {},
    {},
    stats,
    session.player
  );

  sendAchievementsUnlocked(session, unlocked);

  // Advance life goals (e.g. "Tie the Knot").
  updateLifeGoals(session);
}

/**
 * Handler for starting to date someone
 */
export async function onDating(session: PlayerSession): Promise<void> {
  const playerId = session.player.userId;

  trackDating(playerId);
  const stats = getPlayerStatistics(playerId);

  // Check for date_10_people achievement
  const unlocked = await checkAchievementsAsync(
    playerId,
    'dating',
    {},
    {},
    stats,
    session.player
  );

  sendAchievementsUnlocked(session, unlocked);
}

/**
 * Handler for affinity milestone
 */
export async function onAffinityMilestone(
  session: PlayerSession,
  newAffinity: number
): Promise<void> {
  const playerId = session.player.userId;

  trackAffinity(playerId, newAffinity);

  if (newAffinity >= 100) {
    const unlocked = await checkAchievementsAsync(
      playerId,
      'affinity_milestone',
      { affinity: newAffinity },
      {},
      {},
      session.player
    );

    sendAchievementsUnlocked(session, unlocked);
  }
}

/**
 * Handler for child born event
 */
export async function onChildBorn(session: PlayerSession): Promise<void> {
  const playerId = session.player.userId;

  trackChildBorn(playerId);
  const stats = getPlayerStatistics(playerId);

  const unlocked = await checkAchievementsAsync(
    playerId,
    'birth_child',
    {},
    {},
    stats,
    session.player
  );

  sendAchievementsUnlocked(session, unlocked);

  // Advance life goals (e.g. "Raise a Family").
  updateLifeGoals(session);
}

/**
 * Handler for making a friend
 */
export async function onFriendMade(session: PlayerSession): Promise<void> {
  const playerId = session.player.userId;

  trackFriendMade(playerId);
  const stats = getPlayerStatistics(playerId);

  const unlocked = await checkAchievementsAsync(
    playerId,
    'make_friend',
    {},
    {},
    stats,
    session.player
  );

  sendAchievementsUnlocked(session, unlocked);

  // Advance life goals (e.g. "Build Your Circle").
  updateLifeGoals(session);
}

/**
 * Handler for birthday milestone
 */
export async function onBirthday(
  session: PlayerSession,
  age: number
): Promise<void> {
  const playerId = session.player.userId;
  const stats = getPlayerStatistics(playerId);

  const unlocked = await checkAchievementsAsync(
    playerId,
    'birthday',
    { age },
    { age },
    stats,
    session.player
  );

  sendAchievementsUnlocked(session, unlocked);

  // Birthdays change life stage — refresh the goal slate (unlock new goals,
  // advance age-based goals like "Live a Long Life").
  updateLifeGoals(session);
}

/**
 * Handler for purchase event
 */
export async function onPurchase(
  session: PlayerSession,
  itemCount: number
): Promise<void> {
  const playerId = session.player.userId;

  const unlocked = await checkAchievementsAsync(
    playerId,
    'purchase_item',
    {},
    { itemCount },
    {},
    session.player
  );

  sendAchievementsUnlocked(session, unlocked);

  // Update quest progress
  const quest = await updateQuestProgress(playerId, 'buy_item', 1, session.player);
  if (quest) {
    sendQuestProgress(session, quest);
  }
}

/**
 * Handler for conversation completion
 */
export async function onConversationComplete(
  session: PlayerSession
): Promise<void> {
  const playerId = session.player.userId;

  trackConversation(playerId);

  // Update quest progress
  const quest = await updateQuestProgress(playerId, 'talk_to_characters', 1, session.player);
  if (quest) {
    sendQuestProgress(session, quest);
  }
}

/**
 * Handler for date night completion
 */
export async function onDateNight(session: PlayerSession): Promise<void> {
  const playerId = session.player.userId;

  // Update quest progress
  const quest = await updateQuestProgress(playerId, 'go_on_date', 1, session.player);
  if (quest) {
    sendQuestProgress(session, quest);
  }
}

/**
 * Handler for money earned
 */
export async function onMoneyEarned(
  session: PlayerSession,
  amount: number
): Promise<void> {
  const playerId = session.player.userId;

  trackMoneyEarned(playerId, amount);

  // Update quest progress
  const quest = await updateQuestProgress(playerId, 'earn_money', amount, session.player);
  if (quest) {
    sendQuestProgress(session, quest);
  }
}

/**
 * Handler for money spent
 */
export function onMoneySpent(playerId: string, amount: number): void {
  trackMoneySpent(playerId, amount);
}

/**
 * Handler for activity completed
 */
export async function onActivityCompleted(
  session: PlayerSession,
  energySpent: number = 0
): Promise<void> {
  const playerId = session.player.userId;

  trackActivityCompleted(playerId);

  // Update quest progress for activities
  const activityQuest = await updateQuestProgress(
    playerId,
    'complete_activities',
    1,
    session.player
  );
  if (activityQuest) {
    sendQuestProgress(session, activityQuest);
  }

  // Update quest progress for energy spent
  if (energySpent > 0) {
    const energyQuest = await updateQuestProgress(
      playerId,
      'spend_energy',
      energySpent,
      session.player
    );
    if (energyQuest) {
      sendQuestProgress(session, energyQuest);
    }
  }
}

/**
 * Handler for work hours completed
 */
export async function onWorkHours(
  session: PlayerSession,
  hoursWorked: number,
  amountEarned: number
): Promise<void> {
  const playerId = session.player.userId;

  // Update quest progress for work hours
  const workQuest = await updateQuestProgress(playerId, 'work_hours', hoursWorked, session.player);
  if (workQuest) {
    sendQuestProgress(session, workQuest);
  }

  // Update quest progress for money earned
  if (amountEarned > 0) {
    const moneyQuest = await updateQuestProgress(playerId, 'earn_money', amountEarned, session.player);
    if (moneyQuest) {
      sendQuestProgress(session, moneyQuest);
    }
    trackMoneyEarned(playerId, amountEarned);
  }
}

/**
 * Handler for class attendance
 */
export async function onClassAttended(
  session: PlayerSession,
  hoursStudied: number = 1
): Promise<void> {
  const playerId = session.player.userId;

  // Update quest progress for attending class
  const classQuest = await updateQuestProgress(playerId, 'attend_class', 1, session.player);
  if (classQuest) {
    sendQuestProgress(session, classQuest);
  }

  // Update quest progress for studying
  if (hoursStudied > 0) {
    const studyQuest = await updateQuestProgress(playerId, 'study', hoursStudied, session.player);
    if (studyQuest) {
      sendQuestProgress(session, studyQuest);
    }
  }
}

/**
 * Handler for affinity increase
 */
export async function onAffinityIncrease(
  session: PlayerSession,
  amount: number
): Promise<void> {
  const playerId = session.player.userId;

  if (amount > 0) {
    const quest = await updateQuestProgress(playerId, 'increase_affinity', amount, session.player);
    if (quest) {
      sendQuestProgress(session, quest);
    }
  }
}

/**
 * Handler for graduation event
 */
export async function onGraduation(
  session: PlayerSession,
  level: string,
  gpa?: number
): Promise<void> {
  const playerId = session.player.userId;
  const stats = getPlayerStatistics(playerId);

  const unlocked = await checkAchievementsAsync(
    playerId,
    'graduate',
    { level, gpa },
    {},
    stats,
    session.player
  );

  sendAchievementsUnlocked(session, unlocked);

  // Advance life goals (e.g. "Earn Your Diploma", "Get a Degree").
  updateLifeGoals(session);
}

/**
 * Handler for starting school
 */
export async function onStartSchool(session: PlayerSession): Promise<void> {
  const playerId = session.player.userId;

  const unlocked = await checkAchievementsAsync(
    playerId,
    'start_school',
    {},
    {},
    {},
    session.player
  );

  sendAchievementsUnlocked(session, unlocked);
}

// =====================================================
// LIFE GOALS (forward-looking aspirations)
// =====================================================

/**
 * Send the current life-goals state to the client. Includes the active goal
 * slate (with live progressPercent), the completed list, and the running life
 * score. Server->client only; no client-command manifest entry required.
 */
export function sendLifeGoalsUpdate(
  session: PlayerSession,
  justCompleted: CompletedGoalResult[] = []
): void {
  const playerId = session.player.userId;
  session.send({
    type: 'lifeGoalsUpdate',
    active: formatActiveGoals(playerId),
    completed: formatCompletedGoals(playerId),
    lifeScore: getLifeScore(playerId),
    justCompleted: justCompleted.map((g) => ({
      id: g.id,
      title: g.title,
      description: g.description,
      icon: g.icon,
      reward: g.reward,
      lifeScore: g.lifeScore,
    })),
  });

  // Push notification when a goal completes (rewarding the long arc of play).
  for (const goal of justCompleted) {
    queueRealtimeNotification(session.player, {
      title: 'Life Goal Achieved!',
      body: `${goal.title}${goal.reward ? ` — ${goal.reward}` : ''}`,
      type: 'milestone',
      id: `lifegoal_${goal.id}`,
    }).catch((err) => console.error('[Push] life goal error:', err));
  }
}

/**
 * Re-evaluate the player's life goals against current state. Refreshes the
 * active slate (unlocking newly-eligible goals), advances progress, completes
 * any goals that hit their target (granting diamonds + life score), and emits a
 * `lifeGoalsUpdate` to the client whenever anything changed.
 *
 * Safe to call from any hook or from periodic stat checks.
 */
export function updateLifeGoals(session: PlayerSession): CompletedGoalResult[] {
  const playerId = session.player.userId;
  const ctx = buildGoalContext(playerId, session.player);
  const { completed, changed } = evaluateGoals(playerId, ctx, session.player);

  if (changed || completed.length > 0) {
    sendLifeGoalsUpdate(session, completed);
  }

  return completed;
}

// =====================================================
// OFFLINE LIFECYCLE DRAIN (session-free)
// =====================================================

/**
 * Grant achievement + life-goal credit for the lifecycle events a player
 * accumulated while DISCONNECTED.
 *
 * Background: milestone events (graduation, marriage, child born, etc.) that
 * fire during the offline simulation job (`LoopManager.iterateGames` →
 * `initLifeSim(null, ...)`) push entries onto `player.lifecycleQueue` and
 * self-mark `player.events`. Online, `PlayerSession.drainLifecycleQueue()`
 * routes those entries through the `on*` handlers (achievements + life goals).
 * Offline there is NO session, the queue is transient (never persisted), and
 * the self-marked `player.events` blocks re-detection on reconnect — so the
 * credit was permanently lost. This drains the queue server-side, granting the
 * SAME credit via the SAME session-free, idempotent calls the online handlers
 * use under the hood, but WITHOUT any client push (there is no connection).
 *
 * Idempotency: `checkAchievementsAsync` no-ops for already-unlocked
 * achievements (in-memory cache guard + `ON DUPLICATE KEY`), and
 * `evaluateGoals` skips already-completed goals — so a milestone granted here
 * is NOT re-granted when the player later comes online (or if this drain runs
 * again on an empty/unchanged queue).
 *
 * Bounded: callers should only invoke this when `lifecycleQueue` is non-empty.
 * This function additionally short-circuits on an empty queue. It only grants
 * credit for milestones that ALREADY occurred — it never fires interactive
 * events (questions/conversations/dilemmas).
 *
 * @returns the number of lifecycle entries processed.
 */
export async function drainLifecycleQueueOffline(player: Player): Promise<number> {
  const queue = player.lifecycleQueue;
  if (!queue || queue.length === 0) return 0;

  const playerId = player.userId;
  let processed = 0;

  // Re-evaluate life goals once after granting achievements/stats for the
  // batch. Mirrors the online handlers' updateLifeGoals(), minus the client
  // push. evaluateGoals is idempotent (completed goals are skipped).
  let shouldEvaluateGoals = false;

  while (queue.length > 0) {
    const event = queue.shift()!;
    processed++;
    try {
      switch (event.type) {
        case 'job_obtained': {
          trackJobObtained(playerId);
          const stats = getPlayerStatistics(playerId);
          await checkAchievementsAsync(playerId, 'get_job', {}, {}, stats, player);
          shouldEvaluateGoals = true;
          break;
        }
        case 'promotion': {
          const stats = getPlayerStatistics(playerId);
          await checkAchievementsAsync(
            playerId,
            'promotion',
            { title: (event.data?.title as string) ?? 'Senior Position' },
            {},
            stats,
            player
          );
          shouldEvaluateGoals = true;
          break;
        }
        case 'fired': {
          trackFired(playerId);
          const stats = getPlayerStatistics(playerId);
          await checkAchievementsAsync(playerId, 'fired', {}, {}, stats, player);
          break;
        }
        case 'marriage': {
          trackMarriage(playerId);
          const stats = getPlayerStatistics(playerId);
          await checkAchievementsAsync(playerId, 'marriage', {}, {}, stats, player);
          shouldEvaluateGoals = true;
          break;
        }
        case 'dating': {
          trackDating(playerId);
          const stats = getPlayerStatistics(playerId);
          await checkAchievementsAsync(playerId, 'dating', {}, {}, stats, player);
          break;
        }
        case 'child_born': {
          trackChildBorn(playerId);
          const stats = getPlayerStatistics(playerId);
          await checkAchievementsAsync(playerId, 'birth_child', {}, {}, stats, player);
          shouldEvaluateGoals = true;
          break;
        }
        case 'friend_made': {
          trackFriendMade(playerId);
          const stats = getPlayerStatistics(playerId);
          await checkAchievementsAsync(playerId, 'make_friend', {}, {}, stats, player);
          shouldEvaluateGoals = true;
          break;
        }
        case 'birthday': {
          const age = (event.data?.age as number) ?? player.c?.ageYears ?? 0;
          const stats = getPlayerStatistics(playerId);
          await checkAchievementsAsync(playerId, 'birthday', { age }, { age }, stats, player);
          shouldEvaluateGoals = true;
          break;
        }
        case 'graduation': {
          const stats = getPlayerStatistics(playerId);
          await checkAchievementsAsync(
            playerId,
            'graduate',
            {
              level: (event.data?.level as string) ?? 'high school',
              gpa: event.data?.gpa as number | undefined,
            },
            {},
            stats,
            player
          );
          shouldEvaluateGoals = true;
          break;
        }
        default:
          if (process.env.NODE_ENV !== 'test') {
            console.warn(`[Retention] Unknown offline lifecycle event type: ${event.type}`);
          }
      }
    } catch (err) {
      console.error(`[Retention] Error processing offline lifecycle event ${event.type}:`, err);
    }
  }

  // Advance life goals once for the batch (no client push offline).
  if (shouldEvaluateGoals) {
    try {
      const ctx = buildGoalContext(playerId, player);
      evaluateGoals(playerId, ctx, player);
    } catch (err) {
      console.error('[Retention] Error evaluating offline life goals:', err);
    }
  }

  return processed;
}

/**
 * Handler for death event
 */
export async function onDeath(
  session: PlayerSession,
  age: number,
  money: number
): Promise<void> {
  const playerId = session.player.userId;
  const stats = getPlayerStatistics(playerId);

  const unlocked = await checkAchievementsAsync(
    playerId,
    'death',
    { age },
    { age, money },
    stats,
    session.player
  );

  sendAchievementsUnlocked(session, unlocked);
}
