import { Injectable } from '@angular/core';
import { AvropParams, Params } from '@msfa-models/api/params.model';
import { Avrop, AvropAndMeta } from '@msfa-models/avrop.model';
import { Handledare } from '@msfa-models/handledare.model';
import { AvropApiService } from '@msfa-services/api/avrop-api.service';
import { MultiselectFilterOption } from '@msfa-shared/components/multiselect/multiselect-filter-option';
import { BehaviorSubject, combineLatest, Observable, of } from 'rxjs';
import { distinctUntilChanged, filter, map, shareReplay, switchMap, tap } from 'rxjs/operators';
import { HandledareApiService } from './api/handledare.api.service';

type Step = 1 | 2 | 3 | 4;

const DEFAULT_PARAMS: AvropParams = {
  page: 1,
  limit: 5,
  filteredTjanster: null,
  filteredUtforandeVerksamheter: null,
  filteredKommuner: null,
};
@Injectable({
  providedIn: 'root',
})
export class AvropService {
  private _params$ = new BehaviorSubject<AvropParams>(DEFAULT_PARAMS);
  private _selectedAvrop$ = new BehaviorSubject<Avrop[]>([]);
  private _avropIsLocked$ = new BehaviorSubject<boolean>(null);
  private _selectedHandledareId$ = new BehaviorSubject<string>(null);
  private _handledareIsConfirmed$ = new BehaviorSubject<boolean>(false);
  private _avropIsSubmitted$ = new BehaviorSubject<boolean>(false);
  private _error$ = new BehaviorSubject<string>(null);
  private _avropLoading$ = new BehaviorSubject<boolean>(false);
  public avropLoading$: Observable<boolean> = this._avropLoading$.asObservable();
  public handledareIsConfirmed$: Observable<boolean> = this._handledareIsConfirmed$.asObservable();
  public avropIsSubmitted$: Observable<boolean> = this._avropIsSubmitted$.asObservable();
  public error$: Observable<string> = this._error$.asObservable();
  public showUnauthorizedError$: Observable<boolean> = this.avropApiService.showUnauthorizedError$;

  public filteredTjanster$: Observable<MultiselectFilterOption[]> = this._params$.pipe(
    map(({ filteredTjanster }) => filteredTjanster),
    distinctUntilChanged()
  );
  public filteredUtforandeVerksamheter$: Observable<MultiselectFilterOption[]> = this._params$.pipe(
    map(({ filteredUtforandeVerksamheter }) => filteredUtforandeVerksamheter),
    distinctUntilChanged((prevFilter, currFilter) => JSON.stringify(prevFilter) === JSON.stringify(currFilter))
  );
  public filteredKommuner$: Observable<MultiselectFilterOption[]> = this._params$.pipe(
    map(({ filteredKommuner }) => filteredKommuner),
    distinctUntilChanged()
  );
  public selectedAvrop$: Observable<Avrop[]> = this._selectedAvrop$.asObservable();
  public avropIsLocked$: Observable<boolean> = this._avropIsLocked$.asObservable();
  public selectedHandledare$: Observable<Handledare | null> = this._selectedHandledareId$.pipe(
    switchMap(handledareId =>
      handledareId
        ? this.availableHandledare$.pipe(
            map(availableHandledare => availableHandledare.find(handledare => handledare.ciamUserId === handledareId))
          )
        : of(null as null)
    )
  );
  public avropData$: Observable<AvropAndMeta> = combineLatest([this._avropIsSubmitted$, this._params$]).pipe(
    switchMap(() => {
      this._avropLoading$.next(true);
      return this.avropApiService.fetchAvrop$(this._filtersForAvrop).pipe(
        tap(() => {
          this._avropLoading$.next(false);
        })
      );
    })
  );

  public availableTjanster$: Observable<MultiselectFilterOption[]> = this._params$.pipe(
    distinctUntilChanged(
      (prevParams, currParams) =>
        prevParams.filteredKommuner === currParams.filteredKommuner &&
        prevParams.filteredUtforandeVerksamheter === currParams.filteredUtforandeVerksamheter
    ),
    switchMap(() => this.avropApiService.fetchAvailableTjanster$(this._filtersForTjanster)),
    shareReplay(1)
  );

  public availableUtforandeVerksamheter$: Observable<MultiselectFilterOption[]> = this._params$.pipe(
    distinctUntilChanged(
      (prevParams, currParams) =>
        prevParams.filteredKommuner === currParams.filteredKommuner &&
        prevParams.filteredTjanster === currParams.filteredTjanster
    ),
    switchMap(() => this.avropApiService.fetchAvailableUtforandeVerksamheter$(this._filtersForUtforandeVerksamheter)),
    shareReplay(1)
  );

  public availableKommuner$: Observable<MultiselectFilterOption[]> = this._params$.pipe(
    distinctUntilChanged(
      (prevParams, currParams) =>
        prevParams.filteredUtforandeVerksamheter === currParams.filteredUtforandeVerksamheter &&
        prevParams.filteredTjanster === currParams.filteredTjanster
    ),
    switchMap(() => this.avropApiService.fetchAvailableKommuner$(this._filtersForKommuner)),
    shareReplay(1)
  );

  private _lockedAvrop$: Observable<Avrop[]> = combineLatest([this.selectedAvrop$, this.avropIsLocked$]).pipe(
    map(([selectedAvrop, isLocked]) => (isLocked ? selectedAvrop : null))
  );

  public availableHandledare$: Observable<Handledare[]> = this._lockedAvrop$.pipe(
    filter(lockedAvrop => !!lockedAvrop?.length),
    map(lockedAvrop => lockedAvrop.map(avrop => avrop.id)),
    switchMap(lockedAvrop => this.handledareApiService.fetchAvailableHandledare$(lockedAvrop)),
    shareReplay(1)
  );

  public currentStep$: Observable<Step> = combineLatest([
    this._handledareIsConfirmed$,
    this._avropIsLocked$,
    this._avropIsSubmitted$,
  ]).pipe(
    map(([confirmedHandledare, avropIsLocked, avropIsSubmitted]) =>
      AvropService._getCalculatedStep(confirmedHandledare, avropIsLocked, avropIsSubmitted)
    ),
    tap(() => {
      window.scrollTo(0, 0);
    })
  );

  private static _getCalculatedStep(
    confirmedHandledare: boolean,
    avropIsLocked: boolean,
    avropIsSubmitted: boolean
  ): Step {
    if (avropIsLocked) {
      if (confirmedHandledare) {
        if (avropIsSubmitted) {
          return 4;
        }
        return 3;
      }
      return 2;
    }

    return 1;
  }

  private get _tjanstKoder(): { tjanstKoder: string[] } | null {
    const params = this._params$.getValue();
    return params.filteredTjanster?.length ? { tjanstKoder: params.filteredTjanster.map(tjanst => tjanst.id) } : null;
  }

  private get _kommunKoder(): { kommunKoder: string[] } | null {
    const params = this._params$.getValue();
    return params.filteredKommuner?.length ? { kommunKoder: params.filteredKommuner.map(kommun => kommun.id) } : null;
  }

  private get _utforandeverksamhetIds(): { utforandeverksamhetIds: string[] } | null {
    const params = this._params$.getValue();
    return params.filteredUtforandeVerksamheter?.length
      ? {
          utforandeverksamhetIds: params.filteredUtforandeVerksamheter.map(
            utforandeVerksamhet => utforandeVerksamhet.id
          ),
        }
      : null;
  }

  private get _filtersForAvrop(): Params {
    const { page, limit } = this._params$.getValue();
    return {
      ...this._tjanstKoder,
      ...this._kommunKoder,
      ...this._utforandeverksamhetIds,
      page: page.toString(),
      limit: limit.toString(),
    };
  }

  private get _filtersForTjanster(): Params {
    return {
      ...this._kommunKoder,
      ...this._utforandeverksamhetIds,
    };
  }

  private get _filtersForUtforandeVerksamheter(): Params {
    return {
      ...this._tjanstKoder,
      ...this._kommunKoder,
    };
  }

  private get _filtersForKommuner(): Params {
    return {
      ...this._tjanstKoder,
      ...this._utforandeverksamhetIds,
    };
  }

  private _resetParams(): void {
    this._params$.next(DEFAULT_PARAMS);
  }

  constructor(private avropApiService: AvropApiService, private handledareApiService: HandledareApiService) {}

  public setSelectedAvrop(deltagare: Avrop[]): void {
    this._selectedAvrop$.next(deltagare);
  }

  public resetError(): void {
    this._error$.next(null);
  }

  public lockSelectedAvrop(): void {
    this.resetError();
    if ((this._selectedAvrop$?.value?.length ?? -1) <= 0) {
      this._error$.next('För att låsa deltagare behöver några ha markerats först.');
      return;
    }
    this._avropIsLocked$.next(true);
  }

  public unlockSelectedAvrop(): void {
    this.resetError();
    this._avropIsLocked$.next(false);
  }

  public confirmHandledare(): void {
    this.resetError();
    if (!this._selectedHandledareId$?.value) {
      this._error$.next('För att kunna tilldela behövs en handledare väljas först.');
      return;
    }
    this._handledareIsConfirmed$.next(true);
  }

  public unconfirmHandledare(): void {
    this.resetError();
    this._selectedHandledareId$.next(null);
    this._handledareIsConfirmed$.next(false);
  }

  public async save(): Promise<void> {
    this.resetError();
    if (!this._handledareIsConfirmed$) {
      this._error$.next('Handledaren måste bekräftas innan avropet kan sparas');
      return;
    }

    if (!this._avropIsLocked$) {
      this._error$.next('Deltagarlistan måste låsas innan avropet kan sparas');
      return;
    }

    await this.avropApiService.assignHandledare(this._selectedAvrop$.value, this._selectedHandledareId$.value);
    this._avropIsSubmitted$.next(true);
  }

  public assignHandledare(handledareId: string): void {
    this.resetError();
    this._selectedHandledareId$.next(handledareId);
  }

  public setSelectedTjanster(selectedFilterOptions: MultiselectFilterOption[]): void {
    this._params$.next({ ...this._params$.getValue(), filteredTjanster: selectedFilterOptions, page: 1 });
  }

  public setSelectedUtforandeVerksamheter(selectedFilterOptions: MultiselectFilterOption[]): void {
    this._params$.next({ ...this._params$.getValue(), filteredUtforandeVerksamheter: selectedFilterOptions, page: 1 });
  }

  public setSelectedKommuner(selectedFilterOptions: MultiselectFilterOption[]): void {
    this._params$.next({ ...this._params$.getValue(), filteredKommuner: selectedFilterOptions, page: 1 });
  }

  public goToStep1(): void {
    this._selectedHandledareId$.next(null);
    this._selectedAvrop$.next([]);
    this._avropIsLocked$.next(false);
    this._handledareIsConfirmed$.next(false);
    this._avropIsSubmitted$.next(false);
    this._resetParams();
  }

  public removeKommun(kommunToRemove: MultiselectFilterOption): void {
    const params = this._params$.getValue();
    this.setSelectedKommuner(params.filteredKommuner.filter(selectedKommun => selectedKommun !== kommunToRemove));
  }

  public removeUtforandeVerksamhet(utforandeVerksamhetToRemove: MultiselectFilterOption): void {
    const params = this._params$.getValue();
    this.setSelectedUtforandeVerksamheter(
      params.filteredUtforandeVerksamheter.filter(
        selectedUtforandeVerksamhet => selectedUtforandeVerksamhet !== utforandeVerksamhetToRemove
      )
    );
  }

  public removeTjanst(tjanstToRemove: MultiselectFilterOption): void {
    const params = this._params$.getValue();
    this.setSelectedTjanster(params.filteredTjanster.filter(selectedTjanst => selectedTjanst !== tjanstToRemove));
  }

  public setPage(page: number): void {
    this._params$.next({ ...this._params$.getValue(), page });
  }
}
