/**
 * Unit tests for Presence Manager
 * Tests cursor tracking and presence updates
 */

import { describe, it, expect, beforeEach } from 'vitest';
import { Doc } from 'yjs';
import { Presence, RemoteUser } from './presence';

describe('Presence Manager', () => {
  describe('Initialization', () => {
    it('should initialize with user identity', () => {
      const doc = new Doc();
      const presence = new Presence();

      presence.initialize(doc, 'user-1', 'Test User');

      const remoteUsers = presence.getRemoteUsers();
      expect(remoteUsers.size).toBe(0); // Own user not included
    });

    it('should assign a deterministic color to user', () => {
      const doc = new Doc();
      const presence = new Presence();

      presence.initialize(doc, 'user-1', 'Test User');

      const users = Array.from(presence.getRemoteUsers().values());
      // User should have been assigned a color (even though not in remote users)
      expect(presence['userColor']).toBeDefined();
    });
  });

  describe('Cursor Tracking', () => {
    let doc: Doc;
    let presence: Presence;

    beforeEach(() => {
      doc = new Doc();
      presence = new Presence();
      presence.initialize(doc, 'user-1', 'Test User');
    });

    it('should update cursor position', () => {
      const cursor: Parameters<Presence['updateCursorPosition']>[0] = {
        userId: 'user-1',
        userName: 'Test User',
        position: 42,
        color: '#ff0000',
      };

      presence.updateCursorPosition(cursor);

      const ownCursor = presence.getUserCursor('user-1');
      expect(ownCursor?.position).toBe(42);
    });

    it('should update selection range', () => {
      const cursor: Parameters<Presence['updateCursorPosition']>[0] = {
        userId: 'user-1',
        userName: 'Test User',
        position: 10,
        color: '#ff0000',
      };

      presence.updateCursorPosition(cursor);

      const selection: Parameters<Presence['updateSelection']>[0] = {
        anchor: 10,
        head: 20,
      };

      presence.updateSelection(selection);

      const ownCursor = presence.getUserCursor('user-1');
      expect(ownCursor?.selection).toEqual(selection);
    });

    it('should track last active time', () => {
      const cursor: Parameters<Presence['updateCursorPosition']>[0] = {
        userId: 'user-1',
        userName: 'Test User',
        position: 0,
        color: '#ff0000',
      };

      const beforeUpdate = new Date();
      presence.updateCursorPosition(cursor);
      const afterUpdate = new Date();

      const ownCursor = presence.getUserCursor('user-1');
      expect(ownCursor?.lastActive).toBeInstanceOf(Date);
      expect(ownCursor!.lastActive!.getTime()).toBeGreaterThanOrEqual(beforeUpdate.getTime());
      expect(ownCursor!.lastActive!.getTime()).toBeLessThanOrEqual(afterUpdate.getTime());
    });
  });

  describe('Remote Users', () => {
    it('should track multiple remote users', () => {
      const doc = new Doc();
      const presence = new Presence();

      presence.initialize(doc, 'user-1', 'Local User');

      // Simulate remote users joining
      const presenceMap = doc.getMap<RemoteUser>('presence');
      presenceMap.set('user-2', {
        userId: 'user-2',
        userName: 'Remote User 1',
        isEditing: true,
        lastActive: new Date(),
        cursor: {
          userId: 'user-2',
          userName: 'Remote User 1',
          position: 100,
          color: '#00ff00',
        },
      });

      presenceMap.set('user-3', {
        userId: 'user-3',
        userName: 'Remote User 2',
        isEditing: false,
        lastActive: new Date(),
      });

      const remoteUsers = presence.getRemoteUsers();
      expect(remoteUsers.size).toBe(2);
      expect(remoteUsers.get('user-2')?.userName).toBe('Remote User 1');
      expect(remoteUsers.get('user-3')?.userName).toBe('Remote User 2');
    });

    it('should not include own user in remote users', () => {
      const doc = new Doc();
      const presence = new Presence();

      presence.initialize(doc, 'user-1', 'Local User');

      const presenceMap = doc.getMap<RemoteUser>('presence');
      presenceMap.set('user-1', {
        userId: 'user-1',
        userName: 'Local User',
        isEditing: true,
        lastActive: new Date(),
      });

      presenceMap.set('user-2', {
        userId: 'user-2',
        userName: 'Remote User',
        isEditing: false,
        lastActive: new Date(),
      });

      const remoteUsers = presence.getRemoteUsers();
      expect(remoteUsers.size).toBe(1);
      expect(remoteUsers.has('user-1')).toBe(false);
      expect(remoteUsers.has('user-2')).toBe(true);
    });
  });

  describe('Event Listeners', () => {
    it('should notify on user join', () => {
      const doc = new Doc();
      const presence = new Presence();
      
      let joinedUser: RemoteUser | undefined;
      presence.onUserJoin((user) => {
        joinedUser = user;
      });

      presence.initialize(doc, 'user-1', 'Local User');

      const presenceMap = doc.getMap<RemoteUser>('presence');
      presenceMap.set('user-2', {
        userId: 'user-2',
        userName: 'New User',
        isEditing: true,
        lastActive: new Date(),
      });

      // Note: In a real scenario, this would trigger immediately
      // For now, we verify the listener was registered
      expect(presence['userJoinListeners'].size).toBe(1);
    });

    it('should notify on user leave', () => {
      const doc = new Doc();
      const presence = new Presence();
      
      let leftUserId: string | undefined;
      presence.onUserLeave((userId) => {
        leftUserId = userId;
      });

      presence.initialize(doc, 'user-1', 'Local User');

      const presenceMap = doc.getMap<RemoteUser>('presence');
      presenceMap.set('user-2', {
        userId: 'user-2',
        userName: 'Leaving User',
        isEditing: false,
        lastActive: new Date(),
      });

      presenceMap.delete('user-2');

      expect(presence['userLeaveListeners'].size).toBe(1);
    });
  });

  describe('Cleanup', () => {
    it('should clear presence on destroy', () => {
      const doc = new Doc();
      const presence = new Presence();

      presence.initialize(doc, 'user-1', 'Test User');

      const cursor: Parameters<Presence['updateCursorPosition']>[0] = {
        userId: 'user-1',
        userName: 'Test User',
        position: 42,
        color: '#ff0000',
      };
      presence.updateCursorPosition(cursor);

      presence.destroy();

      // After destroy, internal state should be cleared
      expect(presence['doc']).toBe(null);
      expect(presence['userId']).toBe(null);
    });
  });

  describe('User Color Assignment', () => {
    it('should assign different colors to different users', () => {
      const doc = new Doc();
      const presence1 = new Presence();
      const presence2 = new Presence();

      presence1.initialize(doc, 'user-1', 'User 1');
      presence2.initialize(doc, 'user-2', 'User 2');

      expect(presence1['userColor']).not.toBe(presence2['userColor']);
    });

    it('should assign same color to same user', () => {
      const doc = new Doc();
      const presence1 = new Presence();
      const presence2 = new Presence();

      presence1.initialize(doc, 'user-1', 'User 1');
      presence2.initialize(doc, 'user-1', 'User 1');

      expect(presence1['userColor']).toBe(presence2['userColor']);
    });
  });
});
