/**
 * Conversation ripple tests (T011b).
 *
 * Verifies the two bounded "conversation ripples" wired into the tool processor:
 *  - A confession that LANDS (NPC accepts) creates a romance prospect directly.
 *  - A betrayal disclosed in a breakup propagates an affinity loss to mutual NPCs
 *    in the betraying partner's close circle.
 */
import { describe, it, expect, beforeEach } from 'vitest';
import {
  applyConfessionRipple,
  applyBetrayalRipple,
  processToolCall,
} from '../../../src/events/conversations/tool_processor.js';
import { createTestPlayer, createTestCharacter } from '../../utils/conversationTestUtils.js';
import type { Player, Person } from '../../../src/models/index.js';

describe('confession ripple: a landed confession creates a romance prospect', () => {
  let player: Player;
  let partner: Person;

  beforeEach(() => {
    player = createTestPlayer();
    partner = createTestCharacter({ id: 'love-1', firstname: 'Robin', affinity: 60, relationships: ['friend'] });
    // Content safety: romance is adults only (18+). The createTestPlayer/Character
    // helpers leave ageYears at 0 (the `age` field they accept is a no-op), so set
    // adult ages explicitly for the romance-prospect mechanic under test.
    player.c.ageYears = 25;
    partner.ageYears = 24;
    player.r = [partner];
    player.relData = [];
  });

  it('creates a Prospect relData entry directly via applyConfessionRipple', () => {
    expect(player.relData.length).toBe(0);

    const created = applyConfessionRipple(player, partner);

    expect(created).toBe(true);
    expect(player.relData.length).toBe(1);
    expect(player.relData[0].relationshipStatus).toBe('Prospect');
    // The prospect links the player and the partner.
    const rel = player.relData[0];
    expect([rel.person1, rel.person2]).toContain(partner.id);
  });

  it('nudges a sub-threshold but reciprocating partner up to the dating gate', () => {
    // Partner just under the affinity >= 50 gate; acceptance implies interest.
    partner.affinity = 45;
    const created = applyConfessionRipple(player, partner);
    expect(created).toBe(true);
    expect(partner.affinity).toBeGreaterThanOrEqual(50);
    expect(player.relData.length).toBe(1);
  });

  it('creates the prospect end-to-end when accept_confession is processed', () => {
    expect(player.relData.length).toBe(0);

    processToolCall(
      'accept_confession',
      { message: 'I feel the same way!', reaction_style: 'overjoyed', reciprocate_feelings: true },
      partner,
      player
    );

    expect(player.relData.length).toBe(1);
    expect(player.relData[0].relationshipStatus).toBe('Prospect');
  });
});

describe('betrayal ripple: disclosed betrayal propagates affinity loss to mutual NPCs', () => {
  let player: Player;
  let partner: Person;
  let mutualFriend: Person;
  let mutualFamily: Person;
  let stranger: Person;

  beforeEach(() => {
    player = createTestPlayer();
    partner = createTestCharacter({ id: 'partner-1', firstname: 'Sam', affinity: 70, relationships: ['girlfriend'] });
    mutualFriend = createTestCharacter({ id: 'friend-1', firstname: 'Casey', affinity: 60, relationships: ['friend'] });
    mutualFamily = createTestCharacter({ id: 'family-1', firstname: 'Pat', affinity: 80, relationships: ['sibling'] });
    stranger = createTestCharacter({ id: 'stranger-1', firstname: 'Lee', affinity: 50, relationships: ['coworker'] });
    player.r = [partner, mutualFriend, mutualFamily, stranger];
  });

  it('lowers affinity of close-circle NPCs but not the betrayer or unrelated NPCs', () => {
    const friendBefore = mutualFriend.affinity;
    const familyBefore = mutualFamily.affinity;
    const partnerBefore = partner.affinity;
    const strangerBefore = stranger.affinity;

    const affected = applyBetrayalRipple(player, partner.id);

    // Mutual close-circle NPCs took a hit.
    expect(affected.sort()).toEqual([mutualFamily.id, mutualFriend.id].sort());
    expect(mutualFriend.affinity).toBeLessThan(friendBefore);
    expect(mutualFamily.affinity).toBeLessThan(familyBefore);

    // The betrayer and the unrelated coworker are untouched by the ripple.
    expect(partner.affinity).toBe(partnerBefore);
    expect(stranger.affinity).toBe(strangerBefore);
  });

  it('fires through processInitiateBreakup only when the reason is betrayal', () => {
    const friendBefore = mutualFriend.affinity;

    // Non-betrayal reason: no propagation.
    processToolCall(
      'initiate_breakup',
      { message: 'I need space.', reason: 'need_space', tone: 'sad', sentiment: 'negative' },
      partner,
      player
    );
    expect(mutualFriend.affinity).toBe(friendBefore);

    // Betrayal reason: propagation fires.
    processToolCall(
      'initiate_breakup',
      { message: 'You betrayed me.', reason: 'betrayal', tone: 'angry', sentiment: 'negative' },
      partner,
      player
    );
    expect(mutualFriend.affinity).toBeLessThan(friendBefore);
  });
});
