import {
  Component,
  ChangeDetectionStrategy,
  OnInit,
  OnDestroy,
  ElementRef,
  ViewChild,
  inject,
} from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { ApplicationService } from '@core/application';
import { DomainPrinterService } from '@domain/printer';
import { ItemDTO as PrinterItemDTO } from '@generated/swagger/print-api';
import { PrintModalComponent, PrintModalData } from '@modal/printer';
import { BranchDTO } from '@generated/swagger/checkout-api';
import { UiModalService } from '@ui/modal';
import { ModalReviewsComponent } from '@modal/reviews';
import {
  BehaviorSubject,
  combineLatest,
  firstValueFrom,
  Subscription,
} from 'rxjs';
import {
  debounceTime,
  filter,
  first,
  map,
  shareReplay,
  switchMap,
  tap,
  withLatestFrom,
} from 'rxjs/operators';
import { ArticleDetailsStore } from './article-details.store';
import { ModalImagesComponent } from '@modal/images';
import { ProductImageService } from '@cdn/product-image';
import { ModalAvailabilitiesComponent } from '@modal/availabilities';
import { slideYAnimation } from './slide.animation';
import { BreadcrumbService } from '@core/breadcrumb';
import { ItemDTO } from '@generated/swagger/cat-search-api';
import { DateAdapter } from '@ui/common';
import { DatePipe } from '@angular/common';
import { PurchaseOptionsModalService } from '@modal/purchase-options';
import { EnvironmentService } from '@core/environment';
import {
  CheckoutNavigationService,
  ProductCatalogNavigationService,
  CustomerSearchNavigation,
} from '@shared/services/navigation';
import { DomainCheckoutService } from '@domain/checkout';
import { Store } from '@ngrx/store';
import moment, { Moment } from 'moment';
import {
  NavigateAfterRewardSelection,
  RewardSelectionPopUpService,
} from '@isa/checkout/shared/reward-selection-dialog';

@Component({
  selector: 'page-article-details',
  templateUrl: 'article-details.component.html',
  styleUrls: ['article-details.component.scss'],
  changeDetection: ChangeDetectionStrategy.Default,
  providers: [ArticleDetailsStore, DatePipe],
  animations: [slideYAnimation],
  standalone: false,
})
export class ArticleDetailsComponent implements OnInit, OnDestroy {
  private _rewardSelectionPopUpService = inject(RewardSelectionPopUpService);
  private _customerSearchNavigation = inject(CustomerSearchNavigation);

  private readonly subscriptions = new Subscription();
  showRecommendations: boolean;

  imageLoaded$ = new BehaviorSubject<boolean>(false);

  fetchingAvailabilities$ = combineLatest([
    this.store.fetchingDeliveryAvailability$,
    this.store.fetchingDeliveryB2BAvailability$,
    this.store.fetchingDeliveryDigAvailability$,
    this.store.fetchingDownloadAvailability$,
    this.store.fetchingPickUpAvailability$,
    this.store.fetchingTakeAwayAvailability$,
  ]).pipe(map((values) => values.some((v) => v)));

  isAvailable$ = combineLatest([
    this.store.isDeliveryAvailabilityAvailable$,
    this.store.isDeliveryDigAvailabilityAvailable$,
    this.store.isDeliveryB2BAvailabilityAvailable$,
    this.store.isPickUpAvailabilityAvailable$,
    this.store.isTakeAwayAvailabilityAvailable$,
    this.store.isDownloadAvailabilityAvailable$,
  ]).pipe(
    map((values) => values.some((v) => v)),
    shareReplay(),
  );

  showDeliveryTruck$ = combineLatest([
    this.store.isDeliveryAvailabilityAvailable$,
    this.store.isDeliveryDigAvailabilityAvailable$,
  ]).pipe(map(([delivery, digDelivery]) => delivery || digDelivery));

  showDeliveryB2BTruck$ = combineLatest([
    this.store.isDeliveryDigAvailabilityAvailable$,
    this.store.isDeliveryB2BAvailabilityAvailable$,
  ]).pipe(map(([digDelivery, b2bDelivery]) => b2bDelivery && !digDelivery));

  customerFeatures$ = this.applicationService.activatedProcessId$.pipe(
    switchMap((processId) =>
      this._domainCheckoutService.getCustomerFeatures({ processId }),
    ),
  );

  showSubscriptionBadge$ = this.store.item$.pipe(
    map((item) => item?.features?.find((i) => i.key === 'PFO')),
  );

  hasPromotionFeature$ = this.store.item$.pipe(
    map((item) => !!item?.features?.find((i) => i.key === 'Promotion')),
  );
  promotionPoints$ = this.store.item$.pipe(
    map((item) => item?.redemptionPoints),
  );

  showPromotionBadge$ = combineLatest([
    this.hasPromotionFeature$,
    this.promotionPoints$,
  ]).pipe(
    map(
      ([hasPromotionFeature, promotionPoints]) =>
        hasPromotionFeature && promotionPoints > 0,
    ),
  );

  showArchivBadge$ = this.store.item$.pipe(
    map((item) => item?.features?.find((i) => i.key === 'ARC')),
  );

  isBadgeVisible$ = combineLatest([
    this.showSubscriptionBadge$,
    this.showPromotionBadge$,
    this.showArchivBadge$,
  ]).pipe(
    map(
      ([showSubscriptionBadge, showPromotionBadge, showArchivBadge]) =>
        showSubscriptionBadge || showPromotionBadge || showArchivBadge,
    ),
  );

  contributors$ = this.store.item$.pipe(
    map((item) => item?.product?.contributors?.split(';').map((m) => m.trim())),
  );

  /**
   * Observable that emits the formatted publication date of an item.
   *
   * This observable filters the items to ensure they have a valid publication date,
   * then maps the item to the appropriate date format. It considers both the
   * `firstDayOfSale` and `publicationDate` fields, choosing the later of the two
   * if both are valid. If neither date is valid, it returns an empty string.
   *
   * The date is formatted as 'DD. MMMM YYYY'.
   *
   * Cases:
   * - If both `firstDayOfSale` and `publicationDate` are valid, the later date is taken.
   * - If only `firstDayOfSale` is valid, it is taken.
   * - If only `publicationDate` is valid, it is taken.
   * - If neither date is valid, an empty string is returned.
   *
   * @observable
   * @returns {Observable<string>} Formatted publication date or an empty string.
   */
  publicationDate$ = this.store.item$.pipe(
    filter((item) => !!item?.product?.publicationDate),
    map((item) => {
      const firstDayOfSale = item.catalogAvailability?.firstDayOfSale
        ? moment(item.catalogAvailability.firstDayOfSale)
        : null;
      const publicationDate = item.product?.publicationDate
        ? moment(item.product.publicationDate)
        : null;

      if (!firstDayOfSale && !publicationDate) {
        return '';
      }

      const validDates = [firstDayOfSale, publicationDate].filter((date) =>
        date?.isValid(),
      );
      const latestDate = validDates.length ? moment.max(validDates) : null;

      return latestDate ? latestDate.format('DD. MMMM YYYY') : '';
    }),
  );

  selectedBranchId$ = this.applicationService.activatedProcessId$.pipe(
    switchMap((processId) =>
      this.applicationService.getSelectedBranch$(processId),
    ),
  );

  get isTablet$() {
    return this._environment.matchTablet$;
  }

  get resultsPath() {
    return this._navigationService.getArticleSearchResultsPath(
      this.applicationService.activatedProcessId,
    ).path;
  }

  showMore = false;

  @ViewChild('detailsContainer', { read: ElementRef, static: false })
  detailsContainer: ElementRef;

  get detailsContainerNative(): HTMLElement {
    return this.detailsContainer?.nativeElement;
  }

  stockTooltipText$ = combineLatest([
    this.store.defaultBranch$,
    this.selectedBranchId$,
  ]).pipe(
    map(([defaultBranch, selectedBranch]) => {
      if (
        defaultBranch?.branchType !== 4 &&
        selectedBranch &&
        defaultBranch.id !== selectedBranch?.id
      ) {
        return 'Sie sehen den Bestand einer anderen Filiale.';
      }
      return '';
    }),
  );

  priceMaintained$ = combineLatest([
    this.store.takeAwayAvailability$,
    this.store.pickUpAvailability$,
    this.store.deliveryAvailability$,
    this.store.deliveryDigAvailability$,
    this.store.deliveryB2BAvailability$,
    this.store.downloadAvailability$,
  ]).pipe(
    map((availabilities) => {
      return (
        availabilities?.some(
          (availability) => (availability as any)?.priceMaintained,
        ) ?? false
      );
    }),
  );

  price$ = combineLatest([
    this.store.item$,
    this.store.takeAwayAvailability$,
    this.store.pickUpAvailability$,
    this.store.deliveryAvailability$,
    this.store.deliveryDigAvailability$,
    this.store.deliveryB2BAvailability$,
    this.store.downloadAvailability$,
  ]).pipe(
    map(
      ([
        item,
        takeAway,
        pickUp,
        delivery,
        deliveryDig,
        deliveryB2B,
        download,
      ]) => {
        const hasPickupOrTakeaway = takeAway?.inStock || pickUp?.inStock;

        if (
          hasPickupOrTakeaway &&
          item?.catalogAvailability?.price?.value?.value
        ) {
          return item?.catalogAvailability?.price;
        }

        if (takeAway?.price?.value?.value && takeAway?.inStock) {
          return takeAway.price;
        }

        if (delivery?.price?.value?.value) {
          return delivery.price;
        }

        if (deliveryDig?.price?.value?.value) {
          return deliveryDig.price;
        }

        if (deliveryB2B?.price?.value?.value) {
          return deliveryB2B.price;
        }

        if (download?.price?.value?.value) {
          return download.price;
        }

        return null;
      },
    ),
  );

  constructor(
    public readonly applicationService: ApplicationService,
    private activatedRoute: ActivatedRoute,
    public readonly store: ArticleDetailsStore,
    private domainPrinterService: DomainPrinterService,
    private uiModal: UiModalService,
    private productImageService: ProductImageService,
    private breadcrumb: BreadcrumbService,
    private _dateAdapter: DateAdapter,
    private _datePipe: DatePipe,
    private _purchaseOptionsModalService: PurchaseOptionsModalService,
    private _navigationService: ProductCatalogNavigationService,
    private _checkoutNavigationService: CheckoutNavigationService,
    private _environment: EnvironmentService,
    private _router: Router,
    private _domainCheckoutService: DomainCheckoutService,
    private _store: Store,
  ) {}

  ngOnInit() {
    const processIdSubscription = this.activatedRoute.parent.params
      .pipe(
        debounceTime(0),
        switchMap((params) =>
          this.applicationService
            .getSelectedBranch$(Number(params.processId))
            .pipe(map((selectedBranch) => ({ params, selectedBranch }))),
        ),
      )
      .subscribe(({ params, selectedBranch }) => {
        const processId = Number(params.processId);
        const processChanged = processId !== this.store.processId;
        const branchChanged = selectedBranch?.id !== this.store?.branch?.id;

        if (processChanged) {
          this.store.setProcessId(processId);
        }

        if (branchChanged) {
          this.store.setBranch(selectedBranch);
        }
      });

    const id$ = this.activatedRoute.params.pipe(
      tap((_) => (this.showRecommendations = false)),
      map((params) => Number(params?.id) || undefined),
      filter((f) => !!f),
    );

    const ean$ = this.activatedRoute.params.pipe(
      tap((_) => (this.showRecommendations = false)),
      map((params) => params?.ean || undefined),
      filter((f) => !!f),
    );

    const more$ = this.activatedRoute.params.subscribe(
      () => (this.showMore = false),
    );

    this.subscriptions.add(processIdSubscription);
    this.subscriptions.add(more$);
    this.subscriptions.add(this.store.loadDefaultBranch());
    this.subscriptions.add(this.store.loadItemById(id$));
    this.subscriptions.add(this.store.loadItemByEan(ean$));
    this.subscriptions.add(
      this.store.item$
        .pipe(
          withLatestFrom(this.isTablet$),
          filter(([item, isTablet]) => !!item),
        )
        .subscribe(([item, isTablet]) =>
          isTablet
            ? this.updateBreadcrumb(item)
            : this.updateBreadcrumbDesktop(item),
        ),
    );
  }

  ngOnDestroy() {
    this.subscriptions.unsubscribe();
  }

  getDetailsPath(ean?: string) {
    return this._navigationService.getArticleDetailsPathByEan({
      processId: this.applicationService.activatedProcessId,
      ean,
    }).path;
  }

  async updateBreadcrumb(item: ItemDTO) {
    const crumbs = await this.breadcrumb
      .getBreadcrumbsByKeyAndTags$(this.applicationService.activatedProcessId, [
        'catalog',
        'details',
        `${item.id}`,
      ])
      .pipe(first())
      .toPromise();

    for (const crumb of crumbs) {
      await this.breadcrumb.removeBreadcrumbsAfter(crumb.id);
    }

    this.breadcrumb.addBreadcrumbIfNotExists({
      key: this.applicationService.activatedProcessId,
      name: item.product?.name,
      path: this._navigationService.getArticleDetailsPath({
        processId: this.applicationService.activatedProcessId,
        itemId: item.id,
      }).path,
      params: this.activatedRoute.snapshot.queryParams,
      tags: ['catalog', 'details', `${item.id}`],
      section: 'customer',
    });
  }

  async showTooltip() {
    const text = await this.stockTooltipText$.pipe(first()).toPromise();
    if (!text) {
      // Show Tooltip attached to branch selector dropdown
      this._store.dispatch({ type: 'OPEN_TOOLTIP_NO_BRANCH_SELECTED' });
    }
  }

  async updateBreadcrumbDesktop(item: ItemDTO) {
    const crumbs = await this.breadcrumb
      .getBreadcrumbsByKeyAndTags$(this.applicationService.activatedProcessId, [
        'catalog',
        'details',
      ])
      .pipe(first())
      .toPromise();

    if (crumbs.length === 0) {
      this.breadcrumb.addBreadcrumbIfNotExists({
        key: this.applicationService.activatedProcessId,
        name: item.product?.name,
        path: this._navigationService.getArticleDetailsPath({
          processId: this.applicationService.activatedProcessId,
          itemId: item.id,
        }).path,
        params: this.activatedRoute.snapshot.queryParams,
        tags: ['catalog', 'details', `${item.id}`],
        section: 'customer',
      });
    } else {
      const crumb = crumbs.find((_) => true);
      this.breadcrumb.patchBreadcrumb(crumb.id, {
        key: this.applicationService.activatedProcessId,
        name: item.product?.name,
        path: this._navigationService.getArticleDetailsPath({
          processId: this.applicationService.activatedProcessId,
          itemId: item.id,
        }).path,
        params: this.activatedRoute.snapshot.queryParams,
        tags: ['catalog', 'details', `${item.id}`],
        section: 'customer',
      });
    }
  }

  async print() {
    const item = await this.store.item$.pipe(first()).toPromise();
    this.uiModal.open({
      content: PrintModalComponent,
      data: {
        printerType: 'Label',
        // TODO: remove item: item as PrinterItemDTO when the backend is fixed
        print: (printer) =>
          this.domainPrinterService
            .printProduct({ item: item as PrinterItemDTO, printer })
            .toPromise(),
      } as PrintModalData,
      config: {
        panelClass: [],
        showScrollbarY: false,
      },
    });
  }

  async showReviews() {
    const item = await this.store.item$.pipe(first()).toPromise();

    this.uiModal.open({
      content: ModalReviewsComponent,
      title: `${item.reviews?.length} Rezensionen`,
      data: item.reviews,
    });
  }

  async showAvailabilities() {
    const item = await this.store.item$.pipe(first()).toPromise();
    const modal = this.uiModal.open<BranchDTO>({
      content: ModalAvailabilitiesComponent,
      title: 'Bestände in anderen Filialen',
      data: {
        item,
      },
    });

    this.subscriptions.add(
      modal.afterClosed$.subscribe((result) => {
        if (result?.data) {
          this.showPurchasingModal(result.data);
        }
      }),
    );
  }

  async showImages() {
    const item = await this.store.item$.pipe(first()).toPromise();
    const images = item.images ? [...item.images] : [];

    images.unshift({
      url: this.productImageService.getImageUrl({
        imageId: item.imageId,
        width: 400,
        height: 655,
      }),
      thumbUrl: this.productImageService.getImageUrl({
        imageId: item.imageId,
        width: 43,
        height: 69,
      }),
    });

    this.uiModal.open({
      content: ModalImagesComponent,
      title: item.product.name,
      data: {
        images,
      },
    });
  }

  async showPurchasingModal(selectedBranch?: BranchDTO) {
    const processId = this.applicationService.activatedProcessId;
    const item = await this.store.item$.pipe(first()).toPromise();
    const shoppingCart = await firstValueFrom(
      this._domainCheckoutService.getShoppingCart({
        processId,
      }),
    );

    const modalRef = await this._purchaseOptionsModalService.open({
      type: 'add',
      tabId: processId,
      shoppingCartId: shoppingCart.id,
      items: [item],
      pickupBranch: selectedBranch,
      inStoreBranch: selectedBranch,
      preSelectOption: selectedBranch
        ? { option: 'in-store', showOptionOnly: true }
        : undefined,
    });

    modalRef.afterClosed$.subscribe(async (result) => {
      if (result?.data === 'continue') {
        const customer = await this._domainCheckoutService
          .getBuyer({ processId })
          .pipe(first())
          .toPromise();
        if (customer) {
          await this.#rewardSelectionPopUpFlow(processId);
        } else {
          await this.navigateToCustomerSearch();
        }
      } else if (result?.data === 'continue-shopping') {
        this.navigateToResultList();
      }
    });
  }

  async navigateToShoppingCart() {
    await this._checkoutNavigationService
      .getCheckoutReviewPath(this.applicationService.activatedProcessId)
      .navigate();
  }

  async navigateToCustomerSearch() {
    const nav = this._customerSearchNavigation.defaultRoute({
      processId: this.applicationService.activatedProcessId,
    });
    try {
      const response = await this.customerFeatures$
        .pipe(
          first(),
          switchMap((customerFeatures) => {
            return this._domainCheckoutService.canSetCustomer({
              processId: this.applicationService.activatedProcessId,
              customerFeatures,
            });
          }),
        )
        .toPromise();
      this._router.navigate(nav.path, {
        queryParams: { filter_customertype: response.filter.customertype },
      });
    } catch (error) {
      this._router.navigate(nav.path);
    }
  }

  async navigateToResultList() {
    const processId = this.applicationService.activatedProcessId;
    const crumbs = await this.breadcrumb
      .getBreadcrumbsByKeyAndTags$(this.applicationService.activatedProcessId, [
        'catalog',
        'details',
      ])
      .pipe(first())
      .toPromise();

    const crumb = crumbs[crumbs.length - 1];
    if (crumb) {
      await this._navigationService
        .getArticleSearchResultsPath(processId, { queryParams: crumb.params })
        .navigate();
    } else {
      await this._navigationService
        .getArticleSearchBasePath(processId)
        .navigate();
    }
  }

  scrollTop(div: HTMLDivElement) {
    this.detailsContainerNative?.scrollTo({ top: 0, behavior: 'smooth' });
  }

  loadImage() {
    this.imageLoaded$.next(true);
  }

  async #reloadShoppingCart(processId: number) {
    await this._domainCheckoutService.reloadShoppingCart({ processId });
  }

  async #navigateToReward(processId: number) {
    await this._router.navigate([`/${processId}`, 'reward', 'cart']);
  }

  async #rewardSelectionPopUpFlow(tabId: number) {
    await this.#reloadShoppingCart(tabId);
    const navigate: NavigateAfterRewardSelection =
      await this._rewardSelectionPopUpService.popUp();
    await this.#reloadShoppingCart(tabId);

    switch (navigate) {
      case NavigateAfterRewardSelection.CART:
        await this.navigateToShoppingCart();
        break;
      case NavigateAfterRewardSelection.REWARD:
        await this.#navigateToReward(tabId);
        break;
      case NavigateAfterRewardSelection.CATALOG:
        await this.navigateToResultList();
        break;
      default:
        break;
    }
  }
}
