/**
 * ask_favor tool — affinity gates the player's favor outcomes (T008).
 *
 * The player asks an NPC for a favor; whether it is granted and how much money
 * a loan yields is decided by the NPC's affinity via evaluateFavor. A granted
 * loan must CONCRETELY credit the player's money (the audit's core complaint
 * was that affinity was never read into a real outcome).
 */
import { describe, it, expect, beforeEach } from 'vitest';
import { 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('ask_favor tool — affinity-gated loans', () => {
  let player: Player;

  beforeEach(() => {
    player = createTestPlayer({ money: 1000 });
  });

  function ask(affinity: number, args: Record<string, unknown>) {
    const character: Person = createTestCharacter({
      id: 'char-1',
      firstname: 'Sam',
      affinity,
      familiarity: 10,
      relationships: ['friend'],
    });
    player.r = [character];
    return processToolCall('ask_favor', args, character, player);
  }

  it('a high-affinity NPC grants a loan that credits the player MORE than a low-affinity one', () => {
    const startMoney = player.c.money ?? 0;
    const high = ask(80, { message: 'sure', sentiment: 'positive', favor_kind: 'loan' });
    const afterHigh = (player.c.money ?? 0) - startMoney;

    // reset money for the second NPC
    player.c.money = startMoney;
    const low = ask(30, { message: 'ok', sentiment: 'positive', favor_kind: 'loan' });
    const afterLow = (player.c.money ?? 0) - startMoney;

    expect(high.messageData?.granted).toBe(true);
    expect(low.messageData?.granted).toBe(true);
    expect(afterHigh).toBeGreaterThan(afterLow);
    expect(afterHigh).toBeGreaterThanOrEqual(800);
  });

  it('a very-low-affinity NPC declines and credits NO money', () => {
    const startMoney = player.c.money ?? 0;
    const result = ask(-10, { message: 'no', sentiment: 'neutral', favor_kind: 'loan' });

    expect(result.messageData?.granted).toBe(false);
    expect(result.messageData?.moneyGranted).toBe(0);
    expect(player.c.money).toBe(startMoney);
    expect(result.affinityChange).toBeLessThanOrEqual(0);
  });

  it('honors a requested loan amount up to the affinity-derived cap', () => {
    const startMoney = player.c.money ?? 0;
    ask(90, { message: 'here', sentiment: 'positive', favor_kind: 'loan', requested_amount: 250 });
    expect((player.c.money ?? 0) - startMoney).toBe(250);
  });

  it('job referral surfaces a referralStrength that scales with affinity', () => {
    const high = ask(100, { message: 'glad to', sentiment: 'positive', favor_kind: 'job_referral' });
    player.c.money = 1000;
    const low = ask(40, { message: 'fine', sentiment: 'positive', favor_kind: 'job_referral' });

    expect(high.messageData?.referralStrength as number).toBeGreaterThan(
      low.messageData?.referralStrength as number
    );
  });
});
