/**
 * Life Goals Integration Tests
 *
 * Verifies that the existing retention hooks (integration.ts) advance life-goal
 * progress and emit a `lifeGoalsUpdate` server->client message:
 * - onChildBorn advances "Raise a Family"
 * - updateLifeGoals completes a goal and emits justCompleted with the reward
 */
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import {
  onChildBorn,
  updateLifeGoals,
} from '../../../src/services/retention/integration.js';
import type { PlayerSession } from '../../../src/game/PlayerSession.js';
import {
  clearAllLifeGoals,
  clearPlayerLifeGoals,
  getLifeScore,
} from '../../../src/services/retention/lifeGoals.js';
import {
  initializePlayerStatistics,
  incrementStat,
  clearAllStatistics,
} from '../../../src/services/retention/statistics.js';

const PLAYER_ID = 'test-life-goals-integration';

interface SentMessage {
  type: string;
  [key: string]: unknown;
}

/** Minimal fake session: the integration hooks only use .player and .send(). */
function makeFakeSession(opts: { age: number; money?: number; prestige?: number }) {
  const sent: SentMessage[] = [];
  const player = {
    userId: PLAYER_ID,
    money: opts.money ?? 0,
    c: {
      ageYears: opts.age,
      prestige: opts.prestige ?? 0,
      diamonds: 0,
    },
  };
  const session = {
    player,
    send(message: unknown) {
      sent.push(message as SentMessage);
    },
  };
  return { session: session as unknown as PlayerSession, sent, player };
}

function lastLifeGoalsUpdate(sent: SentMessage[]): SentMessage | undefined {
  return [...sent].reverse().find((m) => m.type === 'lifeGoalsUpdate');
}

describe('Life Goals Integration (hooks)', () => {
  beforeEach(() => {
    clearAllLifeGoals();
    clearAllStatistics();
    initializePlayerStatistics(PLAYER_ID);
  });

  afterEach(() => {
    clearPlayerLifeGoals(PLAYER_ID);
    clearAllStatistics();
  });

  it('emits a lifeGoalsUpdate with the active slate when goals are evaluated', () => {
    const { session, sent } = makeFakeSession({ age: 30 });
    updateLifeGoals(session);

    const update = lastLifeGoalsUpdate(sent);
    expect(update).toBeDefined();
    expect(Array.isArray(update!.active)).toBe(true);
    expect((update!.active as unknown[]).length).toBeGreaterThan(0);
    expect(Array.isArray(update!.completed)).toBe(true);
    expect(typeof update!.lifeScore).toBe('number');

    // Active goals carry a live progressPercent the client can render.
    for (const g of update!.active as Array<Record<string, unknown>>) {
      expect(typeof g.id).toBe('string');
      expect(typeof g.progressPercent).toBe('number');
      expect(typeof g.target).toBe('number');
    }
  });

  it('advances "Raise a Family" when onChildBorn fires', async () => {
    const { session, sent } = makeFakeSession({ age: 30 });

    // Seed the slate first.
    updateLifeGoals(session);
    sent.length = 0;

    // onChildBorn increments childrenCount (via trackChildBorn) then updates goals.
    await onChildBorn(session);

    const update = lastLifeGoalsUpdate(sent);
    expect(update).toBeDefined();
    const family = (update!.active as Array<Record<string, unknown>>).find(
      (g) => g.id === 'raise_two_children'
    );
    expect(family).toBeDefined();
    // 1 of 2 children -> 50%.
    expect(family!.current).toBe(1);
    expect(family!.progressPercent).toBe(50);
  });

  it('completes a goal via the hook path and surfaces justCompleted + reward', async () => {
    const { session, sent, player } = makeFakeSession({ age: 30 });

    updateLifeGoals(session);
    sent.length = 0;

    // Pre-load one child, then onChildBorn brings it to the target of 2.
    incrementStat(PLAYER_ID, 'childrenCount', 1);
    await onChildBorn(session);

    const update = lastLifeGoalsUpdate(sent);
    expect(update).toBeDefined();
    const justCompleted = update!.justCompleted as Array<Record<string, unknown>>;
    const done = justCompleted.find((c) => c.id === 'raise_two_children');
    expect(done).toBeDefined();
    expect(done!.reward).toBeGreaterThan(0);

    // Diamonds were granted to the character and life score accrued.
    expect(player.c.diamonds).toBeGreaterThan(0);
    expect(getLifeScore(PLAYER_ID)).toBeGreaterThan(0);

    // Completed goal appears in the completed list.
    const completedIds = (update!.completed as Array<Record<string, unknown>>).map((c) => c.id);
    expect(completedIds).toContain('raise_two_children');
  });
});
