import { describe, expect, it } from 'vitest';

import {
  lateLifeCatalog,
  resolveLateLifeChoice,
  LATE_LIFE_FLAGS,
} from '../../src/events/v2/catalog/lateLife.js';
import { adulthoodCatalog } from '../../src/events/v2/catalog/adulthood.js';
import { eventCatalog } from '../../src/events/v2/catalog/index.js';
import { isEventEligible } from '../../src/events/v2/engine/selector.js';
import type { EventDefinition, EventPlayerContext } from '../../src/events/v2/types.js';

function makePlayer(overrides: Partial<EventPlayerContext> = {}): EventPlayerContext {
  return {
    userId: 'player-1',
    c: { ageYears: 70, occupation: 'work', money: 8000, happiness: 50, stress: 20, energy: 100, social: 50 },
    askedQuestions: new Set<string>(),
    eventLastFired: {},
    flags: new Set<string>(),
    ...overrides,
  };
}

function findLateLife(id: string): EventDefinition {
  const def = lateLifeCatalog.find((event) => event.id === id);
  expect(def, `late-life event ${id} should exist`).toBeDefined();
  return def!;
}

describe('events v2 late-life catalog: structure', () => {
  it('registers a non-trivial set of unique, well-formed late-life events', () => {
    expect(lateLifeCatalog.length).toBeGreaterThanOrEqual(10);

    const ids = lateLifeCatalog.map((e) => e.id);
    expect(new Set(ids).size).toBe(ids.length);

    for (const def of lateLifeCatalog) {
      expect(def.category).toBe('lateLife');
      expect(def.choices.length).toBeGreaterThan(0);
      const choiceIds = def.choices.map((c) => c.choiceId);
      expect(new Set(choiceIds).size).toBe(choiceIds.length);
      // every interactive choice resolves to its static text
      for (const c of def.choices) {
        expect(resolveLateLifeChoice(def.id, c.choiceId)).toBe(c.resolutionText);
      }
      // every late-life event is a third-act event (minAge 55+)
      expect(def.minAge ?? 0).toBeGreaterThanOrEqual(55);
    }
  });

  it('is wired into the global event catalog', () => {
    for (const def of lateLifeCatalog) {
      expect(eventCatalog.some((e) => e.id === def.id)).toBe(true);
    }
  });
});

describe('events v2 late-life catalog: eligible at 55+', () => {
  it('makes a working 70-year-old eligible for the core late-life beats', () => {
    const player = makePlayer();
    for (const id of [
      'lateLife.retirementDecision',
      'lateLife.downsizeHome',
      'lateLife.firstGrandchild',
      'lateLife.peersFuneral',
      'lateLife.writeWill',
      'lateLife.estrangedChild',
      'lateLife.healthScareLate',
    ]) {
      expect(isEventEligible(findLateLife(id), player, 1), `${id} eligible at 70`).toBe(true);
    }
  });

  it('gates late-life content out for a young adult', () => {
    const young = makePlayer({ c: { ageYears: 30, occupation: 'work', money: 8000 } });
    expect(isEventEligible(findLateLife('lateLife.firstGrandchild'), young, 1)).toBe(false);
    expect(isEventEligible(findLateLife('lateLife.writeWill'), young, 1)).toBe(false);
    expect(isEventEligible(findLateLife('lateLife.healthScareLate'), young, 1)).toBe(false);
  });

  it('makes the end-of-life reflection arc repeatable on a cooldown', () => {
    const reflection = findLateLife('lateLife.reflection');
    expect(reflection.repeatable).toBe(true);
    expect(reflection.cooldownDays).toBeGreaterThan(0);

    const player = makePlayer({ c: { ageYears: 75 } });
    expect(isEventEligible(reflection, player, 100)).toBe(true);
    // Just fired — within cooldown, ineligible despite being repeatable.
    player.eventLastFired = { 'lateLife.reflection': 100 };
    expect(isEventEligible(reflection, player, 120)).toBe(false);
    // After the cooldown, eligible again.
    expect(isEventEligible(reflection, player, 100 + (reflection.cooldownDays ?? 0) + 1)).toBe(true);
  });
});

describe('events v2 late-life catalog: staged arc gated on predecessor + flag', () => {
  it('keeps the retirement-adjustment follow-up gated until the player has retired', () => {
    const followUp = findLateLife('lateLife.retirementAdjustment');

    // Has not been asked the retirement decision yet — follow-up ineligible.
    const fresh = makePlayer({ c: { ageYears: 70 } });
    expect(isEventEligible(followUp, fresh, 1)).toBe(false);

    // Asked the decision but did NOT retire (no flag) — still ineligible.
    const askedNotRetired = makePlayer({
      c: { ageYears: 70 },
      askedQuestions: new Set(['lateLife.retirementDecision']),
      flags: new Set<string>(),
    });
    expect(isEventEligible(followUp, askedNotRetired, 1)).toBe(false);

    // Asked the decision AND retired (flag set) — now eligible.
    const retired = makePlayer({
      c: { ageYears: 70 },
      askedQuestions: new Set(['lateLife.retirementDecision']),
      flags: new Set([LATE_LIFE_FLAGS.retired]),
    });
    expect(isEventEligible(followUp, retired, 1)).toBe(true);
  });

  it('keeps the estranged-child reconciliation payoff gated on the reach-out flag', () => {
    const payoff = findLateLife('lateLife.estrangedChildReconciled');

    const reachedOut = makePlayer({
      c: { ageYears: 65 },
      askedQuestions: new Set(['lateLife.estrangedChild']),
      flags: new Set([LATE_LIFE_FLAGS.reconciledChild]),
    });
    expect(isEventEligible(payoff, reachedOut, 1)).toBe(true);

    const letBe = makePlayer({
      c: { ageYears: 65 },
      askedQuestions: new Set(['lateLife.estrangedChild']),
      flags: new Set<string>(),
    });
    expect(isEventEligible(payoff, letBe, 1)).toBe(false);
  });
});

describe('events v2 deepened adulthood catalog', () => {
  it('makes the new emotional adulthood beats eligible in their windows', () => {
    const firstHome = adulthoodCatalog.find((e) => e.id === 'firstHome')!;
    const midlife = adulthoodCatalog.find((e) => e.id === 'midlifeCrossroads')!;
    expect(firstHome, 'firstHome exists').toBeDefined();
    expect(midlife, 'midlifeCrossroads exists').toBeDefined();

    // firstHome: eligible at 32 (within 25-55), gated out at 70.
    const at32 = { userId: 'p', c: { ageYears: 32, money: 12000 }, askedQuestions: new Set<string>(), flags: new Set<string>() } as EventPlayerContext;
    expect(isEventEligible(firstHome, at32, 1)).toBe(true);
    const at70 = { userId: 'p', c: { ageYears: 70, money: 12000 }, askedQuestions: new Set<string>(), flags: new Set<string>() } as EventPlayerContext;
    expect(isEventEligible(firstHome, at70, 1)).toBe(false);

    // midlifeCrossroads: eligible at 45, gated out at 25.
    const at45 = { userId: 'p', c: { ageYears: 45 }, askedQuestions: new Set<string>(), flags: new Set<string>() } as EventPlayerContext;
    expect(isEventEligible(midlife, at45, 1)).toBe(true);
    const at25 = { userId: 'p', c: { ageYears: 25 }, askedQuestions: new Set<string>(), flags: new Set<string>() } as EventPlayerContext;
    expect(isEventEligible(midlife, at25, 1)).toBe(false);
  });

  it('caps the early-career adulthood beats so they stop firing in old age (pacing fix)', () => {
    const firstJob = adulthoodCatalog.find((e) => e.id === 'firstJob')!;
    const promo = adulthoodCatalog.find((e) => e.id === 'promotionOpportunity')!;
    const old = { userId: 'p', c: { ageYears: 80 }, askedQuestions: new Set<string>(), flags: new Set<string>() } as EventPlayerContext;
    expect(isEventEligible(firstJob, old, 1)).toBe(false);
    expect(isEventEligible(promo, old, 1)).toBe(false);
  });
});
