import { DestroyRef, Directive, OnInit, inject } from '@angular/core';
import { PickupShelfDetailsStore, PickupShelfStore } from './store';
import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { Breadcrumb, BreadcrumbService } from '@core/breadcrumb';
import { take } from 'rxjs/operators';
import { NavigationRoute } from '@shared/services/navigation';
import {
  DBHOrderItemListItemDTO,
  KeyValueDTOOfStringAndString,
  ListResponseArgsOfDBHOrderItemListItemDTO,
} from '@generated/swagger/oms-api';
import { isEmpty } from 'lodash';
import { Observable } from 'rxjs';
import { RunCheckTrigger } from './trigger';
import { OrderItemsContext } from '@domain/oms';
import { ActionHandlerService } from './services/action-handler.service';
import { Config } from '@core/config';

export type GetNameForBreadcrumbData = {
  processId: number;
  queryParams: Record<string, string>;
};

export type GetPathForBreadcrumbData = {
  processId: number;
  queryParams: Record<string, string>;
};

export type GetNameAndPathForBreadcrumbData = GetNameForBreadcrumbData & GetPathForBreadcrumbData & { view: string };

/**
 * Enthält die gemeinsame Logik für die Suche und verbindet die Komponenten mit dem Store.
 */
@Directive()
export abstract class PickupShelfBaseComponent implements OnInit {
  protected destroyRef = inject(DestroyRef);

  protected listStore = inject(PickupShelfStore);

  protected detailsStore = inject(PickupShelfDetailsStore);

  protected router = inject(Router);

  protected activatedRoute = inject(ActivatedRoute);

  protected breadcrumbService = inject(BreadcrumbService);

  protected runCheckTrigger = inject(RunCheckTrigger);

  protected actionHandlerService = inject(ActionHandlerService);

  protected config = inject(Config);

  ngOnInit() {
    this.regsiterFetchListResponseHandler();

    this.router.events.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((event) => {
      if (event instanceof NavigationEnd) {
        this._runChecks();
      }
    });

    this.runCheckTrigger.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => {
      this._runChecks();
    });

    this._runChecks();
  }

  // der debounce soll verhindern, dass die breadcrumb zu oft aktualisiert,
  // besonders bei asynchronen calls kommt es sonst zu fehlern
  // @debounce(500)
  // Auskommentiert, da es zu anderen Problemen führt, siehe z.B. Ticket #4538 oder #4540
  // Ursprungsproblem des Tickets #4533 konnte anders gelöst werden, somit wird debounce hier nicht mehr benötigt
  private _runChecks() {
    const processId = this._checkAndUpdateProcessId();
    const queryParams = this._checkAndUpdateQueryParams();
    const view: string = this.activatedRoute.snapshot.queryParams.view ?? this.activatedRoute.snapshot.data.view;

    this._checkBreadcrumbs({ processId, queryParams, view });
  }

  private async _checkBreadcrumbs(data: GetNameAndPathForBreadcrumbData) {
    await this._removeAllBreadcrumbs();
    await this._chackAndUpdateMainBreadcrumb(data);
    await this._checkAndUpdateListBreadcrumb(data);
    await this._checkAndUpdateFilterBreadcrumb(data);
    await this._checkAndUpdateDetailBreadcrumb(data);
    await this._checkAndUpdateEditBreadcrumb(data);
    await this._checkAndUpdateHistoryBreadcrumb(data);
  }

  abstract getProcessId(): number;

  abstract getSection(): 'customer' | 'branch';

  /**
   * Checks if the processId changed and updates the store if necessary.
   */
  private _checkAndUpdateProcessId(): number | undefined {
    const processId = this.getProcessId();

    if (processId !== this.listStore.processId) {
      this.listStore.setProcessId(processId);
    }

    return processId;
  }

  private _checkAndUpdateQueryParams(): Record<string, string> {
    const queryParams = this.activatedRoute.snapshot.queryParams;

    if (isEmpty(queryParams)) {
      this.listStore.setQueryParams(undefined);
    } else {
      this.listStore.setQueryParams(queryParams);
    }

    return queryParams;
  }

  // Fix Ticket #4688 Navigation behaves different based on section PickUpShelfOut and PickUpShelfIn
  abstract regsiterFetchListResponseHandler(): void | Promise<void>;

  /**
   * Sucht die Breadcrumb anhand des Tags.
   * @param tag Der gesuchte Tag
   * @returns Breadcumb
   */
  private _getBreadcrumb(tag?: string) {
    return this.breadcrumbService
      .getBreadcrumbsByKeyAndTags$(
        this.listStore.processId,
        ['pickup-shelf', tag].filter((t) => !!t),
      )
      .pipe(takeUntilDestroyed(this.destroyRef), take(1))
      .toPromise();
  }

  private async _removeBreadcrumbs(breadcrumbs: Breadcrumb[]) {
    for (let crumb of breadcrumbs) {
      await this.breadcrumbService.removeBreadcrumb(crumb.id);
    }
  }

  abstract getNameForMainBreadcrumb(data: GetNameForBreadcrumbData): string | Promise<string>;

  abstract getPathForMainBreadcrumb(
    data: GetPathForBreadcrumbData,
  ): NavigationRoute | null | Promise<NavigationRoute | null>;

  private async _removeAllBreadcrumbs() {
    const crumbs = await this._getBreadcrumb();
    await this._removeBreadcrumbs(crumbs);
  }

  private async _chackAndUpdateMainBreadcrumb({ processId, queryParams, view }: GetNameAndPathForBreadcrumbData) {
    if (view === 'reservation') {
      await this._checkAndUpdateReservationBreadcrumb({ processId, queryParams, view });
      return;
    } else if (view === 'cleanup') {
      await this._checkAndUpdateCleanupBreadcrumb({ processId, queryParams, view });
      return;
    } else if (view === 'wareneingangsliste') {
      await this._checkAndUpdateWareneingangsListeBreadcrumb({ processId, queryParams, view });
      return;
    } else if (view === 'remission') {
      await this._checkAndUpdateAbholfachremissionsvorschauBreadcrumb({ processId, queryParams, view });
      return;
    }

    const breadcrumbs = await this._getBreadcrumb('main');
    let mainBreadcrumb: Breadcrumb = breadcrumbs.pop();
    const name = await this.getNameForMainBreadcrumb({ processId, queryParams });
    const path = await this.getPathForMainBreadcrumb({ processId, queryParams });

    if (!path) {
      return;
    }

    if (!mainBreadcrumb) {
      mainBreadcrumb = {
        key: this.listStore.processId,
        name,
        tags: ['pickup-shelf', 'main'],
        path: path?.path,
        section: this.getSection(),
        params: { ...queryParams, ...path?.queryParams },
      };
      this.breadcrumbService.addBreadcrumb(mainBreadcrumb);
    } else {
      const changes: Partial<Breadcrumb> = {
        name,
        path: path?.path,
        params: { ...queryParams, ...path?.queryParams },
      };
      this.breadcrumbService.patchBreadcrumb(mainBreadcrumb.id, changes);
    }
  }

  private async _checkAndUpdateReservationBreadcrumb({ processId, queryParams }: GetNameAndPathForBreadcrumbData) {
    const breadcrumbs = await this._getBreadcrumb('reservation');
    let reservationBreadcrumb: Breadcrumb = breadcrumbs.pop();

    const name = 'Reservierungen';
    const path: NavigationRoute = {
      path: ['/filiale', 'goods', 'in', 'reservation'],
      queryParams: { view: 'reservation' },
      urlTree: this.router.createUrlTree(['/filiale', 'goods', 'in', 'reservation'], {}),
    };

    if (!reservationBreadcrumb) {
      reservationBreadcrumb = {
        key: processId,
        name,
        tags: ['pickup-shelf', 'reservation'],
        path: path?.path,
        section: this.getSection(),
        params: { ...queryParams, ...path?.queryParams },
      };
      this.breadcrumbService.addBreadcrumb(reservationBreadcrumb);
    } else {
      const changes: Partial<Breadcrumb> = {
        name,
        path: path?.path,
        params: { ...queryParams, ...path?.queryParams },
      };
      this.breadcrumbService.patchBreadcrumb(reservationBreadcrumb.id, changes);
    }

    await this._removeBreadcrumbs(breadcrumbs);
  }

  private async _checkAndUpdateCleanupBreadcrumb({ processId, queryParams }: GetNameAndPathForBreadcrumbData) {
    const breadcrumbs = await this._getBreadcrumb('cleanup');
    let cleanupBreadcrumb: Breadcrumb = breadcrumbs.pop();

    const name = 'Abholfachbereinigungsliste';
    const path: NavigationRoute = {
      path: ['/filiale', 'goods', 'in', 'cleanup'],
      queryParams: { view: 'cleanup' },
      urlTree: this.router.createUrlTree(['/filiale', 'goods', 'in', 'cleanup'], {}),
    };

    if (!cleanupBreadcrumb) {
      cleanupBreadcrumb = {
        key: processId,
        name,
        tags: ['pickup-shelf', 'cleanup'],
        path: path?.path,
        section: this.getSection(),
        params: { ...queryParams, ...path?.queryParams },
      };
      this.breadcrumbService.addBreadcrumb(cleanupBreadcrumb);
    } else {
      const changes: Partial<Breadcrumb> = {
        name,
        path: path?.path,
        params: { ...queryParams, ...path?.queryParams },
      };
      this.breadcrumbService.patchBreadcrumb(cleanupBreadcrumb.id, changes);
    }

    await this._removeBreadcrumbs(breadcrumbs);
  }

  private async _checkAndUpdateWareneingangsListeBreadcrumb({
    processId,
    queryParams,
  }: GetNameAndPathForBreadcrumbData) {
    const breadcrumbs = await this._getBreadcrumb('wareneingangsliste');
    let listBreadcrumb: Breadcrumb = breadcrumbs.pop();

    const name = 'Wareneingangsliste';
    const path: NavigationRoute = {
      path: ['/filiale', 'goods', 'in', 'list'],
      queryParams: { view: 'wareneingangsliste' },
      urlTree: this.router.createUrlTree(['/filiale', 'goods', 'in', 'list'], {}),
    };

    if (!listBreadcrumb) {
      listBreadcrumb = {
        key: processId,
        name,
        tags: ['pickup-shelf', 'wareneingangsliste'],
        path: path?.path,
        section: this.getSection(),
        params: { ...queryParams, ...path?.queryParams },
      };
      this.breadcrumbService.addBreadcrumb(listBreadcrumb);
    } else {
      const changes: Partial<Breadcrumb> = {
        name,
        path: path?.path,
        params: { ...queryParams, ...path?.queryParams },
      };
      this.breadcrumbService.patchBreadcrumb(listBreadcrumb.id, changes);
    }

    await this._removeBreadcrumbs(breadcrumbs);
  }

  private async _checkAndUpdateAbholfachremissionsvorschauBreadcrumb({
    processId,
    queryParams,
  }: GetNameAndPathForBreadcrumbData) {
    const breadcrumbs = await this._getBreadcrumb('preview');
    let listBreadcrumb: Breadcrumb = breadcrumbs.pop();

    const name = 'Abholfachremissionsvorschau';
    const path: NavigationRoute = {
      path: ['/filiale', 'goods', 'in', 'preview'],
      queryParams: { view: 'remission' },
      urlTree: this.router.createUrlTree(['/filiale', 'goods', 'in', 'preview'], {}),
    };

    if (!listBreadcrumb) {
      listBreadcrumb = {
        key: processId,
        name,
        tags: ['pickup-shelf', 'preview'],
        path: path?.path,
        section: this.getSection(),
        params: { ...queryParams, ...path?.queryParams },
      };
      this.breadcrumbService.addBreadcrumb(listBreadcrumb);
    } else {
      const changes: Partial<Breadcrumb> = {
        name,
        path: path?.path,
        params: { ...queryParams, ...path?.queryParams },
      };
      this.breadcrumbService.patchBreadcrumb(listBreadcrumb.id, changes);
    }

    await this._removeBreadcrumbs(breadcrumbs);
  }

  abstract getNameForListBreadcrumb(data: GetNameForBreadcrumbData): string | Promise<string>;

  abstract getPathFoListBreadcrumb(
    data: GetPathForBreadcrumbData,
  ): NavigationRoute | null | Promise<NavigationRoute | null>;

  private async _checkAndUpdateListBreadcrumb({ processId, queryParams, view }: GetNameAndPathForBreadcrumbData) {
    const breadcrumbs = await this._getBreadcrumb('list');
    let listBreadcrumb: Breadcrumb = breadcrumbs.pop();

    const shouldHaveBreadcrumb = ['list', 'filter', 'details', 'edit', 'history'].includes(view);

    if (shouldHaveBreadcrumb) {
      const name = await this.getNameForListBreadcrumb({ processId, queryParams });
      const path = await this.getPathFoListBreadcrumb({ processId, queryParams });

      if (!path) {
        return;
      }

      listBreadcrumb = {
        key: processId,
        name,
        tags: ['pickup-shelf', 'list'],
        path: path?.path,
        section: this.getSection(),
        params: { ...queryParams, ...path?.queryParams },
      };
      this.breadcrumbService.addOrUpdateBreadcrumbIfNotExists(listBreadcrumb);
    } else if (listBreadcrumb) {
      this.breadcrumbService.removeBreadcrumb(listBreadcrumb.id);
    }

    this._removeBreadcrumbs(breadcrumbs);
  }

  abstract getNameForFilterBreadcrumb(data: GetNameForBreadcrumbData): string | Promise<string>;

  abstract getPathForFilter(data: GetPathForBreadcrumbData): NavigationRoute | null | Promise<NavigationRoute | null>;

  private async _checkAndUpdateFilterBreadcrumb({ processId, queryParams, view }: GetNameAndPathForBreadcrumbData) {
    const breadcrumbs = await this._getBreadcrumb('filter');
    let filterBreadcrumb: Breadcrumb = breadcrumbs.pop();

    const shouldHaveBreadcrumb = ['filter'].includes(view);

    if (shouldHaveBreadcrumb) {
      const name = await this.getNameForFilterBreadcrumb({ processId, queryParams });
      const path = await this.getPathForFilter({ processId, queryParams });

      if (!path) {
        return;
      }

      filterBreadcrumb = {
        key: processId,
        name,
        tags: ['pickup-shelf', 'filter'],
        path: path?.path,
        section: this.getSection(),
        params: { ...queryParams, ...path?.queryParams },
      };
      this.breadcrumbService.addOrUpdateBreadcrumbIfNotExists(filterBreadcrumb);
    } else if (filterBreadcrumb) {
      this.breadcrumbService.removeBreadcrumb(filterBreadcrumb.id);
    }

    this._removeBreadcrumbs(breadcrumbs);
  }

  abstract getNameForDetailBreadcrumb(data: GetNameForBreadcrumbData): string | Promise<string>;

  abstract getPathForDetail(item: DBHOrderItemListItemDTO): Observable<NavigationRoute>;

  abstract getPathForDetailForDetailBreadcrumb(
    data: GetPathForBreadcrumbData,
  ): NavigationRoute | null | Promise<NavigationRoute | null>;

  private async _checkAndUpdateDetailBreadcrumb({ processId, queryParams, view }: GetNameAndPathForBreadcrumbData) {
    const breadcrumbs = await this._getBreadcrumb('details');
    let detailBreadcrumb: Breadcrumb = breadcrumbs.pop();

    const shouldHaveBreadcrumb = [
      'details',
      'edit',
      'history',
      'reservation',
      'cleanup',
      'wareneingangsliste',
      'remission',
    ].includes(view);

    if (shouldHaveBreadcrumb) {
      const name = await this.getNameForDetailBreadcrumb({ processId, queryParams });
      const path = await this.getPathForDetailForDetailBreadcrumb({ processId, queryParams });

      if (!path) {
        return;
      }

      detailBreadcrumb = {
        key: processId,
        name,
        tags: ['pickup-shelf', 'details'],
        path: path?.path,
        section: this.getSection(),
        params: { ...queryParams, ...path?.queryParams },
      };
      this.breadcrumbService.addOrUpdateBreadcrumbIfNotExists(detailBreadcrumb);
    } else if (detailBreadcrumb) {
      this.breadcrumbService.removeBreadcrumb(detailBreadcrumb.id);
    }

    this._removeBreadcrumbs(breadcrumbs);
  }

  abstract getPathForEdit(item: DBHOrderItemListItemDTO): Observable<NavigationRoute>;

  abstract getPathForEditBreadcrumb(
    data: GetPathForBreadcrumbData,
  ): NavigationRoute | null | Promise<NavigationRoute | null>;

  abstract getNameForEditBreadcrumb(data: GetNameForBreadcrumbData): string | Promise<string>;

  private async _checkAndUpdateEditBreadcrumb({ processId, queryParams, view }: GetNameAndPathForBreadcrumbData) {
    const breadcrumbs = await this._getBreadcrumb('edit');
    let editBreadcrumb: Breadcrumb = breadcrumbs.pop();

    const shouldHaveBreadcrumb = ['edit'].includes(view);

    if (shouldHaveBreadcrumb) {
      const name = await this.getNameForEditBreadcrumb({ processId, queryParams });
      const path = await this.getPathForEditBreadcrumb({ processId, queryParams });

      if (!path) {
        return;
      }

      editBreadcrumb = {
        key: processId,
        name,
        tags: ['pickup-shelf', 'edit'],
        path: path?.path,
        section: this.getSection(),
        params: { ...queryParams, ...path?.queryParams },
      };
      this.breadcrumbService.addOrUpdateBreadcrumbIfNotExists(editBreadcrumb);
    } else if (editBreadcrumb) {
      this.breadcrumbService.removeBreadcrumb(editBreadcrumb.id);
    }

    this._removeBreadcrumbs(breadcrumbs);
  }

  abstract getPathForHistory(item: DBHOrderItemListItemDTO): Observable<NavigationRoute>;

  abstract getPathForHistoryBreadcrumb(
    data: GetPathForBreadcrumbData,
  ): NavigationRoute | null | Promise<NavigationRoute | null>;

  abstract getNameForHistoryBreadcrumb(data: GetNameForBreadcrumbData): string | Promise<string>;

  private async _checkAndUpdateHistoryBreadcrumb({ processId, queryParams, view }: GetNameAndPathForBreadcrumbData) {
    const breadcrumbs = await this._getBreadcrumb('history');
    let historyBreadcrumb: Breadcrumb = breadcrumbs.pop();

    const shouldHaveBreadcrumb = ['history'].includes(view);

    if (shouldHaveBreadcrumb) {
      const name = await this.getNameForHistoryBreadcrumb({ processId, queryParams });
      const path = await this.getPathForHistoryBreadcrumb({ processId, queryParams });

      if (!path) {
        return;
      }

      historyBreadcrumb = {
        key: processId,
        name,
        tags: ['pickup-shelf', 'history'],
        path: path?.path,
        section: this.getSection(),
        params: { ...queryParams, ...path?.queryParams },
      };
      this.breadcrumbService.addOrUpdateBreadcrumbIfNotExists(historyBreadcrumb);
    } else if (historyBreadcrumb) {
      this.breadcrumbService.removeBreadcrumb(historyBreadcrumb.id);
    }

    this._removeBreadcrumbs(breadcrumbs);
  }

  execAction(action: KeyValueDTOOfStringAndString): Promise<OrderItemsContext> {
    return this.actionHandlerService.handle({
      action,
      items: this.listStore.selectedListItems,
    });
  }
}
