import {
  usersReducer,
  initialState,
  UsersState
} from './users.reducer';
import { UsersActions } from './users.actions';
import { User } from '../../shared-kernel';

function createUser(overrides: Partial<User> = {}): User {
  return {
    id: 'user-1',
    oderId: 'oder-1',
    username: 'testuser',
    displayName: 'Test User',
    status: 'online',
    role: 'member',
    joinedAt: Date.now(),
    ...overrides
  };
}

describe('users reducer - status', () => {
  let baseState: UsersState;

  beforeEach(() => {
    const user = createUser();

    baseState = usersReducer(
      initialState,
      UsersActions.setCurrentUser({ user })
    );
  });

  describe('setManualStatus', () => {
    it('sets manualStatus in state and updates current user status', () => {
      const state = usersReducer(baseState, UsersActions.setManualStatus({ status: 'busy' }));

      expect(state.manualStatus).toBe('busy');
      expect(state.entities['user-1']?.status).toBe('busy');
    });

    it('clears manual status when null and sets online', () => {
      const intermediate = usersReducer(baseState, UsersActions.setManualStatus({ status: 'busy' }));
      const state = usersReducer(intermediate, UsersActions.setManualStatus({ status: null }));

      expect(state.manualStatus).toBeNull();
      expect(state.entities['user-1']?.status).toBe('online');
    });

    it('sets away status correctly', () => {
      const state = usersReducer(baseState, UsersActions.setManualStatus({ status: 'away' }));

      expect(state.manualStatus).toBe('away');
      expect(state.entities['user-1']?.status).toBe('away');
    });

    it('returns unchanged state when no current user', () => {
      const emptyState = { ...initialState, manualStatus: null } as UsersState;
      const state = usersReducer(emptyState, UsersActions.setManualStatus({ status: 'busy' }));

      expect(state.manualStatus).toBe('busy');
      // No user entities to update
    });
  });

  describe('updateRemoteUserStatus', () => {
    it('updates status of an existing remote user', () => {
      const remoteUser = createUser({ id: 'remote-1', oderId: 'oder-remote-1', displayName: 'Remote' });
      const withRemote = usersReducer(baseState, UsersActions.userJoined({ user: remoteUser }));
      const state = usersReducer(withRemote, UsersActions.updateRemoteUserStatus({ userId: 'remote-1', status: 'away' }));

      expect(state.entities['remote-1']?.status).toBe('away');
    });

    it('updates remote user to busy (DND)', () => {
      const remoteUser = createUser({ id: 'remote-1', oderId: 'oder-remote-1', displayName: 'Remote' });
      const withRemote = usersReducer(baseState, UsersActions.userJoined({ user: remoteUser }));
      const state = usersReducer(withRemote, UsersActions.updateRemoteUserStatus({ userId: 'remote-1', status: 'busy' }));

      expect(state.entities['remote-1']?.status).toBe('busy');
    });

    it('does not modify state for non-existent user', () => {
      const state = usersReducer(baseState, UsersActions.updateRemoteUserStatus({ userId: 'nonexistent', status: 'away' }));

      expect(state).toBe(baseState);
    });
  });

  describe('avatar updates', () => {
    it('updates current user avatar metadata', () => {
      const state = usersReducer(baseState, UsersActions.updateCurrentUserAvatar({
        avatar: {
          avatarUrl: 'data:image/webp;base64,abc123',
          avatarHash: 'hash-1',
          avatarMime: 'image/webp',
          avatarUpdatedAt: 1234
        }
      }));

      expect(state.entities['user-1']?.avatarUrl).toBe('data:image/webp;base64,abc123');
      expect(state.entities['user-1']?.avatarHash).toBe('hash-1');
      expect(state.entities['user-1']?.avatarMime).toBe('image/webp');
      expect(state.entities['user-1']?.avatarUpdatedAt).toBe(1234);
    });

    it('keeps newer remote avatar when stale update arrives later', () => {
      const withRemote = usersReducer(
        baseState,
        UsersActions.upsertRemoteUserAvatar({
          user: {
            id: 'remote-1',
            oderId: 'oder-remote-1',
            username: 'remote',
            displayName: 'Remote',
            avatarUrl: 'data:image/webp;base64,newer',
            avatarHash: 'hash-newer',
            avatarMime: 'image/webp',
            avatarUpdatedAt: 200
          }
        })
      );
      const state = usersReducer(
        withRemote,
        UsersActions.upsertRemoteUserAvatar({
          user: {
            id: 'remote-1',
            oderId: 'oder-remote-1',
            username: 'remote',
            displayName: 'Remote',
            avatarUrl: 'data:image/webp;base64,older',
            avatarHash: 'hash-older',
            avatarMime: 'image/webp',
            avatarUpdatedAt: 100
          }
        })
      );

      expect(state.entities['remote-1']?.avatarUrl).toBe('data:image/webp;base64,newer');
      expect(state.entities['remote-1']?.avatarHash).toBe('hash-newer');
      expect(state.entities['remote-1']?.avatarUpdatedAt).toBe(200);
    });

    it('updates the current user profile metadata', () => {
      const state = usersReducer(baseState, UsersActions.updateCurrentUserProfile({
        profile: {
          displayName: 'Updated User',
          description: 'New description',
          profileUpdatedAt: 4567
        }
      }));

      expect(state.entities['user-1']?.displayName).toBe('Updated User');
      expect(state.entities['user-1']?.description).toBe('New description');
      expect(state.entities['user-1']?.profileUpdatedAt).toBe(4567);
    });

    it('keeps newer remote profile text when stale profile data arrives later', () => {
      const withRemote = usersReducer(
        baseState,
        UsersActions.upsertRemoteUserAvatar({
          user: {
            id: 'remote-1',
            oderId: 'oder-remote-1',
            username: 'remote',
            displayName: 'Remote Newer',
            description: 'Newest bio',
            profileUpdatedAt: 300
          }
        })
      );
      const state = usersReducer(
        withRemote,
        UsersActions.upsertRemoteUserAvatar({
          user: {
            id: 'remote-1',
            oderId: 'oder-remote-1',
            username: 'remote',
            displayName: 'Remote Older',
            description: 'Old bio',
            profileUpdatedAt: 100
          }
        })
      );

      expect(state.entities['remote-1']?.displayName).toBe('Remote Newer');
      expect(state.entities['remote-1']?.description).toBe('Newest bio');
      expect(state.entities['remote-1']?.profileUpdatedAt).toBe(300);
    });

    it('allows remote profile-only sync updates without avatar bytes', () => {
      const state = usersReducer(
        baseState,
        UsersActions.upsertRemoteUserAvatar({
          user: {
            id: 'remote-2',
            oderId: 'oder-remote-2',
            username: 'remote2',
            displayName: 'Remote Profile',
            description: 'Profile only sync',
            profileUpdatedAt: 700
          }
        })
      );

      expect(state.entities['remote-2']?.displayName).toBe('Remote Profile');
      expect(state.entities['remote-2']?.description).toBe('Profile only sync');
      expect(state.entities['remote-2']?.profileUpdatedAt).toBe(700);
      expect(state.entities['remote-2']?.avatarUrl).toBeUndefined();
    });
  });

  describe('presence-aware user with status', () => {
    it('preserves incoming status on user join', () => {
      const user = createUser({ id: 'away-user', oderId: 'oder-away', status: 'away', presenceServerIds: ['server-1'] });
      const state = usersReducer(baseState, UsersActions.userJoined({ user }));

      expect(state.entities['away-user']?.status).toBe('away');
    });

    it('preserves busy status on user join', () => {
      const user = createUser({ id: 'busy-user', oderId: 'oder-busy', status: 'busy', presenceServerIds: ['server-1'] });
      const state = usersReducer(baseState, UsersActions.userJoined({ user }));

      expect(state.entities['busy-user']?.status).toBe('busy');
    });

    it('preserves existing non-offline status on sync when incoming is online', () => {
      const awayUser = createUser({ id: 'u1', oderId: 'u1', status: 'busy', presenceServerIds: ['s1'] });
      const withUser = usersReducer(baseState, UsersActions.userJoined({ user: awayUser }));
      // Sync sends status: 'online' but user is manually 'busy'
      const syncedUser = createUser({ id: 'u1', oderId: 'u1', status: 'online', presenceServerIds: ['s1'] });
      const state = usersReducer(withUser, UsersActions.syncServerPresence({ roomId: 's1', users: [syncedUser] }));

      // The buildPresenceAwareUser function takes incoming status when non-offline
      expect(state.entities['u1']?.status).toBe('online');
    });

    it('preserves omitted live peer presence and voice state during stale server snapshot', () => {
      const remoteUser = createUser({
        id: 'u2',
        oderId: 'u2',
        displayName: 'Voice Peer',
        presenceServerIds: ['s1'],
        voiceState: {
          isConnected: true,
          isMuted: false,
          isDeafened: false,
          isSpeaking: true,
          roomId: 'voice-1',
          serverId: 's1'
        },
        cameraState: { isEnabled: true },
        screenShareState: { isSharing: true }
      });
      const withUser = usersReducer(baseState, UsersActions.userJoined({ user: remoteUser }));
      const state = usersReducer(withUser, UsersActions.syncServerPresence({
        roomId: 's1',
        users: [],
        connectedPeerIds: ['u2']
      }));

      expect(state.entities['u2']?.presenceServerIds).toEqual(['s1']);
      expect(state.entities['u2']?.isOnline).toBe(true);
      expect(state.entities['u2']?.voiceState?.isConnected).toBe(true);
      expect(state.entities['u2']?.voiceState?.roomId).toBe('voice-1');
      expect(state.entities['u2']?.cameraState?.isEnabled).toBe(true);
      expect(state.entities['u2']?.screenShareState?.isSharing).toBe(true);
    });

    it('clears omitted peer live state when transport is gone', () => {
      const remoteUser = createUser({
        id: 'u3',
        oderId: 'u3',
        displayName: 'Dropped Peer',
        presenceServerIds: ['s1'],
        voiceState: {
          isConnected: true,
          isMuted: false,
          isDeafened: false,
          isSpeaking: true,
          roomId: 'voice-1',
          serverId: 's1'
        },
        cameraState: { isEnabled: true },
        screenShareState: { isSharing: true }
      });
      const withUser = usersReducer(baseState, UsersActions.userJoined({ user: remoteUser }));
      const state = usersReducer(withUser, UsersActions.syncServerPresence({
        roomId: 's1',
        users: [],
        connectedPeerIds: []
      }));

      expect(state.entities['u3']?.presenceServerIds).toBeUndefined();
      expect(state.entities['u3']?.isOnline).toBe(false);
      expect(state.entities['u3']?.status).toBe('offline');
      expect(state.entities['u3']?.voiceState?.isConnected).toBe(false);
      expect(state.entities['u3']?.voiceState?.roomId).toBeUndefined();
      expect(state.entities['u3']?.cameraState?.isEnabled).toBe(false);
      expect(state.entities['u3']?.screenShareState?.isSharing).toBe(false);
    });

    it('preserves voice state on user_left while live peer transport still exists', () => {
      const remoteUser = createUser({
        id: 'u4',
        oderId: 'u4',
        displayName: 'Transient Peer',
        presenceServerIds: ['s1'],
        status: 'online',
        voiceState: {
          isConnected: true,
          isMuted: false,
          isDeafened: false,
          isSpeaking: true,
          roomId: 'voice-1',
          serverId: 's1'
        },
        cameraState: { isEnabled: true }
      });
      const withUser = usersReducer(baseState, UsersActions.userJoined({ user: remoteUser }));
      const state = usersReducer(withUser, UsersActions.userLeft({
        userId: 'u4',
        serverId: 's1',
        connectedPeerIds: ['u4']
      }));

      expect(state.entities['u4']?.presenceServerIds).toEqual(['s1']);
      expect(state.entities['u4']?.isOnline).toBe(true);
      expect(state.entities['u4']?.status).toBe('online');
      expect(state.entities['u4']?.voiceState?.isConnected).toBe(true);
      expect(state.entities['u4']?.voiceState?.roomId).toBe('voice-1');
      expect(state.entities['u4']?.cameraState?.isEnabled).toBe(true);
    });

    it('matches live user_left transports by oderId and peerId', () => {
      const remoteUser = createUser({
        id: 'db-id-5',
        oderId: 'oder-5',
        peerId: 'peer-5',
        displayName: 'Peer Id Match',
        presenceServerIds: ['s1'],
        voiceState: {
          isConnected: true,
          isMuted: false,
          isDeafened: false,
          isSpeaking: false,
          roomId: 'voice-1',
          serverId: 's1'
        }
      });
      const withUser = usersReducer(baseState, UsersActions.userJoined({ user: remoteUser }));
      const byOderId = usersReducer(withUser, UsersActions.userLeft({
        userId: 'db-id-5',
        serverId: 's1',
        connectedPeerIds: ['oder-5']
      }));
      const byPeerId = usersReducer(withUser, UsersActions.userLeft({
        userId: 'db-id-5',
        serverId: 's1',
        connectedPeerIds: ['peer-5']
      }));

      expect(byOderId.entities['db-id-5']?.voiceState?.isConnected).toBe(true);
      expect(byPeerId.entities['db-id-5']?.voiceState?.isConnected).toBe(true);
    });

    it('clears voice state on user_left when the peer transport is gone', () => {
      const remoteUser = createUser({
        id: 'u6',
        oderId: 'u6',
        displayName: 'Gone Peer',
        presenceServerIds: ['s1'],
        voiceState: {
          isConnected: true,
          isMuted: false,
          isDeafened: false,
          isSpeaking: true,
          roomId: 'voice-1',
          serverId: 's1'
        }
      });
      const withUser = usersReducer(baseState, UsersActions.userJoined({ user: remoteUser }));
      const state = usersReducer(withUser, UsersActions.userLeft({
        userId: 'u6',
        serverId: 's1',
        connectedPeerIds: []
      }));

      expect(state.entities['u6']?.presenceServerIds).toBeUndefined();
      expect(state.entities['u6']?.isOnline).toBe(false);
      expect(state.entities['u6']?.voiceState?.isConnected).toBe(false);
      expect(state.entities['u6']?.voiceState?.roomId).toBeUndefined();
    });
  });

  describe('manual status overrides auto idle', () => {
    it('manual DND is not overridden by auto status changes', () => {
      // Set DND
      let state = usersReducer(baseState, UsersActions.setManualStatus({ status: 'busy' }));

      expect(state.manualStatus).toBe('busy');
      expect(state.entities['user-1']?.status).toBe('busy');

      // Simulate auto status update attempt - reducer only allows changing via setManualStatus
      // (The service checks manualStatus before dispatching updateCurrentUser)
      state = usersReducer(state, UsersActions.updateCurrentUser({ updates: { status: 'away' } }));

      // updateCurrentUser would override, but the service prevents this when manual is set
      expect(state.entities['user-1']?.status).toBe('away');
      // This demonstrates the need for the service to check manualStatus first
      expect(state.manualStatus).toBe('busy');
    });
  });
});
