import { describe, it, expect } from 'vitest';
import type { EventDefinition, EventPlayerContext } from '../../src/events/v2/types.js';
import { isEventEligible, interpolateTarget } from '../../src/events/v2/engine/selector.js';
import { applyEventEffects } from '../../src/events/v2/engine/effects.js';
import { EventEngine } from '../../src/events/v2/engine/EventEngine.js';
import { EventResponder } from '../../src/events/v2/engine/respond.js';
import { EventRegistry } from '../../src/events/v2/registry.js';

const basePlayer = (): EventPlayerContext =>
  ({ userId: 'u1', c: { ageYears: 20, ageDays: 7300 }, r: [] }) as unknown as EventPlayerContext;

describe('selectTarget eligibility', () => {
  const def = (selectTarget: EventDefinition['selectTarget']): EventDefinition => ({
    id: 'bind-test',
    category: 'friendship',
    prompt: 'Hello {target}',
    choices: [{ choiceId: 'ok', text: 'ok' }],
    repeatable: true,
    selectTarget,
  });

  it('event with selectTarget returning null is ineligible', () => {
    expect(isEventEligible(def(() => null), basePlayer(), 100)).toBe(false);
  });

  it('event with selectTarget returning a target is eligible', () => {
    expect(isEventEligible(def(() => ({ personId: 'p1', name: 'Ana' })), basePlayer(), 100)).toBe(true);
  });
});

describe('interpolateTarget', () => {
  it('replaces every {target} occurrence', () => {
    expect(interpolateTarget('Hey {target}, {target} called.', 'Ana')).toBe('Hey Ana, Ana called.');
  });
  it('no-ops without a name', () => {
    expect(interpolateTarget('Hey {target}', undefined)).toBe('Hey {target}');
  });
});

describe('effects.target resolution', () => {
  const playerWithFriend = () =>
    ({
      userId: 'u1',
      c: { ageYears: 20, ageDays: 7300 },
      r: [{ id: 'npc-1', firstname: 'Ana', affinity: 50, relationships: ['friend'] }],
    }) as any;

  it('applies target affinityDelta to the bound NPC and reports it by name', () => {
    const player = playerWithFriend();
    const applied = applyEventEffects(player, { target: { affinityDelta: 10 } }, 'npc-1');
    expect(player.r[0].affinity).toBe(60);
    expect(applied).toEqual([{ personId: 'npc-1', name: 'Ana', affinityDelta: 10 }]);
  });

  it('stamps lastFriendInteractionDay on a positive target delta', () => {
    const player = playerWithFriend();
    applyEventEffects(player, { target: { affinityDelta: 5 } }, 'npc-1');
    expect(player.r[0].lastFriendInteractionDay).toBe(7300);
  });

  it('does NOT stamp interaction day on a negative delta', () => {
    const player = playerWithFriend();
    applyEventEffects(player, { target: { affinityDelta: -10 } }, 'npc-1');
    expect(player.r[0].affinity).toBe(40);
    expect(player.r[0].lastFriendInteractionDay).toBeUndefined();
  });

  it('no-ops gracefully when the bound NPC is missing (e.g. died mid-event)', () => {
    const player = playerWithFriend();
    const applied = applyEventEffects(player, { target: { affinityDelta: 10 } }, 'npc-gone');
    expect(applied).toEqual([]);
  });

  it('no-ops target effects when no targetPersonId is supplied', () => {
    const player = playerWithFriend();
    const applied = applyEventEffects(player, { target: { affinityDelta: 10 } });
    expect(player.r[0].affinity).toBe(50);
    expect(applied).toEqual([]);
  });
});

describe('end-to-end target binding (fire -> respond)', () => {
  function makeStore() {
    const instances: any[] = [];
    return {
      instances,
      async getPendingEventInstances(playerId: string) {
        return instances.filter((i) => i.playerId === playerId && i.status === 'pending');
      },
      async createEventInstance(input: any) {
        const record = { ...input, status: 'pending' };
        instances.push(record);
        return record;
      },
      async answerEventInstance(instanceId: string, choiceId: string) {
        const inst = instances.find((i) => i.instanceId === instanceId);
        if (!inst) return false;
        inst.status = 'answered';
        inst.selectedChoiceId = choiceId;
        return true;
      },
      async resolveEventInstance(instanceId: string) {
        const inst = instances.find((i) => i.instanceId === instanceId);
        if (!inst) return false;
        inst.status = 'resolved';
        return true;
      },
    };
  }

  const boundDef: EventDefinition = {
    id: 'bound-coffee',
    category: 'friendship',
    prompt: '{target} invites you to coffee.',
    repeatable: true,
    selectTarget: (player) => {
      const friend = (player as any).r?.find((p: any) =>
        (p.relationships ?? []).includes('friend')
      );
      return friend ? { personId: friend.id, name: friend.firstname } : null;
    },
    choices: [
      {
        choiceId: 'go',
        text: 'Grab coffee with {target}',
        resolutionText: 'You and {target} caught up over coffee.',
        effects: { target: { affinityDelta: 5 } },
      },
    ],
  };

  it('binds, interpolates, and applies the target effect on respond', async () => {
    const store = makeStore();
    const registry = new EventRegistry([boundDef]);
    const engine = new EventEngine(registry, store as any);
    const responder = new EventResponder(registry, store as any);
    const player: any = {
      userId: 'u1',
      c: { ageYears: 20, ageDays: 7300, money: 100 },
      r: [{ id: 'npc-1', firstname: 'Ana', affinity: 50, relationships: ['friend'] }],
      askedQuestions: new Set<string>(),
    };

    const prompt = await engine.promptNext(player);
    expect(prompt?.type).toBe('event_prompt');
    expect((prompt as any).prompt).toBe('Ana invites you to coffee.');
    expect((prompt as any).choices[0].text).toBe('Grab coffee with Ana');
    expect(store.instances[0].context).toMatchObject({
      targetPersonId: 'npc-1',
      targetName: 'Ana',
    });

    const resolved = await responder.respond(player, { eventId: 'bound-coffee', choiceId: 'go' });
    expect(resolved.type).toBe('event_resolved');
    expect((resolved as any).resolutionText).toBe('You and Ana caught up over coffee.');
    expect(player.r[0].affinity).toBe(55);
    expect((resolved as any).resolvedRelationships).toEqual([
      { personId: 'npc-1', name: 'Ana', affinityDelta: 5 },
    ]);
  });
});
