import { Injectable } from '@angular/core';
import { DomainTaskCalendarService } from '@domain/task-calendar';
import { ComponentStore, tapResponse } from '@ngrx/component-store';
import { DisplayInfoDTO, InputDTO, QuerySettingsDTO, ResponseArgsOfIEnumerableOfInputDTO } from '@swagger/eis';
import { CalendarIndicator } from '@ui/calendar';
import { DateAdapter } from '@ui/common';
import { Filter, FilterOption, SelectFilter, UiFilter, UiFilterMappingService } from '@ui/filter';
import { UiMessageModalComponent, UiModalRef, UiModalResult, UiModalService } from '@ui/modal';
import { clone } from 'lodash';
import { Observable } from 'rxjs';
import { debounceTime, map, switchMap, tap, withLatestFrom } from 'rxjs/operators';
import { InfoModalComponent } from './modals/info/info-modal.component';
import { PreInfoModalComponent } from './modals/preinfo/preinfo-modal.component';
import { TaskModalComponent } from './modals/task/task-modal.component';

export interface TaskCalendarState {
  mode: 'week' | 'month';
  selectedDate: Date;
  displayedDate: Date;
  displayInfos: DisplayInfoDTO[];
  initialFilter: UiFilter;
  filter: UiFilter;
  message: string;
  fetching: boolean;
}

@Injectable()
export class TaskCalendarStore extends ComponentStore<TaskCalendarState> {
  readonly selectSelectedDate = this.select((s) => s.selectedDate);

  readonly selectDisplayedDate = this.select((s) => s.displayedDate);

  readonly selectSelectedYear = this.select(this.selectSelectedDate, (selectedDate) => this.dateAdapter.getYear(selectedDate));

  readonly selectSelectedMonth = this.select(this.selectSelectedDate, (selectedDate) => this.dateAdapter.getMonth(selectedDate));

  readonly selectedCalendarWeek = this.select(this.selectSelectedDate, (selectedDate) => this.dateAdapter.getCalendarWeek(selectedDate));

  readonly selectDisplayInfos = this.select((s) => s.displayInfos);

  readonly selectCalendarIndicators = this.select(this.selectDisplayInfos, (displayItems) =>
    displayItems.reduce<CalendarIndicator[]>((agg, item) => {
      const calendarIndicator = this.mapDisplayInfoToCalendarIndicator(item);
      if (
        !agg.some(
          (s) =>
            this.dateAdapter.equals({ first: s.date, second: new Date(calendarIndicator.date), precision: 'day' }) &&
            s.color === calendarIndicator.color
        ) &&
        !item.successor // do not show color indicator for items with successor
      ) {
        return [...agg, calendarIndicator];
      }
      return agg;
    }, [])
  );

  readonly selectInitialFilter = this.select((s) => s.initialFilter);

  readonly selectFilter = this.select((s) => s.filter);

  readonly selectFetching = this.select((s) => s.fetching);

  readonly selectMessage = this.select((s) => s.message);

  readonly selectStartStop = this.select((s) => {
    const { mode, displayedDate } = s;

    if (mode === 'week') {
      const fdow = this.dateAdapter.getFirstDateOfWeek(displayedDate);
      const fdowWithOffset = this.dateAdapter.addCalendarDays(fdow, -7);
      const ldow = this.dateAdapter.getLastDateOfWeek(displayedDate);
      const ldowWithOffset = this.dateAdapter.addCalendarDays(ldow, 7);

      return {
        start: fdowWithOffset,
        stop: ldowWithOffset,
      };
    } else {
      const fdom = this.dateAdapter.getFirstDateOfMonth(displayedDate);
      const fdomWithOffset = this.dateAdapter.addCalendarDays(fdom, -7);
      const ldom = this.dateAdapter.getLastDateOfMonth(displayedDate);
      const ldomWithOffset = this.dateAdapter.addCalendarDays(ldom, 7);
      return {
        start: fdomWithOffset,
        stop: ldomWithOffset,
      };
    }
  });

  constructor(
    private dateAdapter: DateAdapter,
    private domainTaskCalendarService: DomainTaskCalendarService,
    private uiModal: UiModalService,
    private uiFilterMappingService: UiFilterMappingService
  ) {
    super({
      mode: 'month',
      selectedDate: dateAdapter.today(),
      displayedDate: dateAdapter.today(),
      displayInfos: [],
      initialFilter: undefined,
      filter: undefined,
      message: undefined,
      fetching: false,
    });
  }

  readonly setSelectedDate = this.updater((s, { date }: { date: Date }) => ({
    ...s,
    selectedDate: date,
  }));

  readonly setDisplayedDate = this.updater((s, { date }: { date: Date }) => ({
    ...s,
    displayedDate: date,
  }));

  readonly setFilter = this.updater((s, { filters }: { filters: UiFilter }) => ({
    ...s,
    filter: filters,
  }));

  readonly setMode = this.updater((s, { mode }: { mode: 'week' | 'month' }) => ({
    ...s,
    mode,
  }));

  readonly loadItems = this.effect(($: Observable<void>) =>
    $.pipe(
      tap(() => this.patchState({ fetching: true })),
      debounceTime(500),
      withLatestFrom(this.domainTaskCalendarService.currentBranchId$, this.selectStartStop, this.selectFilter),
      switchMap(([_, branchId, date, filter]) => {
        const querytoken = { ...filter?.getQueryToken() };
        return this.domainTaskCalendarService
          .getInfos({
            ...querytoken,
            filter: {
              ...querytoken?.filter,
              branch_id: String(branchId),
              timespan: `"${date?.start?.toISOString()}"-"${date.stop?.toISOString()}"`,
            },
          })
          .pipe(
            tapResponse(
              (response) => {
                if (!response.error) {
                  const preInfos = response.result.filter((info) => this.domainTaskCalendarService.getInfoType(info) === 'PreInfo');
                  preInfos.forEach((info) => {
                    const preInfoTask = clone(info);
                    delete preInfoTask.publicationDate;
                    response.result.push(preInfoTask);
                  });

                  this.patchState({ displayInfos: response.result, fetching: false, message: undefined });
                } else {
                  this.uiModal.open({
                    content: UiMessageModalComponent,
                    data: response,
                  });
                  this.patchState({ displayInfos: [], fetching: false, message: 'Keine Suchergebnisse' });
                }
              },
              (error) => {
                console.error(error);
                this.patchState({ displayInfos: [], fetching: false, message: 'Keine Suchergebnisse' });
              }
            )
          );
      })
    )
  );

  readonly loadFilter = this.effect(($: Observable<void>) =>
    $.pipe(
      tap(() => this.patchState({ fetching: true })),
      switchMap((_) => this.domainTaskCalendarService.getSettings()),
      tapResponse(
        (response: QuerySettingsDTO) => {
          this.patchState({ filter: UiFilter.create(response), initialFilter: UiFilter.create(response), fetching: false });
        },
        (error) => {
          console.error(error);
          this.patchState({ filter: undefined, initialFilter: undefined, fetching: false });
        }
      )
    )
  );

  open(displayInfoDTO: DisplayInfoDTO) {
    const type = this.domainTaskCalendarService.getInfoType(displayInfoDTO);
    let taskModalRef: UiModalRef<{ successorId: number }, DisplayInfoDTO>;

    switch (type) {
      case 'Info':
        taskModalRef = this.uiModal.open({
          content: InfoModalComponent,
          data: displayInfoDTO,
        });
        break;

      case 'PreInfo':
        taskModalRef = this.uiModal.open({
          content: PreInfoModalComponent,
          data: displayInfoDTO,
        });
        break;

      case 'Task':
        taskModalRef = this.uiModal.open({
          content: TaskModalComponent,
          data: displayInfoDTO,
        });
        break;
    }

    taskModalRef?.afterClosed$.subscribe({
      next: async (result: UiModalResult<{ successorId: number }>) => {
        if (result.data?.successorId) {
          const info = await this.domainTaskCalendarService
            .getInfoById({ infoId: result.data.successorId })
            .pipe(map((res) => res.result))
            .toPromise();
          if (info) {
            this.open(info);
          }
        }
      },
      complete: () => this.loadItems(),
    });
  }

  mapInputArrayToFilterArray(source: InputDTO[]): SelectFilter[] {
    const mappedArr = source.map((input) => {
      return this.uiFilterMappingService.fromInputDto(input as any);
    });
    return mappedArr;
  }

  mapFilterArrayToStringDictionary(source: Filter[]): { [key: string]: string } {
    const dict: { [key: string]: string } = {};
    for (const filter of source) {
      const options: FilterOption[] = filter.options;
      const selected = options.filter((o) => o.selected);
      if (selected.length > 0) {
        dict[filter.key] = selected.map((o) => o.id).join(';');
      }
    }
    return dict;
  }

  mapDisplayInfoToCalendarIndicator(source: DisplayInfoDTO): CalendarIndicator {
    return {
      date: this.domainTaskCalendarService.getDisplayInfoDate(source),
      color: this.domainTaskCalendarService.getProcessingStatusColorCode(source),
    };
  }
}
