import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { SELECTED_ORGANIZATION_NUMBER_KEY } from '@msfa-constants/local-storage-keys';
import { UnsubscribeDirective } from '@msfa-directives/unsubscribe.directive';
import { environment } from '@msfa-environment';
import { EmployeeResponse } from '@msfa-models/api/employee.response.model';
import { OrganizationResponse } from '@msfa-models/api/organization.response.model';
import { UserInfoResponse } from '@msfa-models/api/user-info.response.model';
import { Employee, mapResponseToEmployee } from '@msfa-models/employee.model';
import { mapResponseToOrganization, Organization } from '@msfa-models/organization.model';
import { Role } from '@msfa-models/role.model';
import { mapResponseToUserInfo, UserInfo } from '@msfa-models/user-info.model';
import { BehaviorSubject, combineLatest, Observable } from 'rxjs';
import { filter, map, switchMap, tap } from 'rxjs/operators';
import { AuthenticationService } from './authentication.service';

@Injectable({
  providedIn: 'root',
})
export class UserService extends UnsubscribeDirective {
  private _apiAuthUrl = `${environment.api.url}/auth`;
  private _apiUserUrl = `${environment.api.url}/users/currentUser`;

  private _isLoggedIn$: Observable<boolean> = this.authenticationService.isLoggedIn$;
  private _organizations$ = new BehaviorSubject<Organization[]>(null);
  public organizations$: Observable<Organization[]> = this._organizations$.asObservable();
  private _user$ = new BehaviorSubject<Employee>(null);
  public user$: Observable<Employee> = this._user$.asObservable();
  private _userRolesLoading$ = new BehaviorSubject<boolean>(false);
  public userRolesLoading$: Observable<boolean> = this._userRolesLoading$.asObservable();
  private _userLoading$ = new BehaviorSubject<boolean>(false);
  public userLoading$: Observable<boolean> = this._userLoading$.asObservable();
  private _userRoles$ = new BehaviorSubject<Role[]>(null);
  public userRoles$: Observable<Role[]> = this._userRoles$.asObservable();
  private _selectedOrganizationNumber$ = new BehaviorSubject<string>(null);

  constructor(private httpClient: HttpClient, private authenticationService: AuthenticationService) {
    super();
    this._selectedOrganizationNumber$.next(this._selectedOrganizationNumber);
    super.unsubscribeOnDestroy(
      this._isLoggedIn$
        .pipe(
          filter(loggedIn => !!loggedIn),
          switchMap(() => this._fetchOrganizations$())
        )
        .subscribe(organizations => {
          this._organizations$.next(organizations);
        }),
      combineLatest([this._isLoggedIn$, this.selectedOrganization$])
        .pipe(
          filter(([loggedIn, selectedOrganization]) => !!(loggedIn && selectedOrganization)),
          tap(() => {
            this._userLoading$.next(true);
            this._userRolesLoading$.next(true);
          }),
          switchMap(() => combineLatest([this._fetchUserInfo$(), this._fetchCurrentUser$()]))
        )
        .subscribe(([userInfo, currentUser]) => {
          this._userRoles$.next(userInfo.roles);
          this._user$.next(currentUser);
          this._userLoading$.next(false);
          this._userRolesLoading$.next(false);
        })
    );
  }

  private _fetchOrganizations$(): Observable<Organization[]> {
    return this.httpClient.get<{ data: OrganizationResponse[] }>(`${this._apiAuthUrl}/organizations`).pipe(
      filter(response => !!response?.data),
      map(({ data }) => data.map(organization => mapResponseToOrganization(organization)))
    );
  }

  private _fetchUserInfo$(): Observable<UserInfo> {
    return this.httpClient.get<{ data: UserInfoResponse }>(`${this._apiAuthUrl}/userinfo`).pipe(
      filter(response => !!response?.data),
      map(({ data }) => mapResponseToUserInfo(data))
    );
  }

  private _fetchCurrentUser$(): Observable<Employee> {
    return this.httpClient.get<{ data: EmployeeResponse }>(`${this._apiUserUrl}`).pipe(
      filter(response => !!response?.data),
      map(({ data }) => mapResponseToEmployee(data))
    );
  }

  private get _selectedOrganizationNumber(): string | null {
    return localStorage.getItem(SELECTED_ORGANIZATION_NUMBER_KEY);
  }

  public get selectedOrganization$(): Observable<Organization | null> {
    return combineLatest([this._selectedOrganizationNumber$, this._organizations$]).pipe(
      filter(([, organizations]) => !!organizations?.length),
      map(([organizationNumber, organizations]) => {
        return organizationNumber
          ? organizations.find(organization => organization.organizationNumber === organizationNumber)
          : null;
      })
    );
  }

  public setSelectedOrganization(organization: Organization): void {
    localStorage.setItem(SELECTED_ORGANIZATION_NUMBER_KEY, organization.organizationNumber);
    this._selectedOrganizationNumber$.next(organization.organizationNumber);
  }
}
