import { CommonModule } from '@angular/common';
import {
  Component,
  ChangeDetectionStrategy,
  OnInit,
  OnDestroy,
  OnChanges,
  SimpleChanges,
  computed,
  input,
} from '@angular/core';
import {
  FormControl,
  FormsModule,
  ReactiveFormsModule,
  Validators,
} from '@angular/forms';
import { ProductImageModule } from '@cdn/product-image';
import { InputControlModule } from '@shared/components/input-control';
import { ElementLifecycleModule } from '@shared/directives/element-lifecycle';
import { UiCommonModule, UiOverlayTriggerDirective } from '@ui/common';
import { UiQuantityDropdownModule } from '@ui/quantity-dropdown';
import { UiSpinnerModule } from '@ui/spinner';
import { UiTooltipModule } from '@ui/tooltip';
import { combineLatest, ReplaySubject, Subscription } from 'rxjs';
import { IconComponent } from '@shared/components/icon';
import {
  map,
  take,
  shareReplay,
  startWith,
  switchMap,
  withLatestFrom,
} from 'rxjs/operators';
import { GIFT_CARD_MAX_PRICE, PRICE_PATTERN } from '../constants';
import {
  Item,
  PurchaseOptionsStore,
  isDownload,
  isItemDTO,
  isShoppingCartItemDTO,
} from '../store';
import { OrderDeadlinePipeModule } from '@shared/pipes/order-deadline';
import { UiSelectModule } from '@ui/select';
import { KeyValueDTOOfStringAndString } from '@generated/swagger/cat-search-api';
import { ScaleContentComponent } from '@shared/components/scale-content';
import moment from 'moment';
import { toSignal } from '@angular/core/rxjs-interop';
import { NgIcon, provideIcons } from '@ng-icons/core';
import { isaOtherInfo } from '@isa/icons';

@Component({
  selector: 'shared-purchase-options-list-item',
  templateUrl: 'purchase-options-list-item.component.html',
  styleUrls: ['purchase-options-list-item.component.css'],
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [
    CommonModule,
    UiQuantityDropdownModule,
    UiSelectModule,
    ProductImageModule,
    IconComponent,
    UiSpinnerModule,
    ReactiveFormsModule,
    InputControlModule,
    FormsModule,
    ElementLifecycleModule,
    UiTooltipModule,
    UiCommonModule,
    ScaleContentComponent,
    OrderDeadlinePipeModule,
    NgIcon,
  ],
  host: { class: 'shared-purchase-options-list-item' },
  providers: [provideIcons({ isaOtherInfo })],
})
export class PurchaseOptionsListItemComponent
  implements OnInit, OnDestroy, OnChanges
{
  private _subscriptions = new Subscription();

  private _itemSubject = new ReplaySubject<Item>(1);

  item = input.required<Item>();

  get item$() {
    return this._itemSubject.asObservable();
  }

  get product() {
    return this.item().product;
  }

  redemptionPoints = computed(() => {
    const item = this.item();
    if (isShoppingCartItemDTO(item, this._store.type)) {
      return item.loyalty?.value;
    }

    return item.redemptionPoints;
  });

  showRedemptionPoints = toSignal(this._store.useRedemptionPoints$);

  quantityFormControl = new FormControl<number>(null);

  private readonly _giftCardValidators = [
    Validators.required,
    Validators.min(1),
    Validators.max(GIFT_CARD_MAX_PRICE),
    Validators.pattern(PRICE_PATTERN),
  ];

  private readonly _defaultValidators = [
    Validators.required,
    Validators.min(0.01),
    Validators.max(999.99),
    Validators.pattern(PRICE_PATTERN),
  ];

  priceFormControl = new FormControl<string>(null);

  manualVatFormControl = new FormControl<string>('', [Validators.required]);

  selectedFormControl = new FormControl<boolean>(false);

  availabilities$ = this.item$.pipe(
    switchMap((item) => this._store.getAvailabilitiesForItem$(item.id)),
  );

  availability$ = combineLatest([this.item$, this._store.purchaseOption$]).pipe(
    switchMap(([item, purchaseOption]) =>
      this._store.getAvailabilityWithPurchaseOption$(item.id, purchaseOption),
    ),
    map((availability) => availability?.data),
  );

  availability = toSignal(this.availability$);

  price$ = this.item$.pipe(switchMap((item) => this._store.getPrice$(item.id)));

  priceValue$ = this.price$.pipe(map((price) => price?.value?.value));

  // Ticket #4074 analog zu Ticket #2244
  // take(2) um die Response des Katalogpreises und danach um die Response der OLAs abzuwarten
  // Logik gilt ausschließlich für Archivartikel
  setManualPrice$ = this.price$.pipe(
    take(2),
    map((price) => {
      // Logik nur beim Hinzufügen über Kaufoptionen, da über Ändern im Warenkorb die Info fehlt ob das jeweilige ShoppingCartItem ein Archivartikel ist oder nicht
      const features = this.item().features as KeyValueDTOOfStringAndString[];
      if (!!features && Array.isArray(features)) {
        const isArchive = !!features?.find(
          (feature) => feature?.enabled === true && feature?.key === 'ARC',
        );
        return isArchive
          ? !price?.value?.value || price?.vat === undefined
          : false;
      }
      return false;
    }),
  );

  vats$ = this._store.vats$.pipe(shareReplay());

  priceVat$ = this.price$.pipe(map((price) => price?.vat?.vatType));

  canAddResult$ = this.item$.pipe(
    switchMap((item) =>
      this._store.getCanAddResultForItemAndCurrentPurchaseOption$(item.id),
    ),
  );

  canEditPrice$ = this.item$.pipe(
    switchMap((item) =>
      combineLatest([
        this.canAddResult$,
        this._store.getCanEditPrice$(item.id),
      ]),
    ),
    map(([canAddResult, canEditPrice]) => canAddResult?.canAdd && canEditPrice),
  );

  canEditVat$ = this.item$.pipe(
    switchMap((item) =>
      combineLatest([this.canAddResult$, this._store.getCanEditVat$(item.id)]),
    ),
    map(([canAddResult, canEditVat]) => canAddResult?.canAdd && canEditVat),
  );

  isGiftCard$ = this.item$.pipe(
    switchMap((item) => this._store.getIsGiftCard$(item.id)),
  );

  maxSelectableQuantity$ = combineLatest([
    this._store.purchaseOption$,
    this.availability$,
  ]).pipe(
    map(([purchaseOption, availability]) => {
      if (purchaseOption === 'in-store') {
        return availability?.inStock;
      }

      return 999;
    }),
    startWith(999),
  );

  showMaxAvailableQuantity$ = combineLatest([
    this._store.purchaseOption$,
    this.availability$,
    this.item$,
  ]).pipe(
    map(([purchaseOption, availability, item]) => {
      if (
        purchaseOption === 'pickup' &&
        availability?.inStock < item.quantity
      ) {
        return true;
      }

      return false;
    }),
  );

  fetchingAvailabilitiesArray$ = this.item$.pipe(
    switchMap((item) => this._store.getFetchingAvailabilitiesForItem$(item.id)),
  );

  fetchingAvailabilities$ = this.fetchingAvailabilitiesArray$.pipe(
    map((fetchingAvailabilities) => fetchingAvailabilities.length > 0),
  );

  fetchingInStoreAvailability$ = this.fetchingAvailabilitiesArray$.pipe(
    map((fetchingAvailabilities) =>
      fetchingAvailabilities.some((fa) => fa.purchaseOption === 'in-store'),
    ),
  );

  isFetchingInStore = toSignal(this.fetchingInStoreAvailability$, {
    initialValue: false,
  });

  showNotAvailable$ = combineLatest([
    this.availabilities$,
    this.fetchingAvailabilities$,
  ]).pipe(
    map(([availabilities, fetchingAvailabilities]) => {
      if (fetchingAvailabilities) {
        return false;
      }

      if (availabilities.length === 0) {
        return true;
      }

      return false;
    }),
  );

  isDownload$ = this.item$.pipe(map((item) => isDownload(item)));

  isDownloadItem = toSignal(this.isDownload$, { initialValue: false });

  showNoDownloadAvailability$ = combineLatest([
    this.isDownload$,
    this.availabilities$,
    this.fetchingAvailabilities$,
  ]).pipe(
    map(([isDownloadItem, availabilities, fetchingAvailabilities]) => {
      // Only check for download items
      if (!isDownloadItem) {
        return false;
      }

      // Don't show error while loading
      if (fetchingAvailabilities) {
        return false;
      }

      // Check if download availability exists
      const hasDownloadAvailability = availabilities.some(
        (a) => a.purchaseOption === 'download',
      );

      return !hasDownloadAvailability;
    }),
  );

  // Ticket #4813 Artikeldetailseite // EVT-Datum bei der Kaufoption Rücklage anzeigen
  get isEVT() {
    // Einstieg über Kaufoptionen - Hier wird die Katalogavailability verwendet die am ItemDTO hängt
    if (isItemDTO(this.item, this._store.type)) {
      const firstDayOfSale = this.item?.catalogAvailability?.firstDayOfSale;
      return this.firstDayOfSaleBiggerThanToday(firstDayOfSale);
    }

    // Einstieg über Warenkorb - Da wir hier ein ShoppingCartItemDTO haben werden hier die Katalogdaten neu gefetched und eine Katalogavailability drangehängt
    if (isShoppingCartItemDTO(this.item, this._store.type)) {
      const catalogAvailabilities = this._store.availabilities?.filter(
        (availability) => availability?.purchaseOption === 'catalog',
      );
      // #4813 Fix: Hier muss als Kriterium auf EAN statt itemId verglichen werden, denn ein ShoppingCartItemDTO (this.item) hat eine andere ItemId wie das ItemDTO (availability.itemId)
      const firstDayOfSale = catalogAvailabilities?.find(
        (availability) => this.item().product?.ean === availability?.ean,
      )?.data?.firstDayOfSale;
      return this.firstDayOfSaleBiggerThanToday(firstDayOfSale);
    }

    return undefined;
  }

  useRedemptionPoints = toSignal(this._store.useRedemptionPoints$);

  purchaseOption = toSignal(this._store.purchaseOption$);

  isReservePurchaseOption = computed(() => {
    return this.purchaseOption() === 'in-store';
  });

  showLowStockMessage = computed(() => {
    const availability = this.availability();
    const inStock = availability?.inStock ?? 0;

    return (
      this.useRedemptionPoints() &&
      this.isReservePurchaseOption() &&
      !this.isDownloadItem() &&
      !this.isFetchingInStore() &&
      inStock > 0 &&
      inStock < 2
    );
  });

  constructor(private _store: PurchaseOptionsStore) {}

  firstDayOfSaleBiggerThanToday(firstDayOfSale: string): Date {
    if (!!firstDayOfSale && moment(firstDayOfSale)?.isAfter(moment())) {
      return moment(firstDayOfSale).toDate();
    }
    return undefined;
  }

  onPriceInputInit(
    target: HTMLElement,
    overlayTrigger: UiOverlayTriggerDirective,
  ) {
    if (this._store.getIsGiftCard(this.item().id)) {
      overlayTrigger.open();
    }

    target?.focus();
  }

  // Wichtig für das korrekte Setzen des Preises an das Item für den Endpoint request
  parsePrice(value: string) {
    if (PRICE_PATTERN.test(value)) {
      return parseFloat(value.replace(',', '.'));
    }
  }

  stringifyPrice(value: number) {
    if (!value) return '';

    const price = value.toFixed(2).replace('.', ',');
    if (price.includes(',')) {
      const [integer, decimal] = price.split(',');
      return `${integer},${decimal.padEnd(2, '0')}`;
    }

    return price;
  }

  ngOnInit(): void {
    this.initPriceValidatorSubscription();
    this.initQuantitySubscription();
    this.initPriceSubscription();
    this.initVatSubscription();
    this.initSelectedSubscription();
  }

  ngOnChanges({ item }: SimpleChanges) {
    if (item) {
      this._itemSubject.next(this.item());
    }
  }

  ngOnDestroy(): void {
    this._itemSubject.complete();
    this._subscriptions.unsubscribe();
  }

  initPriceValidatorSubscription() {
    const sub = this.item$
      .pipe(switchMap((item) => this._store.getIsGiftCard$(item.id)))
      .subscribe((isGiftCard) => {
        if (isGiftCard) {
          this.priceFormControl.setValidators(this._giftCardValidators);
        } else {
          this.priceFormControl.setValidators(this._defaultValidators);
        }
      });

    this._subscriptions.add(sub);
  }

  initQuantitySubscription() {
    const sub = this.item$.subscribe((item) => {
      if (this.quantityFormControl.value !== item.quantity) {
        this.quantityFormControl.setValue(item.quantity);
      }
    });

    const valueChangesSub = this.quantityFormControl.valueChanges.subscribe(
      (quantity) => {
        if (this.item().quantity !== quantity) {
          this._store.setItemQuantity(this.item().id, quantity);
        }
      },
    );

    this._subscriptions.add(sub);
    this._subscriptions.add(valueChangesSub);
  }

  initPriceSubscription() {
    const sub = combineLatest([this.canEditPrice$, this.price$]).subscribe(
      ([canEditPrice, price]) => {
        if (!canEditPrice) {
          return;
        }

        const priceStr = this.stringifyPrice(price?.value?.value);
        if (priceStr === '') return;

        if (
          this.parsePrice(this.priceFormControl.value) !== price?.value?.value
        ) {
          this.priceFormControl.setValue(priceStr);
        }
      },
    );

    const valueChangesSub = combineLatest([
      this.canEditPrice$,
      this.priceFormControl.valueChanges,
    ]).subscribe(([canEditPrice, value]) => {
      if (!canEditPrice) {
        return;
      }

      const price = this._store.getPrice(this.item().id);
      const parsedPrice = this.parsePrice(value);

      if (!parsedPrice) {
        this._store.setPrice(this.item().id, null);
        return;
      }

      if (price[this.item().id] !== parsedPrice) {
        this._store.setPrice(this.item().id, this.parsePrice(value));
      }
    });
    this._subscriptions.add(sub);
    this._subscriptions.add(valueChangesSub);
  }

  initVatSubscription() {
    const valueChangesSub = this.manualVatFormControl.valueChanges
      .pipe(withLatestFrom(this.vats$))
      .subscribe(([formVatType, vats]) => {
        const price = this._store.getPrice(this.item().id);

        const vat = vats.find((vat) => vat?.vatType === Number(formVatType));

        if (!vat) {
          this._store.setVat(this.item().id, null);
          return;
        }

        if (price[this.item().id]?.vat?.vatType !== vat?.vatType) {
          this._store.setVat(this.item().id, vat);
        }
      });
    this._subscriptions.add(valueChangesSub);
  }

  initSelectedSubscription() {
    const sub = this.item$
      .pipe(
        switchMap((item) =>
          this._store.selectedItemIds$.pipe(
            map((ids) => ids.includes(item.id)),
          ),
        ),
      )
      .subscribe((selected) => {
        if (this.selectedFormControl.value !== selected) {
          this.selectedFormControl.setValue(selected);
        }
      });
    const valueChangesSub = this.selectedFormControl.valueChanges.subscribe(
      (selected) => {
        const current = this._store.selectedItemIds.includes(this.item().id);
        if (current !== selected) {
          this._store.setSelectedItem(this.item().id, selected);
        }
      },
    );
    this._subscriptions.add(sub);
    this._subscriptions.add(valueChangesSub);
  }
}
