import { beforeEach, describe, expect, it, vi } from 'vitest';
import { Person } from '../../src/models/Person.js';
import { Player } from '../../src/models/Player.js';

const {
  romanceMock,
  establishMatchRelationshipMock,
  dateNightMock,
  breakUpMock,
  divorceMock,
  partnerGiftMock,
  getDateIdeasMock,
  updateQuestProgressMock,
  sendQuestProgressMock,
} = vi.hoisted(() => ({
  romanceMock: vi.fn(() => true),
  establishMatchRelationshipMock: vi.fn(() => ({ id: 'rel-1', relationshipStatus: 'Dating' })),
  dateNightMock: vi.fn(() => ({ id: 'dateNight', message: 'Nice date' })),
  breakUpMock: vi.fn(() => true),
  divorceMock: vi.fn(() => true),
  partnerGiftMock: vi.fn(() => null),
  getDateIdeasMock: vi.fn(() => []),
  updateQuestProgressMock: vi.fn(async () => null),
  sendQuestProgressMock: vi.fn(),
}));

vi.mock('../../src/services/relationships/index.js', () => ({
  romance: romanceMock,
  establishMatchRelationship: establishMatchRelationshipMock,
  dateNight: dateNightMock,
  breakUp: breakUpMock,
  divorce: divorceMock,
  partnerGift: partnerGiftMock,
  getDateIdeas: getDateIdeasMock,
  propose: vi.fn(() => true),
  marry: vi.fn(() => true),
}));

vi.mock('../../src/services/character/character_manager.js', () => ({
  createCharacter: vi.fn(() => new Person({ id: 'swipe-1', firstname: 'Pat', lastname: 'Lee' })),
}));

vi.mock('../../src/services/dating/compatibility.js', () => ({
  calculateCompatibility: vi.fn(() => 50),
}));

vi.mock('../../src/services/retention/index.js', () => ({
  updateQuestProgress: updateQuestProgressMock,
  sendQuestProgress: sendQuestProgressMock,
}));

import {
  handleRomance,
  handleDateNight,
  handleGetSwipeCharacter,
  handleSwipeMatch,
} from '../../src/handlers/romance.js';

class MockSession {
  player: Player;
  sent: unknown[] = [];

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

  send(message: unknown): void {
    this.sent.push(message);
  }

  sendPlayerObject(): void {
    this.sent.push({ type: 'playerObject' });
  }
}

function createPlayer(): Player {
  const character = new Person({
    id: 'char-1',
    firstname: 'Test',
    lastname: 'Player',
    sex: 'Male',
    ageYears: 25,
    money: 1000,
    energy: 100,
  });

  return new Player({
    userId: 'user-1',
    character,
    status: 'playing',
    date: '2026-02-07',
  });
}

describe('romance handler payload regressions', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  it('accepts raw string payload for romance partner id', async () => {
    const session = new MockSession(createPlayer());

    await handleRomance('partner-123', session as unknown as Parameters<typeof handleRomance>[1]);

    expect(romanceMock).toHaveBeenCalledWith(session.player, 'partner-123');
    expect(session.sent).toContainEqual(
      expect.objectContaining({ type: 'romanceStarted', partnerId: 'partner-123', success: true })
    );
  });

  it('accepts raw string payload for dateNight idea name', async () => {
    const session = new MockSession(createPlayer());

    await handleDateNight('Movie Night at Home', session as unknown as Parameters<typeof handleDateNight>[1]);

    expect(dateNightMock).toHaveBeenCalledWith(session.player, 'Movie Night at Home');
    expect(updateQuestProgressMock).toHaveBeenCalledWith('user-1', 'go_on_date', 1, session.player);
    expect(session.sent).toContainEqual(expect.objectContaining({ type: 'messageEvent' }));
  });

  it('promotes a swipe match into an active relationship', async () => {
    const session = new MockSession(createPlayer());

    // Seed a swipe character (mocked createCharacter returns Person 'swipe-1').
    await handleGetSwipeCharacter(undefined, session as unknown as Parameters<typeof handleGetSwipeCharacter>[1]);
    await handleSwipeMatch(undefined, session as unknown as Parameters<typeof handleSwipeMatch>[1]);

    // The matched character is added to relationships and promoted to a relationship.
    expect(session.player.r.some((p) => p.id === 'swipe-1')).toBe(true);
    expect(establishMatchRelationshipMock).toHaveBeenCalledTimes(1);
    expect(establishMatchRelationshipMock).toHaveBeenCalledWith(
      session.player,
      expect.objectContaining({ id: 'swipe-1' })
    );
    // Swipe character is cleared and full state is pushed to the client.
    expect((session.player as unknown as { swipeCharacter: unknown }).swipeCharacter).toBe(false);
    expect(session.sent).toContainEqual(expect.objectContaining({ type: 'playerObject' }));
  });
});
