/**
 * Data Handler Tests — account deletion lifecycle.
 *
 * Covers the Apple-required functional account deletion path:
 *  - handleDeleteAccount persists a deletionScheduledAt ~30 days out (and still
 *    rejects without the 'DELETE' confirmation).
 *  - handleCancelAccountDeletion clears the persisted marker.
 *
 * Persistence is exercised through a mock session whose savePlayer() is spied,
 * mirroring the real PlayerSession contract (handlers mutate player + call
 * savePlayer()). We assert both the in-memory player state AND that savePlayer
 * was invoked so the change actually survives save/load.
 */
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { Person } from '../../src/models/Person';
import { Player } from '../../src/models/Player';
import {
  handleDeleteAccount,
  handleCancelAccountDeletion,
} from '../../src/handlers/data';

class MockPlayerSession {
  player: Player;
  sentMessages: any[] = [];
  savePlayer = vi.fn(async () => undefined);

  constructor(player: Player) {
    this.player = player;
  }

  send(message: any) {
    this.sentMessages.push(message);
  }

  getLastMessage() {
    return this.sentMessages[this.sentMessages.length - 1];
  }
}

function makePlayer(): Player {
  const character = new Person({
    id: 'char-1',
    firstname: 'Test',
    lastname: 'Player',
    sex: 'Male',
    ageYears: 25,
  });
  return new Player({
    userId: 'user-1',
    character,
    r: [],
    status: 'playing',
  });
}

describe('handleDeleteAccount', () => {
  let player: Player;
  let session: MockPlayerSession;

  beforeEach(() => {
    player = makePlayer();
    session = new MockPlayerSession(player);
  });

  it("rejects without confirmation 'DELETE' and does not schedule deletion", async () => {
    await handleDeleteAccount({ confirmation: 'nope' }, session as any);

    expect(session.getLastMessage()).toMatchObject({
      type: 'error',
      error_code: 'INVALID_CONFIRMATION',
    });
    expect(player.deletionScheduledAt == null).toBe(true);
    expect(session.savePlayer).not.toHaveBeenCalled();
  });

  it("persists a deletionScheduledAt ~30 days out with confirmation 'DELETE'", async () => {
    const before = Date.now();
    await handleDeleteAccount({ confirmation: 'DELETE' }, session as any);

    // Persisted marker is set and saved.
    expect(typeof player.deletionScheduledAt).toBe('string');
    expect(session.savePlayer).toHaveBeenCalledTimes(1);

    const scheduledMs = Date.parse(player.deletionScheduledAt as string);
    const thirtyDays = 30 * 24 * 60 * 60 * 1000;
    // Allow a generous window around the 30-day target.
    expect(scheduledMs).toBeGreaterThan(before + thirtyDays - 60_000);
    expect(scheduledMs).toBeLessThan(Date.now() + thirtyDays + 60_000);

    // Client still gets the existing response shape.
    expect(session.getLastMessage()).toMatchObject({
      type: 'accountDeletionScheduled',
      gracePeriodDays: 30,
    });
  });

  it('round-trips the persisted marker through Player toJSON/constructor', async () => {
    await handleDeleteAccount({ confirmation: 'DELETE' }, session as any);
    const scheduled = player.deletionScheduledAt;

    const reloaded = new Player(JSON.parse(JSON.stringify(player.toJSON())) as any);
    expect(reloaded.deletionScheduledAt).toBe(scheduled);
  });
});

describe('handleCancelAccountDeletion', () => {
  it('clears the persisted deletionScheduledAt and saves', async () => {
    const player = makePlayer();
    player.deletionScheduledAt = new Date(Date.now() + 30 * 86_400_000).toISOString();
    const session = new MockPlayerSession(player);

    await handleCancelAccountDeletion({}, session as any);

    expect(player.deletionScheduledAt == null).toBe(true);
    expect(session.savePlayer).toHaveBeenCalledTimes(1);
    expect(session.getLastMessage()).toMatchObject({
      type: 'accountDeletionCancelled',
      success: true,
    });
  });
});
