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

import {
  familyArcCatalog,
  resolveFamilyArcChoice,
  FAMILY_ARC_FLAGS,
} from '../../src/events/v2/catalog/family-arcs.js';
import { EventResponder } from '../../src/events/v2/engine/respond.js';
import { isEventEligible, playerHasFlag } from '../../src/events/v2/engine/selector.js';
import { createEventRegistry } from '../../src/events/v2/registry.js';
import { applyEventEffects } from '../../src/events/v2/engine/effects.js';
import type { EventDefinition, EventPlayerContext } from '../../src/events/v2/types.js';

function makePlayer(overrides: Partial<EventPlayerContext> = {}): EventPlayerContext {
  return {
    userId: 'p1',
    c: { ageYears: 40, money: 8000, happiness: 50, stress: 20, energy: 100, social: 50 },
    askedQuestions: new Set<string>(),
    eventLastFired: {},
    flags: new Set<string>(),
    r: [],
    ...overrides,
  };
}

function findEvent(id: string): EventDefinition {
  const def = familyArcCatalog.find((event) => event.id === id);
  expect(def, `family-arc event ${id} should exist`).toBeDefined();
  return def!;
}

function makeStore(eventId: string) {
  let pending = [{ instanceId: 'inst-1', playerId: 'p1', eventId, status: 'pending' as const }];
  return {
    getPendingEventInstances: async () => pending,
    answerEventInstance: async () => true,
    resolveEventInstance: async () => {
      pending = [];
      return true;
    },
  };
}

const withParent = { r: [{ status: 'alive', relationships: ['mother'] }] } as Partial<EventPlayerContext>;
const withSibling = { r: [{ status: 'alive', relationships: ['sibling'] }] } as Partial<EventPlayerContext>;
const withChild = { r: [{ status: 'alive', relationships: ['child'] }] } as Partial<EventPlayerContext>;
const withDeadParent = { r: [{ status: 'dead', relationships: ['father'] }] } as Partial<EventPlayerContext>;

describe('events v2 family-arc catalog: structure', () => {
  it('registers the milestone arcs with unique, namespaced ids (no collision with family.ts)', () => {
    expect(familyArcCatalog.length).toBeGreaterThanOrEqual(14);

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

    for (const def of familyArcCatalog) {
      expect(def.category).toBe('family');
      expect(def.id.startsWith('familyArc.')).toBe(true);
      expect(def.choices.length).toBeGreaterThan(0);
      const choiceIds = def.choices.map((c) => c.choiceId);
      expect(new Set(choiceIds).size).toBe(choiceIds.length);
      for (const c of def.choices) {
        expect(resolveFamilyArcChoice(def.id, c.choiceId)).toBe(c.resolutionText);
      }
    }
  });
});

describe('events v2 family-arc catalog: eligibility', () => {
  it('gates parentGetsSick on a living parent', () => {
    expect(isEventEligible(findEvent('familyArc.parentGetsSick'), makePlayer(), 1)).toBe(false);
    expect(isEventEligible(findEvent('familyArc.parentGetsSick'), makePlayer(withParent), 1)).toBe(true);
  });

  it('gates siblingsWedding on a living sibling', () => {
    expect(isEventEligible(findEvent('familyArc.siblingsWedding'), makePlayer(), 1)).toBe(false);
    expect(isEventEligible(findEvent('familyArc.siblingsWedding'), makePlayer(withSibling), 1)).toBe(true);
  });

  it('gates childMilestones on a living child and respects the cooldown', () => {
    const def = findEvent('familyArc.childMilestones');
    expect(def.repeatable).toBe(true);
    expect(def.cooldownDays).toBe(360);

    expect(isEventEligible(def, makePlayer(), 1)).toBe(false);

    const parent = makePlayer(withChild);
    expect(isEventEligible(def, parent, 100)).toBe(true);

    parent.eventLastFired = { 'familyArc.childMilestones': 100 };
    (parent.askedQuestions as Set<string>).add('familyArc.childMilestones');
    expect(isEventEligible(def, parent, 300)).toBe(false); // within cooldown
    expect(isEventEligible(def, parent, 470)).toBe(true); // after cooldown, repeatable
  });

  it('gates inheritanceDispute on a deceased parent', () => {
    expect(isEventEligible(findEvent('familyArc.inheritanceDispute'), makePlayer(), 1)).toBe(false);
    expect(isEventEligible(findEvent('familyArc.inheritanceDispute'), makePlayer(withDeadParent), 1)).toBe(true);
  });
});

describe('events v2 family-arc catalog: flag-gated multi-stage arcs', () => {
  it('attending a sibling wedding unlocks prep + day, not the aftermath', async () => {
    const player = makePlayer(withSibling);
    const registry = createEventRegistry(familyArcCatalog);
    const responder = new EventResponder(registry, makeStore('familyArc.siblingsWedding'));

    await responder.respond(player, { eventId: 'familyArc.siblingsWedding', choiceId: 'attend' });
    expect(playerHasFlag(player, FAMILY_ARC_FLAGS.weddingAttending)).toBe(true);
    expect(playerHasFlag(player, FAMILY_ARC_FLAGS.weddingSkipped)).toBe(false);

    expect(isEventEligible(findEvent('familyArc.weddingPrep'), player, 1)).toBe(true);
    expect(isEventEligible(findEvent('familyArc.weddingAftermath'), player, 1)).toBe(false);

    (player.askedQuestions as Set<string>).add('familyArc.weddingPrep');
    expect(isEventEligible(findEvent('familyArc.weddingDay'), player, 1)).toBe(true);
  });

  it('skipping the wedding unlocks only the aftermath', async () => {
    const player = makePlayer(withSibling);
    const registry = createEventRegistry(familyArcCatalog);
    const responder = new EventResponder(registry, makeStore('familyArc.siblingsWedding'));

    await responder.respond(player, { eventId: 'familyArc.siblingsWedding', choiceId: 'skip' });
    expect(playerHasFlag(player, FAMILY_ARC_FLAGS.weddingSkipped)).toBe(true);
    expect(isEventEligible(findEvent('familyArc.weddingPrep'), player, 1)).toBe(false);
    expect(isEventEligible(findEvent('familyArc.weddingAftermath'), player, 1)).toBe(true);
  });

  it('confronting a family secret unlocks aftermath; investigating unlocks the investigation', async () => {
    const confronter = makePlayer();
    let registry = createEventRegistry(familyArcCatalog);
    await new EventResponder(registry, makeStore('familyArc.familySecret')).respond(confronter, {
      eventId: 'familyArc.familySecret',
      choiceId: 'confront',
    });
    expect(playerHasFlag(confronter, FAMILY_ARC_FLAGS.secretConfronted)).toBe(true);
    expect(isEventEligible(findEvent('familyArc.secretAftermath'), confronter, 1)).toBe(true);
    expect(isEventEligible(findEvent('familyArc.secretInvestigation'), confronter, 1)).toBe(false);

    const investigator = makePlayer();
    registry = createEventRegistry(familyArcCatalog);
    await new EventResponder(registry, makeStore('familyArc.familySecret')).respond(investigator, {
      eventId: 'familyArc.familySecret',
      choiceId: 'investigate',
    });
    expect(playerHasFlag(investigator, FAMILY_ARC_FLAGS.secretInvestigated)).toBe(true);
    expect(isEventEligible(findEvent('familyArc.secretInvestigation'), investigator, 1)).toBe(true);
    expect(isEventEligible(findEvent('familyArc.secretAftermath'), investigator, 1)).toBe(false);
  });

  it('attending a reunion unlocks the reunion day; skipping does not', async () => {
    const attendee = makePlayer(withSibling);
    let registry = createEventRegistry(familyArcCatalog);
    await new EventResponder(registry, makeStore('familyArc.familyReunion')).respond(attendee, {
      eventId: 'familyArc.familyReunion',
      choiceId: 'organize',
    });
    expect(playerHasFlag(attendee, FAMILY_ARC_FLAGS.reunionAttending)).toBe(true);
    expect(isEventEligible(findEvent('familyArc.reunionDay'), attendee, 1)).toBe(true);

    const skipper = makePlayer(withSibling);
    registry = createEventRegistry(familyArcCatalog);
    await new EventResponder(registry, makeStore('familyArc.familyReunion')).respond(skipper, {
      eventId: 'familyArc.familyReunion',
      choiceId: 'skip',
    });
    expect(playerHasFlag(skipper, FAMILY_ARC_FLAGS.reunionAttending)).toBe(false);
    expect(isEventEligible(findEvent('familyArc.reunionDay'), skipper, 1)).toBe(false);
  });

  it('parentGetsSick chains support -> outcome via wasAsked (no flag branch needed)', async () => {
    const player = makePlayer(withParent);
    const registry = createEventRegistry(familyArcCatalog);
    await new EventResponder(registry, makeStore('familyArc.parentGetsSick')).respond(player, {
      eventId: 'familyArc.parentGetsSick',
      choiceId: 'rush',
    });
    expect(isEventEligible(findEvent('familyArc.parentSickSupport'), player, 1)).toBe(true);
    (player.askedQuestions as Set<string>).add('familyArc.parentSickSupport');
    expect(isEventEligible(findEvent('familyArc.parentSickOutcome'), player, 1)).toBe(true);
  });
});

describe('events v2 family-arc catalog: effects apply', () => {
  it('applies the inheritance fight money gain and social hit', () => {
    const player = makePlayer({ c: { ageYears: 50, money: 1000, stress: 10, social: 50 } });
    const fight = findEvent('familyArc.inheritanceDispute').choices.find((c) => c.choiceId === 'fight')!;
    applyEventEffects(player as { c: Record<string, unknown> }, fight.effects);
    expect(player.c.money).toBe(6000);
    expect(player.c.stress).toBe(30);
    expect(player.c.social).toBe(40);
  });

  it('applies childMilestones celebrate cost via moneyCost path', () => {
    const player = makePlayer({ c: { ageYears: 40, money: 1000, happiness: 50, social: 50 } });
    const celebrate = findEvent('familyArc.childMilestones').choices.find((c) => c.choiceId === 'celebrate')!;
    applyEventEffects(player as { c: Record<string, unknown> }, {
      ...celebrate.effects,
      resources: { money: -(celebrate.moneyCost ?? 0), ...celebrate.effects?.resources },
    });
    expect(player.c.money).toBe(950);
    expect(player.c.happiness).toBe(65);
  });
});
