import { beforeEach, describe, expect, it, vi } from 'vitest';
import { Person } from '../../../src/models/Person.js';
import { Player } from '../../../src/models/Player.js';
import { queueLiveActivityUpdate } from '../../../src/services/notifications/liveActivityManager.js';
import { sendLiveActivityUpdate } from '../../../src/services/notifications/pushNotificationService.js';

vi.mock('../../../src/services/notifications/pushNotificationService.js', async (importOriginal) => {
  const actual = await importOriginal<typeof import('../../../src/services/notifications/pushNotificationService.js')>();
  return {
    ...actual,
    sendLiveActivityUpdate: vi.fn(),
  };
});

const mockSend = vi.mocked(sendLiveActivityUpdate);

function createPlayerWithLiveActivity(): Player {
  const player = new Player({
    userId: 'player-dereg-1',
    status: 'playing',
    controller: 'active',
    character: new Person({
      id: 'char-dereg-1',
      firstname: 'Alex',
      lastname: 'River',
      sex: 'Female',
      ageYears: 18,
      status: 'alive',
    }),
  });
  player.liveActivity = {
    activityId: 'activity-1',
    characterId: 'char-dereg-1',
    pushToken: 'dead-token-1234',
    startedAt: '2026-06-10T11:00:00Z',
  };
  return player;
}

describe('queueLiveActivityUpdate token failure handling', () => {
  beforeEach(() => {
    mockSend.mockReset();
  });

  it('deregisters the Live Activity when APNS reports BadDeviceToken', async () => {
    mockSend.mockResolvedValue({ success: false, error: 'BadDeviceToken', statusCode: 400 });
    const player = createPlayerWithLiveActivity();

    const result = await queueLiveActivityUpdate(player);

    expect(result.sent).toBe(false);
    expect(result.reason).toBe('token_rejected');
    expect(player.liveActivity).toBeUndefined();
  });

  it('deregisters the Live Activity when APNS returns 410 Unregistered', async () => {
    mockSend.mockResolvedValue({ success: false, error: 'Unregistered', statusCode: 410 });
    const player = createPlayerWithLiveActivity();

    await queueLiveActivityUpdate(player);

    expect(player.liveActivity).toBeUndefined();
  });

  it('keeps the registration but throttles after a transient failure', async () => {
    mockSend.mockResolvedValue({ success: false, error: 'HTTP/2 error: ECONNRESET' });
    const player = createPlayerWithLiveActivity();
    const now = new Date('2026-06-10T12:00:00Z');

    const result = await queueLiveActivityUpdate(player, {}, now);

    expect(result.sent).toBe(false);
    expect(player.liveActivity).toBeDefined();
    // The attempt is stamped so the next tick is throttled instead of hot-looping.
    expect(player.liveActivity?.lastSentAt).toBe(now.toISOString());

    const retry = await queueLiveActivityUpdate(player, {}, new Date(now.getTime() + 5_000));
    expect(retry.reason).toBe('throttled');
    expect(mockSend).toHaveBeenCalledTimes(1);
  });

  it('still stamps lastSentAt and keeps the registration on success', async () => {
    mockSend.mockResolvedValue({ success: true, statusCode: 200 });
    const player = createPlayerWithLiveActivity();
    const now = new Date('2026-06-10T12:00:00Z');

    const result = await queueLiveActivityUpdate(player, {}, now);

    expect(result.sent).toBe(true);
    expect(player.liveActivity?.lastSentAt).toBe(now.toISOString());
  });
});
