import {
  ChangeDetectionStrategy,
  ChangeDetectorRef,
  Component,
  EventEmitter,
  Input,
  NgZone,
  OnInit,
  Output,
  inject,
} from '@angular/core';
import { ApplicationService } from '@core/application';
import { EnvironmentService } from '@core/environment';
import { DomainAvailabilityService } from '@domain/availability';
import { DomainCheckoutService } from '@domain/checkout';
import { ComponentStore } from '@ngrx/component-store';
import { ProductCatalogNavigationService } from '@shared/services/navigation';
import { ItemType, ShoppingCartItemDTO } from '@generated/swagger/checkout-api';
import { cloneDeep } from 'lodash';
import moment from 'moment';
import { combineLatest } from 'rxjs';
import { filter, first, map, shareReplay, switchMap } from 'rxjs/operators';

export interface ShoppingCartItemComponentState {
  item: ShoppingCartItemDTO;
  orderType: string;
  loadingOnItemChangeById?: number;
  loadingOnQuantityChangeById?: number;
  refreshingAvailability: boolean;
  sscChanged: boolean;
  sscTextChanged: boolean;
  estimatedShippingDateChanged: boolean;
}

@Component({
  selector: 'page-shopping-cart-item',
  templateUrl: 'shopping-cart-item.component.html',
  styleUrls: ['shopping-cart-item.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush,
  standalone: false,
})
export class ShoppingCartItemComponent
  extends ComponentStore<ShoppingCartItemComponentState>
  implements OnInit
{
  private _zone = inject(NgZone);

  @Output() changeItem = new EventEmitter<{
    shoppingCartItem: ShoppingCartItemDTO;
  }>();
  @Output() changeDummyItem = new EventEmitter<{
    shoppingCartItem: ShoppingCartItemDTO;
  }>();
  @Output() changeQuantity = new EventEmitter<{
    shoppingCartItem: ShoppingCartItemDTO;
    quantity: number;
  }>();

  @Input()
  get item() {
    return this.get((s) => s.item);
  }
  set item(item: ShoppingCartItemDTO) {
    if (this.item !== item) {
      this.patchState({ item });
    }
  }
  readonly item$ = this.select((s) => s.item);

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

  get showLoyaltyValue() {
    return this.item?.loyalty?.value > 0;
  }

  @Input()
  get orderType() {
    return this.get((s) => s.orderType);
  }
  set orderType(orderType: string) {
    if (this.orderType !== orderType) {
      this.patchState({ orderType });
    }
  }
  readonly orderType$ = this.select((s) => s.orderType);

  @Input()
  get loadingOnItemChangeById() {
    return this.get((s) => s.loadingOnItemChangeById);
  }
  set loadingOnItemChangeById(loadingOnItemChangeById: number) {
    if (this.loadingOnItemChangeById !== loadingOnItemChangeById) {
      this.patchState({ loadingOnItemChangeById });
    }
  }
  readonly loadingOnItemChangeById$ = this.select(
    (s) => s.loadingOnItemChangeById,
  ).pipe(shareReplay());

  @Input()
  get loadingOnQuantityChangeById() {
    return this.get((s) => s.loadingOnQuantityChangeById);
  }
  set loadingOnQuantityChangeById(loadingOnQuantityChangeById: number) {
    if (this.loadingOnQuantityChangeById !== loadingOnQuantityChangeById) {
      this.patchState({ loadingOnQuantityChangeById });
    }
  }
  readonly loadingOnQuantityChangeById$ = this.select(
    (s) => s.loadingOnQuantityChangeById,
  ).pipe(shareReplay());

  @Input()
  quantityError: string;

  isDummy$ = this.item$.pipe(
    map((item) => item?.availability?.supplyChannel === 'MANUALLY'),
    shareReplay(),
  );
  hasOrderType$ = this.orderType$.pipe(
    map((orderType) => orderType !== undefined),
    shareReplay(),
  );

  canEdit$ = combineLatest([
    this.isDummy$,
    this.hasOrderType$,
    this.item$,
  ]).pipe(
    map(([isDummy, hasOrderType, item]) => {
      if (item.itemType === (66560 as ItemType)) {
        return false;
      }
      return isDummy || hasOrderType;
    }),
  );

  quantityRange$ = combineLatest([this.orderType$, this.item$]).pipe(
    map(([orderType, item]) =>
      orderType === 'Rücklage' ? item.availability?.inStock : 999,
    ),
  );

  isDownloadAvailable$ = combineLatest([this.item$, this.orderType$]).pipe(
    filter(([, orderType]) => orderType === 'Download'),
    switchMap(([item]) =>
      this.availabilityService.getDownloadAvailability({
        item: {
          ean: item.product.ean,
          price: item.availability.price,
          itemId: +item.product.catalogProductNumber,
        },
      }),
    ),
    map(
      (availability) =>
        availability && this.availabilityService.isAvailable({ availability }),
    ),
  );

  olaError$ = this.checkoutService
    .getOlaErrors({ processId: this.application.activatedProcessId })
    .pipe(map((ids) => ids?.find((id) => id === this.item.id)));

  get productSearchResultsPath() {
    return this._productNavigationService.getArticleSearchResultsPath(
      this.application.activatedProcessId,
    ).path;
  }

  get productSearchDetailsPath() {
    return this._productNavigationService.getArticleDetailsPathByEan({
      processId: this.application.activatedProcessId,
      ean: this.item?.product?.ean,
    }).path;
  }

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

  refreshingAvailabilit$ = this.select((s) => s.refreshingAvailability);

  sscChanged$ = this.select((s) => s.sscChanged || s.sscTextChanged);

  estimatedShippingDateChanged$ = this.select(
    (s) => s.estimatedShippingDateChanged,
  );

  notAvailable$ = this.item$.pipe(
    map((item) => {
      const availability = item?.availability;

      if (availability.availabilityType === 0) {
        return false;
      }

      if (availability.inStock && item.quantity > availability.inStock) {
        return true;
      }

      return !this.availabilityService.isAvailable({ availability });
    }),
  );

  constructor(
    private availabilityService: DomainAvailabilityService,
    private checkoutService: DomainCheckoutService,
    public application: ApplicationService,
    private _productNavigationService: ProductCatalogNavigationService,
    private _environment: EnvironmentService,
    private _cdr: ChangeDetectorRef,
  ) {
    super({
      item: undefined,
      orderType: '',
      refreshingAvailability: false,
      sscChanged: false,
      sscTextChanged: false,
      estimatedShippingDateChanged: false,
    });
  }

  ngOnInit() {
    // Component initialization
  }

  async onChangeItem() {
    const isDummy = await this.isDummy$.pipe(first()).toPromise();
    if (isDummy) {
      this.changeDummyItem.emit({ shoppingCartItem: this.item });
    } else {
      this.changeItem.emit({ shoppingCartItem: this.item });
    }
  }

  onChangeQuantity(quantity: number) {
    this.changeQuantity.emit({ shoppingCartItem: this.item, quantity });
  }

  async refreshAvailability() {
    const currentAvailability = cloneDeep(this.item.availability);

    try {
      this.patchRefreshingAvailability(true);
      this._cdr.markForCheck();
      const availability = await this.checkoutService.refreshAvailability({
        processId: this.application.activatedProcessId,
        shoppingCartItemId: this.item.id,
      });

      if (currentAvailability.ssc !== availability.ssc) {
        this.sscChanged();
      }
      if (currentAvailability.sscText !== availability.sscText) {
        this.ssctextChanged();
      }
      if (
        moment(currentAvailability.estimatedShippingDate)
          .startOf('day')
          .diff(moment(availability.estimatedShippingDate).startOf('day'))
      ) {
        this.estimatedShippingDateChanged();
      }
    } catch {
      // Error handling for availability refresh
    }

    this.patchRefreshingAvailability(false);
    this._cdr.markForCheck();
  }

  patchRefreshingAvailability(value: boolean) {
    this._zone.run(() => {
      this.patchState({ refreshingAvailability: value });
      this._cdr.markForCheck();
    });
  }

  ssctextChanged() {
    this.patchState({ sscTextChanged: true });
    this._cdr.markForCheck();
  }

  sscChanged() {
    this.patchState({ sscChanged: true });
    this._cdr.markForCheck();
  }

  estimatedShippingDateChanged() {
    this.patchState({ estimatedShippingDateChanged: true });
    this._cdr.markForCheck();
  }
}
