import { describe, expect, it, vi } from 'vitest';
import { handleClaimEvent } from '../../src/handlers/retention.js';

interface MockSession {
  player: {
    userId: string;
    c: {
      money: number;
      energy: number;
      diamonds: number;
    };
    events: Set<string>;
    lifeEvents?: Array<{
      id: string;
      claimed?: boolean;
      moneyReward?: number;
      energyReward?: number;
      diamondReward?: number;
      claimedAt?: string;
    }>;
  };
  send: ReturnType<typeof vi.fn>;
  sendPlayerObject: ReturnType<typeof vi.fn>;
}

function createSession(): MockSession {
  return {
    player: {
      userId: 'player-1',
      c: {
        money: 100,
        energy: 50,
        diamonds: 2,
      },
      events: new Set(),
      lifeEvents: [],
    },
    send: vi.fn(),
    sendPlayerObject: vi.fn(),
  };
}

describe('retention claimEvent security behavior', () => {
  it('rejects claims for events the player does not have', async () => {
    const session = createSession();

    await handleClaimEvent({ eventId: 'missing_event' }, session as unknown as any);

    expect(session.send).toHaveBeenCalledWith(expect.objectContaining({
      type: 'eventClaimed',
      success: false,
      eventId: 'missing_event',
    }));
    expect(session.sendPlayerObject).not.toHaveBeenCalled();
  });

  it('applies rewards and marks event claimed once', async () => {
    const session = createSession();
    session.player.events.add('graduation_bonus');
    session.player.lifeEvents = [{
      id: 'graduation_bonus',
      claimed: false,
      moneyReward: 250,
      energyReward: 10,
      diamondReward: 5,
    }];

    await handleClaimEvent({ eventId: 'graduation_bonus' }, session as unknown as any);

    expect(session.player.c.money).toBe(350);
    expect(session.player.c.energy).toBe(60);
    expect(session.player.c.diamonds).toBe(7);
    expect(session.player.lifeEvents[0].claimed).toBe(true);
    expect(session.player.events.has('claimed:graduation_bonus')).toBe(true);
    expect(session.send).toHaveBeenCalledWith(expect.objectContaining({
      type: 'eventClaimed',
      success: true,
      eventId: 'graduation_bonus',
    }));
  });

  it('rejects duplicate claims for the same event', async () => {
    const session = createSession();
    session.player.events.add('starter_bonus');
    session.player.lifeEvents = [{
      id: 'starter_bonus',
      claimed: false,
      diamondReward: 3,
    }];

    await handleClaimEvent({ eventId: 'starter_bonus' }, session as unknown as any);
    await handleClaimEvent({ eventId: 'starter_bonus' }, session as unknown as any);

    const lastSend = session.send.mock.calls[session.send.mock.calls.length - 1]?.[0];
    expect(lastSend).toEqual(expect.objectContaining({
      type: 'eventClaimed',
      success: false,
      eventId: 'starter_bonus',
    }));
  });
});
