import { beforeEach, describe, expect, it, vi } from 'vitest';
import { Person } from '../../src/models/Person';
import { Player } from '../../src/models/Player';
import { handleConversation } from '../../src/handlers/conversations';
import { getOpenAIResponse } from '../../src/events/conversations/index';
import { markConversationAsRead, saveConversation } from '../../src/database/players';

vi.mock('../../src/database/players', () => ({
  saveConversation: vi.fn().mockResolvedValue(undefined),
  markConversationAsRead: vi.fn().mockResolvedValue(undefined),
}));

vi.mock('../../src/events/conversations/index', async (importOriginal) => {
  const actual = await importOriginal<typeof import('../../src/events/conversations/index')>();
  return {
    ...actual,
    getOpenAIResponse: vi.fn().mockResolvedValue(undefined),
  };
});

vi.mock('../../src/services/retention/index', () => ({
  updateQuestProgress: vi.fn().mockResolvedValue(null),
  trackConversation: vi.fn(),
  sendQuestProgress: vi.fn(),
}));

class MockPlayerSession {
  player: Player;
  sentMessages: any[] = [];
  isWSOpen = true;

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

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

function createPlayer(hourOfDay: number): Player {
  const playerCharacter = new Person({
    id: 'player-character',
    firstname: 'Test',
    lastname: 'Player',
    ageYears: 18,
  });

  const npc = new Person({
    id: 'npc-julian',
    firstname: 'Julian',
    lastname: 'Rivera',
    relationships: ['friend'],
    affinity: 50,
  });

  return new Player({
    userId: 'ios-device-id-longer-than-thirty-six-characters',
    character: playerCharacter,
    r: [npc],
    conversations: [],
    status: 'playing',
    date: '01-01',
    time: `${hourOfDay}:00`,
    hourOfDay,
    minuteOfHour: 0,
  });
}

describe('conversation messaging handler', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  it('treats whitespace-only chat input as a load-only request', async () => {
    const session = new MockPlayerSession(createPlayer(10));

    await handleConversation({
      conversationEvent: 'freeResponse',
      characterID: 'npc-julian',
      cType: 'chat',
      response: '   ',
      tempId: 'temp-blank',
    }, session as any);

    const conversationEvents = session.sentMessages.filter((message) => message.type === 'conversationEvent');
    expect(conversationEvents).toHaveLength(1);
    expect(conversationEvents[0].conversation).toEqual([]);
    expect(getOpenAIResponse).not.toHaveBeenCalled();
  });

  it('persists read state when opening a conversation', async () => {
    const player = createPlayer(10);
    const session = new MockPlayerSession(player);

    await handleConversation({
      conversationEvent: 'load',
      characterID: 'npc-julian',
      cType: 'chat',
    }, session as any);

    const conversationEvents = session.sentMessages.filter((message) => message.type === 'conversationEvent');
    expect(conversationEvents).toHaveLength(1);
    expect(conversationEvents[0].unread).toBe(false);
    expect(markConversationAsRead).toHaveBeenCalledWith(
      conversationEvents[0].id,
      'ios-device-id-longer-than-thirty-six-characters',
    );
  });

  it('trims player text and persists unavailable delivery notices as system messages', async () => {
    const session = new MockPlayerSession(createPlayer(4));

    await handleConversation({
      conversationEvent: 'freeResponse',
      characterID: 'npc-julian',
      cType: 'chat',
      response: ' Hey ',
      tempId: 'temp-hey',
    }, session as any);

    const conversationEvents = session.sentMessages.filter((message) => message.type === 'conversationEvent');
    expect(conversationEvents).toHaveLength(2);

    const immediateMessages = conversationEvents[0].conversation;
    expect(immediateMessages).toHaveLength(1);
    expect(immediateMessages[0]).toMatchObject({
      message: 'Hey',
      sender: 'player-character',
      tempId: 'temp-hey',
    });

    const unavailableMessages = conversationEvents[1].conversation;
    expect(unavailableMessages).toHaveLength(2);
    expect(unavailableMessages[1].sender).toBe('system');
    expect(unavailableMessages[1].message).toContain('Julian is probably asleep');
    expect(saveConversation).toHaveBeenCalledWith(
      'ios-device-id-longer-than-thirty-six-characters',
      'npc-julian',
      expect.any(String),
      unavailableMessages,
      false,
    );
    expect(getOpenAIResponse).not.toHaveBeenCalled();
  });
});
