import { describe, it, expect } from 'vitest';
import { friendshipCatalog } from '../../src/events/v2/catalog/friendship.js';
import { eventCatalog } from '../../src/events/v2/catalog/index.js';

describe('friendship v2 catalog', () => {
  it('registers 6 friendship events in the live catalog', () => {
    expect(friendshipCatalog).toHaveLength(6);
    const ids = friendshipCatalog.map((d) => d.id);
    for (const id of ids) {
      expect(eventCatalog.some((d) => d.id === id)).toBe(true);
    }
  });

  it('every friendship event binds a target; recurring beats are repeatable with cooldowns', () => {
    for (const def of friendshipCatalog) {
      expect(def.category).toBe('friendship');
      expect(typeof def.selectTarget).toBe('function');
      if (def.id === 'friendship-betrayal-amends') {
        // The arc payoff fires once per life (flag-gated), not on a cooldown.
        expect(def.repeatable).toBeUndefined();
        continue;
      }
      expect(def.repeatable).toBe(true);
      expect(def.cooldownDays ?? 0).toBeGreaterThan(0);
    }
  });

  it('selectTarget returns null with no friends and a friend when one qualifies', () => {
    const crisis = friendshipCatalog.find((d) => d.id === 'friendship-friend-in-crisis')!;
    const noFriends: any = { userId: 'u', c: { ageYears: 20 }, r: [] };
    expect(crisis.selectTarget!(noFriends)).toBeNull();

    const withFriend: any = {
      userId: 'u',
      c: { ageYears: 20 },
      r: [{ id: 'f1', firstname: 'Ana', status: 'alive', affinity: 50, relationships: ['friend'] }],
    };
    expect(crisis.selectTarget!(withFriend)).toEqual({ personId: 'f1', name: 'Ana' });
  });

  it('every choice references the target via effects.target (not hardcoded personIds)', () => {
    for (const def of friendshipCatalog) {
      for (const choice of def.choices) {
        expect(choice.effects?.relationships).toBeUndefined();
      }
    }
  });

  describe('friendship-betrayal-amends (arc follow-up)', () => {
    const amends = () => friendshipCatalog.find((d) => d.id === 'friendship-betrayal-amends')!;

    const basePlayer = (overrides: Record<string, unknown> = {}): any => ({
      userId: 'u',
      c: { ageYears: 20, ageDays: 7300 },
      r: [
        { id: 'f1', firstname: 'Ana', status: 'alive', affinity: 55, relationships: ['friend'] },
        { id: 'f2', firstname: 'Bo', status: 'alive', affinity: 38, relationships: ['friend'] },
      ],
      flags: new Set(['friendship_betrayal_confronted']),
      eventLastFired: { 'friendship-betrayal': 7300 - 60 },
      ...overrides,
    });

    it('is ineligible without the confronted flag', () => {
      expect(amends().isEligible!(basePlayer({ flags: new Set() }))).toBe(false);
    });

    it('is ineligible until 30 days after the betrayal fired', () => {
      const early = basePlayer({ eventLastFired: { 'friendship-betrayal': 7300 - 10 } });
      expect(amends().isEligible!(early)).toBe(false);
    });

    it('is eligible with the flag once the gap has passed (and with an unknown stamp)', () => {
      expect(amends().isEligible!(basePlayer())).toBe(true);
      expect(amends().isEligible!(basePlayer({ eventLastFired: {} }))).toBe(true);
    });

    it('binds the LOWEST-affinity friend in the estranged band', () => {
      expect(amends().selectTarget!(basePlayer())).toEqual({ personId: 'f2', name: 'Bo' });
    });

    it('reads flags in post-JSON array form too', () => {
      const loaded = basePlayer({ flags: ['friendship_betrayal_confronted'] });
      expect(amends().isEligible!(loaded)).toBe(true);
    });
  });
});
