import { FormSelectItem } from '@af/digi-ng/_form/form-select';
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { UnsubscribeDirective } from '@msfa-directives/unsubscribe.directive';
import { SortOrder } from '@msfa-enums/sort-order.enum';
import { environment } from '@msfa-environment';
import { AvropResponse } from '@msfa-models/api/avrop.response.model';
import { ContactInformationResponse } from '@msfa-models/api/contact-information.response.model';
import { DeltagareCompactApiResponse } from '@msfa-models/api/deltagare.response.model';
import { DisabilityResponse } from '@msfa-models/api/disability.response.model';
import { DriversLicenseResponse } from '@msfa-models/api/drivers-license.response.model';
import { EducationsResponse } from '@msfa-models/api/educations.response.model';
import { HighestEducationResponse } from '@msfa-models/api/highest-education.response.model';
import { ReportResponse } from '@msfa-models/api/report.response.model';
import { Params } from '@msfa-models/api/params.model';
import { TranslatorResponse } from '@msfa-models/api/translator.response.model';
import { WorkExperiencesResponse } from '@msfa-models/api/work-experiences.response.model';
import { WorkLanguagesResponse } from '@msfa-models/api/work-languages.response.model';
import { Avrop, mapAvropResponseToAvrop } from '@msfa-models/avrop.model';
import { ContactInformation, mapResponseToContactInformation } from '@msfa-models/contact-information.model';
import {
  Deltagare,
  DeltagareCompact,
  DeltagareCompactData,
  mapResponseToDeltagareCompact
} from '@msfa-models/deltagare.model';
import { Disability, mapResponseToDisability } from '@msfa-models/disability.model';
import { DriversLicense, mapResponseToDriversLicense } from '@msfa-models/drivers-license.model';
import { Education, mapResponseToEducation } from '@msfa-models/education.model';
import { errorToCustomError } from '@msfa-models/error/custom-error';
import { HighestEducation, mapResponseToHighestEducation } from '@msfa-models/highest-education.model';
import { mapReportsResponseToReport, ReportsData } from '@msfa-models/reports.model';
import { Sort } from '@msfa-models/sort.model';
import { mapResponseToWorkExperience, WorkExperience } from '@msfa-models/work-experience.model';
import { ErrorService } from '@msfa-services/error.service';
import { sortFromToDates } from '@msfa-utils/sort.util';
import { BehaviorSubject, combineLatest, Observable, of } from 'rxjs';
import { catchError, filter, map, switchMap } from 'rxjs/operators';

@Injectable({
  providedIn: 'root',
})
export class DeltagareService extends UnsubscribeDirective {
  private _apiBaseUrl = `${environment.api.url}/deltagare`;
  private _currentDeltagareId$ = new BehaviorSubject<string>(null);
  private _limit$ = new BehaviorSubject<number>(20);
  private _page$ = new BehaviorSubject<number>(1);
  private _sort$ = new BehaviorSubject<Sort<keyof DeltagareCompact>>({ key: 'fullName', order: SortOrder.ASC });
  public sort$: Observable<Sort<keyof DeltagareCompact>> = this._sort$.asObservable();
  private _reportType$ = new BehaviorSubject<FormSelectItem>(null);
  reportType$: Observable<FormSelectItem> = this._reportType$.asObservable();
  private _onlyMyDeltagare$ = new BehaviorSubject<boolean>(false);
  public onlyMyDeltagare$: Observable<boolean> = this._onlyMyDeltagare$.asObservable();

  constructor(private httpClient: HttpClient, private errorService: ErrorService) {
    super();
    super.unsubscribeOnDestroy(
      this._currentDeltagareId$
        .pipe(
          filter(currentDeltagareId => !!currentDeltagareId),
          switchMap(currentDeltagareId => this._fetchDeltagare$(currentDeltagareId))
        )
        .subscribe(deltagare => {
          this._deltagare$.next(deltagare);
        })
    );
  }

  private _deltagare$ = new BehaviorSubject<Deltagare>(null);
  public deltagare$: Observable<Deltagare> = this._deltagare$.asObservable();

  public allDeltagareData$: Observable<DeltagareCompactData> = combineLatest([
    this._limit$,
    this._page$,
    this._sort$,
    this._onlyMyDeltagare$,
  ]).pipe(
    switchMap(([limit, page, sort, onlyMyDeltagare]) => this._fetchAllDeltagare$(limit, page, sort, onlyMyDeltagare))
  );

  public reportsData$: Observable<ReportsData> = combineLatest([
    this._limit$,
    this._page$
  ]).pipe(switchMap(([limit, page]) => this._fetchReports$(limit, page)));

  public setSort(newSortKey: keyof DeltagareCompact): void {
    const currentSort = this._sort$.getValue();
    const order =
      currentSort.key === newSortKey && currentSort.order === SortOrder.ASC ? SortOrder.DESC : SortOrder.ASC;

    this._sort$.next({ key: newSortKey, order });
  }

  public setPage(page: number): void {
    this._page$.next(page);
  }

  private _fetchAllDeltagare$(
    limit: number,
    page: number,
    sort: Sort<keyof DeltagareCompact>,
    onlyMyDeltagare?: boolean
  ): Observable<DeltagareCompactData> {
    const params: Params = {
      sort: sort.key as string,
      order: sort.order as string,
      limit: limit.toString(),
      page: page.toString(),
    };

    if (onlyMyDeltagare) {
      params.onlyMyDeltagare = onlyMyDeltagare.toString();
    }
    return this.httpClient
      .get<DeltagareCompactApiResponse>(this._apiBaseUrl, {
        params,
      })
      .pipe(
        map(({ data, meta }) => {
          return { data: data.map(deltagare => mapResponseToDeltagareCompact(deltagare)), meta };
        })
      );
  }

  private _fetchReports$(
    limit: number,
    page: number
  ): Observable<ReportsData> {
    const params: { [param: string]: string | string[] } = {
      limit: limit.toString(),
      page: page.toString()
    };

    return this.httpClient
      .get<ReportResponse>(`${this._apiBaseUrl}/report`, {
        params
      })
      .pipe(
        map(({ data, meta }) => {
          data.sort((reportA, reportB) =>
            +reportA.sendDate < +reportB.sendDate ? 1 : -1)
          return { data: data.map(report => mapReportsResponseToReport(report)), meta };
        })
      );
  }

  public setReportType(reportType: FormSelectItem): void {
    this._reportType$.next(reportType);
  }

  public setCurrentDeltagareId(currentDeltagareId: string): void {
    this._deltagare$.next(null);
    this._currentDeltagareId$.next(currentDeltagareId);
  }

  public setOnlyMyDeltagare(value: boolean): void {
    this._onlyMyDeltagare$.next(value);
  }

  private _fetchContactInformation$(id: string): Observable<ContactInformation | Partial<ContactInformation>> {
    return this.httpClient.get<{ data: ContactInformationResponse }>(`${this._apiBaseUrl}/${id}/contact`).pipe(
      map(({ data }) => mapResponseToContactInformation(data)),
      catchError(error => {
        this.errorService.add(errorToCustomError(error));
        return of({});
      })
    );
  }

  private _fetchDriversLicense$(id: string): Observable<DriversLicense | Partial<DriversLicense>> {
    return this.httpClient.get<{ data: DriversLicenseResponse }>(`${this._apiBaseUrl}/${id}/driverlicense`).pipe(
      map(({ data }) => mapResponseToDriversLicense(data)),
      catchError(error => {
        this.errorService.add(errorToCustomError(error));
        return of({});
      })
    );
  }

  private _fetchHighestEducation$(id: string): Observable<HighestEducation | Partial<HighestEducation>> {
    return this.httpClient
      .get<{ data: HighestEducationResponse }>(`${this._apiBaseUrl}/${id}/educationlevels/highest`)
      .pipe(
        map(({ data }) => mapResponseToHighestEducation(data)),
        catchError(error => {
          this.errorService.add(errorToCustomError(error));
          return of({});
        })
      );
  }

  private _fetchEducations$(id: string): Observable<Education[]> {
    return this.httpClient.get<{ data: EducationsResponse }>(`${this._apiBaseUrl}/${id}/educations`).pipe(
      map(({ data }) =>
        data.utbildningar
          ? data.utbildningar.sort((a, b) =>
            sortFromToDates({ from: a.period_from, to: a.period_tom }, { from: b.period_from, to: b.period_tom })
          )
          : []
      ),
      map(educations => educations.map(utbildning => mapResponseToEducation(utbildning))),
      catchError(error => {
        this.errorService.add(errorToCustomError(error));
        return of([]);
      })
    );
  }

  private _fetchTranslator$(id: string): Observable<string> {
    return this.httpClient.get<{ data: TranslatorResponse }>(`${this._apiBaseUrl}/${id}/translator`).pipe(
      map(({ data }) => data.sprak?.beskrivning || null),
      catchError(error => {
        this.errorService.add(errorToCustomError(error));
        return of('');
      })
    );
  }

  private _fetchWorkLanguages$(id: string): Observable<string[]> {
    return this.httpClient.get<{ data: WorkLanguagesResponse }>(`${this._apiBaseUrl}/${id}/work/languages`).pipe(
      map(({ data }) => data?.sprak?.map(sprak => sprak.beskrivning) || []),
      catchError(error => {
        this.errorService.add(errorToCustomError(error));
        return of([]);
      })
    );
  }

  private _fetchDisabilities$(id: string): Observable<Disability[]> {
    return this.httpClient.get<{ data: DisabilityResponse[] }>(`${this._apiBaseUrl}/${id}/work/disabilities`).pipe(
      map(({ data }) => data?.map(funktionsnedsattning => mapResponseToDisability(funktionsnedsattning)) || []),
      catchError(error => {
        this.errorService.add(errorToCustomError(error));
        return of([]);
      })
    );
  }

  private _fetchWorkExperiences$(id: string): Observable<WorkExperience[]> {
    return this.httpClient.get<{ data: WorkExperiencesResponse }>(`${this._apiBaseUrl}/${id}/work/experiences`).pipe(
      map(
        ({ data }) =>
          data?.arbetslivserfarenheter?.sort((a, b) =>
            sortFromToDates({ from: a.period_from, to: a.period_tom }, { from: b.period_from, to: b.period_tom })
          ) || []
      ),
      map(workExperiences => workExperiences.map(erfarenhet => mapResponseToWorkExperience(erfarenhet))),
      catchError(error => {
        this.errorService.add(errorToCustomError(error));
        return of([]);
      })
    );
  }

  private _fetchAvropInformation$(id: string): Observable<Avrop | Partial<Avrop>> {
    return this.httpClient.get<{ data: AvropResponse }>(`${this._apiBaseUrl}/${id}/avrop`).pipe(
      map(({ data }) => (data ? mapAvropResponseToAvrop(data) : {})),
      catchError(error => {
        this.errorService.add(errorToCustomError(error));
        return of({});
      })
    );
  }

  // As TypeScript has some limitations regarding combining Observables this way,
  // we need to type it manually when exceeding 6 Observables inside a combineLatest.
  // Read: https://github.com/ReactiveX/rxjs/issues/3601#issuecomment-384711601
  private _fetchDeltagare$(id: string): Observable<Deltagare> {
    return combineLatest([
      this._fetchContactInformation$(id),
      this._fetchDriversLicense$(id),
      this._fetchHighestEducation$(id),
      this._fetchEducations$(id),
      this._fetchTranslator$(id),
      this._fetchWorkLanguages$(id),
      this._fetchDisabilities$(id),
      this._fetchWorkExperiences$(id),
      this._fetchAvropInformation$(id),
    ]).pipe(
      map(
        ([
          contactInformation,
          driversLicense,
          highestEducation,
          educations,
          translator,
          workLanguages,
          disabilities,
          workExperiences,
          avropInformation,
        ]: [
            ContactInformation,
            DriversLicense,
            HighestEducation,
            Education[],
            string,
            string[],
            Disability[],
            WorkExperience[],
            Avrop
          ]) => ({
            id,
            ...contactInformation,
            driversLicense,
            highestEducation,
            educations,
            translator,
            workLanguages,
            disabilities,
            workExperiences,
            avropInformation,
          })
      )
    );
  }
}
