import {TestBed} from '@angular/core/testing';
import {firstValueFrom, of} from 'rxjs';
import {UserService} from './user.service';
import {UserSessionService} from './user-session.service';
import {UserSongUsageService} from './user-song-usage.service';
import {ShowSongIndexService} from '../../modules/shows/services/show-song-index.service';

describe('UserService', () => {
  let service: UserService;
  let sessionSpy: jasmine.SpyObj<UserSessionService>;
  let songUsageSpy: jasmine.SpyObj<UserSongUsageService>;
  let showSongIndexSpy: jasmine.SpyObj<ShowSongIndexService>;

  beforeEach(async () => {
    sessionSpy = jasmine.createSpyObj<UserSessionService>(
      'UserSessionService',
      ['currentUser', 'getUserbyId', 'getUserbyId$', 'login', 'loggedIn$', 'list$', 'logout', 'update$', 'changePassword', 'createNewUser'],
      {
        users$: of([{id: 'user-1'}]) as never,
        userId$: of('user-1'),
        user$: of({id: 'user-1'}) as never,
      }
    );
    songUsageSpy = jasmine.createSpyObj<UserSongUsageService>('UserSongUsageService', ['incSongCount', 'decSongCount', 'rebuildSongUsage']);
    showSongIndexSpy = jasmine.createSpyObj<ShowSongIndexService>('ShowSongIndexService', ['rebuildShowSongIds']);

    sessionSpy.currentUser.and.resolveTo({id: 'user-1'} as never);
    sessionSpy.getUserbyId.and.resolveTo({id: 'user-2'} as never);
    sessionSpy.getUserbyId$.and.returnValue(of({id: 'user-2'}) as never);
    sessionSpy.login.and.resolveTo('user-1');
    sessionSpy.loggedIn$.and.returnValue(of(true));
    sessionSpy.list$.and.returnValue(of([{id: 'user-1'}]) as never);
    sessionSpy.logout.and.resolveTo();
    sessionSpy.update$.and.resolveTo();
    sessionSpy.changePassword.and.resolveTo();
    sessionSpy.createNewUser.and.resolveTo();
    songUsageSpy.incSongCount.and.resolveTo();
    songUsageSpy.decSongCount.and.resolveTo();
    songUsageSpy.rebuildSongUsage.and.resolveTo({usersProcessed: 1, showsProcessed: 2, showSongsProcessed: 3});
    showSongIndexSpy.rebuildShowSongIds.and.resolveTo({showsProcessed: 2, showSongsProcessed: 3});

    await TestBed.configureTestingModule({
      providers: [
        {provide: UserSessionService, useValue: sessionSpy},
        {provide: UserSongUsageService, useValue: songUsageSpy},
        {provide: ShowSongIndexService, useValue: showSongIndexSpy},
      ],
    });

    service = TestBed.inject(UserService);
  });

  it('should be created', () => {
    expect(service).toBeTruthy();
  });

  it('should expose the session streams directly', async () => {
    await expectAsync(firstValueFrom(service.userId$)).toBeResolvedTo('user-1');
    await expectAsync(firstValueFrom(service.user$)).toBeResolvedTo({id: 'user-1'} as never);
    await expectAsync(firstValueFrom(service.users$)).toBeResolvedTo([{id: 'user-1'}] as never);
  });

  it('should delegate session operations to UserSessionService', async () => {
    await expectAsync(service.currentUser()).toBeResolvedTo({id: 'user-1'} as never);
    await expectAsync(service.getUserbyId('user-2')).toBeResolvedTo({id: 'user-2'} as never);
    await expectAsync(service.login('mail', 'secret')).toBeResolvedTo('user-1');
    await service.logout();
    await service.update$('user-1', {name: 'Benjamin'} as never);
    await service.changePassword('mail');
    await service.createNewUser('mail', 'Benjamin', 'secret');

    expect(sessionSpy.currentUser).toHaveBeenCalled();
    expect(sessionSpy.getUserbyId).toHaveBeenCalledWith('user-2');
    expect(sessionSpy.login).toHaveBeenCalledWith('mail', 'secret');
    expect(sessionSpy.logout).toHaveBeenCalled();
    expect(sessionSpy.update$).toHaveBeenCalledWith('user-1', {name: 'Benjamin'} as never);
    expect(sessionSpy.changePassword).toHaveBeenCalledWith('mail');
    expect(sessionSpy.createNewUser).toHaveBeenCalledWith('mail', 'Benjamin', 'secret');
  });

  it('should delegate user lookup and loggedIn/list streams to UserSessionService', async () => {
    await expectAsync(firstValueFrom(service.getUserbyId$('user-2'))).toBeResolvedTo({id: 'user-2'} as never);
    await expectAsync(firstValueFrom(service.loggedIn$())).toBeResolvedTo(true);
    await expectAsync(firstValueFrom(service.list$())).toBeResolvedTo([{id: 'user-1'}] as never);
    expect(sessionSpy.getUserbyId$).toHaveBeenCalledWith('user-2');
    expect(sessionSpy.loggedIn$).toHaveBeenCalled();
    expect(sessionSpy.list$).toHaveBeenCalled();
  });

  it('should delegate song usage operations to UserSongUsageService', async () => {
    await service.incSongCount('song-1');
    await service.decSongCount('song-2');
    await expectAsync(service.rebuildSongUsage()).toBeResolvedTo({usersProcessed: 1, showsProcessed: 2, showSongsProcessed: 3});
    await expectAsync(service.rebuildShowSongIds()).toBeResolvedTo({showsProcessed: 2, showSongsProcessed: 3});

    expect(songUsageSpy.incSongCount).toHaveBeenCalledWith('song-1');
    expect(songUsageSpy.decSongCount).toHaveBeenCalledWith('song-2');
    expect(songUsageSpy.rebuildSongUsage).toHaveBeenCalled();
    expect(showSongIndexSpy.rebuildShowSongIds).toHaveBeenCalled();
  });
});
