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

// Mock WebSocket for testing
class MockWebSocket {
  messages: string[] = [];
  readyState = 1; // OPEN

  send(data: string) {
    this.messages.push(data);
  }

  close() {
    this.readyState = 3; // CLOSED
  }
}

// Simplified PlayerSession for testing
class TestPlayerSession {
  player: Player;
  ws: MockWebSocket;
  isConnected: boolean = true;

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

  send(message: unknown) {
    if (this.isConnected && this.ws.readyState === 1) {
      this.ws.send(JSON.stringify(message));
    }
  }

  sendPlayerObject() {
    this.send({ type: 'playerObject', player: this.player.toJSON() });
  }

  disconnect() {
    this.isConnected = false;
    this.ws.close();
  }
}

describe('Player Session', () => {
  let player: Player;
  let ws: MockWebSocket;
  let session: TestPlayerSession;

  beforeEach(() => {
    const character = new Person({
      id: 'char-1',
      firstname: 'Test',
      lastname: 'Character',
      sex: 'Male',
      ageYears: 25,
      ageDays: 9125,
      money: 1000,
      diamonds: 50,
      energy: 80,
      health: 90,
    });

    player = new Player({
      userId: 'player-1',
      character,
      status: 'playing',
      date: '2024-06-15',
      hourOfDay: 10,
    });

    ws = new MockWebSocket();
    session = new TestPlayerSession(player, ws);
  });

  describe('Session Creation', () => {
    it('should create session with player', () => {
      expect(session.player).toBeDefined();
      expect(session.player.userId).toBe('player-1');
    });

    it('should create session with WebSocket', () => {
      expect(session.ws).toBeDefined();
      expect(session.ws.readyState).toBe(1);
    });

    it('should start connected', () => {
      expect(session.isConnected).toBe(true);
    });
  });

  describe('Message Sending', () => {
    it('should send JSON messages', () => {
      session.send({ type: 'test', data: 'hello' });

      expect(ws.messages).toHaveLength(1);
      expect(ws.messages[0]).toBe(JSON.stringify({ type: 'test', data: 'hello' }));
    });

    it('should send player object', () => {
      session.sendPlayerObject();

      expect(ws.messages).toHaveLength(1);
      const parsed = JSON.parse(ws.messages[0]);
      expect(parsed.type).toBe('playerObject');
      expect(parsed.player).toBeDefined();
    });

    it('should not send when disconnected', () => {
      session.disconnect();
      session.send({ type: 'test' });

      expect(ws.messages).toHaveLength(0);
    });
  });

  describe('Session State', () => {
    it('should access player character', () => {
      expect(session.player.c.firstname).toBe('Test');
      expect(session.player.c.ageYears).toBe(25);
    });

    it('should modify player state', () => {
      session.player.c.money = 2000;

      expect(session.player.c.money).toBe(2000);
    });

    it('should access player relationships', () => {
      expect(session.player.r).toEqual([]);
    });

    it('should add relationships', () => {
      const friend = new Person({ id: 'friend-1', firstname: 'Friend', lastname: 'Person', sex: 'Male' });
      session.player.r.push(friend);

      expect(session.player.r).toHaveLength(1);
    });
  });

  describe('Disconnection', () => {
    it('should mark session as disconnected', () => {
      session.disconnect();

      expect(session.isConnected).toBe(false);
    });

    it('should close WebSocket', () => {
      session.disconnect();

      expect(ws.readyState).toBe(3);
    });
  });

  describe('Player Updates', () => {
    it('should send lightweight updates', () => {
      session.send({
        type: 'u',
        energy: 75,
        money: 1100,
        diamonds: 55,
      });

      const parsed = JSON.parse(ws.messages[0]);
      expect(parsed.type).toBe('u');
      expect(parsed.energy).toBe(75);
    });

    it('should send batch updates', () => {
      session.send({
        type: 'batch_update',
        energy: 70,
        health: 85,
        happiness: 60,
        money: 1200,
        time: '11:30',
        date: '2024-06-15',
      });

      const parsed = JSON.parse(ws.messages[0]);
      expect(parsed.type).toBe('batch_update');
    });

    it('should send full player object', () => {
      session.sendPlayerObject();

      const parsed = JSON.parse(ws.messages[0]);
      expect(parsed.player.c).toBeDefined();
    });
  });

  describe('Event Messages', () => {
    it('should send message event', () => {
      session.send({
        type: 'messageEvent',
        id: 'event-1',
        message: 'Something happened!',
        category: 'random',
      });

      const parsed = JSON.parse(ws.messages[0]);
      expect(parsed.type).toBe('messageEvent');
      expect(parsed.message).toBe('Something happened!');
    });

    it('should send question event', () => {
      session.send({
        type: 'questionEvent',
        id: 'question-1',
        message: 'What do you want to do?',
        answerOptions: [
          { id: '1', label: 'Option A' },
          { id: '2', label: 'Option B' },
        ],
      });

      const parsed = JSON.parse(ws.messages[0]);
      expect(parsed.type).toBe('questionEvent');
      expect(parsed.answerOptions).toHaveLength(2);
    });

    it('should send conversation event', () => {
      session.send({
        type: 'conversationEvent',
        id: 'conv-1',
        character: 'friend-1',
        conversation: [],
      });

      const parsed = JSON.parse(ws.messages[0]);
      expect(parsed.type).toBe('conversationEvent');
    });
  });

  describe('Error Handling', () => {
    it('should send error messages', () => {
      session.send({
        type: 'error',
        message: 'Something went wrong',
        code: 'INTERNAL_ERROR',
      });

      const parsed = JSON.parse(ws.messages[0]);
      expect(parsed.type).toBe('error');
      expect(parsed.code).toBe('INTERNAL_ERROR');
    });
  });

  describe('Session Timing', () => {
    it('should track session start time', () => {
      const sessionStart = Date.now();

      expect(sessionStart).toBeLessThanOrEqual(Date.now());
    });

    it('should calculate session duration', () => {
      const sessionStart = Date.now() - 60000; // 1 minute ago
      const duration = Date.now() - sessionStart;

      expect(duration).toBeGreaterThanOrEqual(60000);
    });
  });

  describe('Multiple Messages', () => {
    it('should handle multiple sequential messages', () => {
      session.send({ type: 'msg1' });
      session.send({ type: 'msg2' });
      session.send({ type: 'msg3' });

      expect(ws.messages).toHaveLength(3);
    });

    it('should maintain message order', () => {
      session.send({ type: 'first' });
      session.send({ type: 'second' });
      session.send({ type: 'third' });

      const types = ws.messages.map(m => JSON.parse(m).type);
      expect(types).toEqual(['first', 'second', 'third']);
    });
  });

  describe('Player Identification', () => {
    it('should have unique player ID', () => {
      expect(session.player.userId).toBe('player-1');
    });
  });

  describe('Game State Sync', () => {
    it('should sync game status', () => {
      session.send({
        type: 'statusUpdate',
        status: session.player.status,
      });

      const parsed = JSON.parse(ws.messages[0]);
      expect(parsed.status).toBe('playing');
    });

    it('should sync time of day', () => {
      session.send({
        type: 'timeUpdate',
        hourOfDay: session.player.hourOfDay,
        date: session.player.date,
      });

      const parsed = JSON.parse(ws.messages[0]);
      expect(parsed.hourOfDay).toBe(10);
      expect(parsed.date).toBe('2024-06-15');
    });
  });
});
