import { inject, Injectable } from '@angular/core';
import {
  ItemsResult,
  StoreCheckoutShoppingCartService,
  ItemPayload,
  AddToShoppingCartDTO,
  UpdateShoppingCartItemDTO,
} from '@generated/swagger/checkout-api';
import {
  AddItemToShoppingCartParams,
  AddItemToShoppingCartParamsSchema,
  CanAddItemsToShoppingCartParams,
  CanAddItemsToShoppingCartParamsSchema,
  RemoveShoppingCartItemParams,
  RemoveShoppingCartItemParamsSchema,
  UpdateShoppingCartItemParams,
  UpdateShoppingCartItemParamsSchema,
} from '../schemas';
import { RewardSelectionItem, ShoppingCart, ShoppingCartItem } from '../models';
import {
  ResponseArgsError,
  takeUntilAborted,
  ensureCurrencyDefaults,
} from '@isa/common/data-access';
import { firstValueFrom } from 'rxjs';
import { logger } from '@isa/core/logging';
import { CheckoutMetadataService } from './checkout-metadata.service';
import { getItemKey } from '../helpers';
import { ShoppingCartEvent, ShoppingCartEvents } from '../shopping-cart.events';

@Injectable({ providedIn: 'root' })
export class ShoppingCartService {
  #logger = logger(() => ({ service: 'ShoppingCartService' }));
  #shoppingCartStream = inject(ShoppingCartEvents);
  #storeCheckoutShoppingCartService = inject(StoreCheckoutShoppingCartService);
  #checkoutMetadataService = inject(CheckoutMetadataService);

  async createShoppingCart(): Promise<ShoppingCart> {
    const req$ =
      this.#storeCheckoutShoppingCartService.StoreCheckoutShoppingCartCreateShoppingCart();

    const res = await firstValueFrom(req$);

    if (res.error) {
      const err = new ResponseArgsError(res);
      this.#logger.error('Failed to create shopping cart', err);
      throw err;
    }

    this.#shoppingCartStream.pub(
      ShoppingCartEvent.Created,
      res.result as ShoppingCart,
      'ShoppingCartService',
    );
    return res.result as ShoppingCart;
  }

  async getShoppingCart(
    shoppingCartId: number,
    abortSignal?: AbortSignal,
  ): Promise<ShoppingCart | undefined> {
    let req$ =
      this.#storeCheckoutShoppingCartService.StoreCheckoutShoppingCartGetShoppingCart(
        {
          shoppingCartId,
        },
      );

    if (abortSignal) {
      req$ = req$.pipe(takeUntilAborted(abortSignal));
    }

    const res = await firstValueFrom(req$);

    if (res.error) {
      const err = new ResponseArgsError(res);
      this.#logger.error('Failed to fetch shopping cart', err);
      throw err;
    }

    this.#shoppingCartStream.pub(
      ShoppingCartEvent.ItemUpdated,
      res.result as ShoppingCart,
      'ShoppingCartService',
    );
    return res.result as ShoppingCart;
  }

  async canAddItems(
    params: CanAddItemsToShoppingCartParams,
  ): Promise<ItemsResult[]> {
    const parsed = CanAddItemsToShoppingCartParamsSchema.parse(params);
    const req$ =
      this.#storeCheckoutShoppingCartService.StoreCheckoutShoppingCartCanAddItems(
        {
          shoppingCartId: parsed.shoppingCartId,
          payload: parsed.payload as ItemPayload[],
        },
      );

    const res = await firstValueFrom(req$);

    if (res.error) {
      const err = new ResponseArgsError(res);
      this.#logger.error(
        'Failed to check if items can be added to shopping cart',
        err,
      );
      throw err;
    }

    return res.result as unknown as ItemsResult[];
  }

  async addItem(params: AddItemToShoppingCartParams): Promise<ShoppingCart> {
    const parsed = AddItemToShoppingCartParamsSchema.parse(params);

    const req$ =
      this.#storeCheckoutShoppingCartService.StoreCheckoutShoppingCartAddItemToShoppingCart(
        {
          shoppingCartId: parsed.shoppingCartId,
          items: parsed.items as AddToShoppingCartDTO[],
        },
      );

    const res = await firstValueFrom(req$);

    if (res.error) {
      const err = new ResponseArgsError(res);
      this.#logger.error('Failed to add item to shopping cart', err);
      throw err;
    }

    this.#shoppingCartStream.pub(
      ShoppingCartEvent.ItemAdded,
      res.result as ShoppingCart,
      'ShoppingCartService',
    );
    return res.result as ShoppingCart;
  }

  async updateItem(
    params: UpdateShoppingCartItemParams,
  ): Promise<ShoppingCart> {
    const parsed = UpdateShoppingCartItemParamsSchema.parse(params);

    const req$ =
      this.#storeCheckoutShoppingCartService.StoreCheckoutShoppingCartUpdateShoppingCartItem(
        {
          shoppingCartId: parsed.shoppingCartId,
          shoppingCartItemId: parsed.shoppingCartItemId,
          values: parsed.values as UpdateShoppingCartItemDTO,
        },
      );

    const res = await firstValueFrom(req$);

    if (res.error) {
      const err = new ResponseArgsError(res);
      this.#logger.error('Failed to update shopping cart item', err);
      throw err;
    }

    this.#shoppingCartStream.pub(
      ShoppingCartEvent.ItemUpdated,
      res.result as ShoppingCart,
      'ShoppingCartService',
    );
    return res.result as ShoppingCart;
  }

  async removeItem(
    params: RemoveShoppingCartItemParams,
  ): Promise<ShoppingCart> {
    const parsed = RemoveShoppingCartItemParamsSchema.parse(params);
    const req$ =
      this.#storeCheckoutShoppingCartService.StoreCheckoutShoppingCartUpdateShoppingCartItem(
        {
          shoppingCartId: parsed.shoppingCartId,
          shoppingCartItemId: parsed.shoppingCartItemId,
          values: {
            quantity: 0,
          },
        },
      );

    const res = await firstValueFrom(req$);

    if (res.error) {
      const err = new ResponseArgsError(res);
      this.#logger.error('Failed to remove item from shopping cart', err);
      throw err;
    }

    this.#shoppingCartStream.pub(
      ShoppingCartEvent.ItemRemoved,
      res.result as ShoppingCart,
      'ShoppingCartService',
    );
    return res.result as ShoppingCart;
  }

  // TODO: Code Kommentieren + Beschreiben
  async completeRewardSelection({
    tabId,
    rewardSelectionItems,
  }: {
    tabId: number;
    rewardSelectionItems: RewardSelectionItem[];
  }) {
    // Fetch or create both shopping cart IDs
    const shoppingCartId = await this.#getOrCreateShoppingCartId(tabId);
    const rewardShoppingCartId =
      await this.#getOrCreateRewardShoppingCartId(tabId);

    // Get Current Carts
    const currentCart = await this.getShoppingCart(shoppingCartId);
    const currentRewardCart = await this.getShoppingCart(rewardShoppingCartId);

    // Get Current Items from Cart
    const currentCartItems = currentCart?.items?.map((item) => item.data) ?? [];
    const currentRewardCartItems =
      currentRewardCart?.items?.map((item) => item.data) ?? [];

    for (const selectionItem of rewardSelectionItems) {
      // Search by Key needed because of different ShoppingCartItem IDs the items have in the different carts
      const selectionItemKey = getItemKey(selectionItem.item);
      const currentInCart = currentCartItems.find(
        (item) => item && getItemKey(item) === selectionItemKey,
      );
      const currentInRewardCart = currentRewardCartItems.find(
        (item) => item && getItemKey(item) === selectionItemKey,
      );

      // Handle regular cart
      await this.#handleCart({
        shoppingCartId,
        itemId: currentInCart?.id,
        currentCartItem: currentInCart,
        rewardSelectionItem: selectionItem,
      });

      // Handle reward cart
      await this.#handleRewardCart({
        rewardShoppingCartId,
        itemId: currentInRewardCart?.id,
        currentRewardCartItem: currentInRewardCart,
        rewardSelectionItem: selectionItem,
      });
    }
  }

  // TODO: If Logik in eigene Funktionen auslagern - Für Remove die Update Funktion mit Quantity 0 nutzen, Code Kommentieren
  async #handleCart({
    shoppingCartId,
    itemId,
    currentCartItem,
    rewardSelectionItem,
  }: {
    shoppingCartId: number;
    itemId: number | undefined;
    currentCartItem: ShoppingCartItem | undefined;
    rewardSelectionItem: RewardSelectionItem;
  }) {
    const desiredCartQuantity = rewardSelectionItem.cartQuantity;
    if (currentCartItem && itemId) {
      const currentQuantity = currentCartItem.quantity;
      if (desiredCartQuantity !== currentQuantity) {
        if (desiredCartQuantity === 0) {
          this.#logger.info('Removing item from regular cart', () => ({
            shoppingCartId,
            itemId,
            currentQuantity,
          }));
          await this.removeItem({
            shoppingCartId,
            shoppingCartItemId: itemId,
          });
        } else {
          this.#logger.info('Updating item quantity in regular cart', () => ({
            shoppingCartId,
            itemId,
            currentQuantity,
            desiredCartQuantity,
          }));
          await this.updateItem({
            shoppingCartId,
            shoppingCartItemId: itemId,
            values: { quantity: desiredCartQuantity },
          });
        }
      }
    } else if (desiredCartQuantity > 0) {
      this.#logger.info('Adding item to regular cart', () => ({
        shoppingCartId,
        itemId,
        desiredCartQuantity,
        productNumber: rewardSelectionItem?.item?.product?.catalogProductNumber,
      }));
      await this.addItem({
        shoppingCartId,
        items: [
          {
            destination: rewardSelectionItem.item.destination,
            product: {
              ...rewardSelectionItem.item.product,
              catalogProductNumber:
                rewardSelectionItem?.item?.product?.catalogProductNumber ??
                String(rewardSelectionItem?.item?.id),
            },
            availability: {
              ...rewardSelectionItem.item.availability,
              price: ensureCurrencyDefaults(
                rewardSelectionItem?.availabilityPrice ??
                  rewardSelectionItem?.catalogPrice,
              ),
            },
            promotion: rewardSelectionItem?.item?.promotion,
            quantity: desiredCartQuantity,
            loyalty: undefined,
          },
        ],
      });
    }
  }

  // TODO: If Logik in eigene Funktionen auslagern - Für Remove die Update Funktion mit Quantity 0 nutzen, Code Kommentieren
  async #handleRewardCart({
    rewardShoppingCartId,
    itemId,
    currentRewardCartItem,
    rewardSelectionItem,
  }: {
    rewardShoppingCartId: number;
    itemId: number | undefined;
    currentRewardCartItem: ShoppingCartItem | undefined;
    rewardSelectionItem: RewardSelectionItem;
  }) {
    const desiredRewardCartQuantity = rewardSelectionItem.rewardCartQuantity;
    if (currentRewardCartItem && itemId) {
      const currentQuantity = currentRewardCartItem.quantity;
      if (desiredRewardCartQuantity !== currentQuantity) {
        if (desiredRewardCartQuantity === 0) {
          this.#logger.info('Removing item from reward cart', () => ({
            rewardShoppingCartId,
            itemId,
            currentQuantity,
          }));
          await this.removeItem({
            shoppingCartId: rewardShoppingCartId,
            shoppingCartItemId: itemId,
          });
        } else {
          this.#logger.info('Updating item quantity in reward cart', () => ({
            rewardShoppingCartId,
            itemId,
            currentQuantity,
            desiredRewardCartQuantity,
          }));
          await this.updateItem({
            shoppingCartId: rewardShoppingCartId,
            shoppingCartItemId: itemId,
            values: { quantity: desiredRewardCartQuantity },
          });
        }
      }
    } else if (desiredRewardCartQuantity > 0) {
      this.#logger.info('Adding item to reward cart', () => ({
        rewardShoppingCartId,
        itemId,
        desiredRewardCartQuantity,
        rewardPoints: rewardSelectionItem.catalogRewardPoints,
        productNumber: rewardSelectionItem?.item?.product?.catalogProductNumber,
      }));
      await this.addItem({
        shoppingCartId: rewardShoppingCartId,
        items: [
          {
            destination: rewardSelectionItem.item.destination,
            product: {
              ...rewardSelectionItem.item.product,
              catalogProductNumber:
                rewardSelectionItem?.item?.product?.catalogProductNumber ??
                String(rewardSelectionItem?.item?.id),
            },
            quantity: desiredRewardCartQuantity,
            promotion: undefined, // If loyalty is set, we need to remove promotion
            loyalty: { value: rewardSelectionItem.catalogRewardPoints }, // Set loyalty points from item
            availability: {
              ...rewardSelectionItem.item.availability,
              price: {
                value: {
                  value: 0,
                  currency: 'EUR',
                  currencySymbol: '€',
                },
              },
            },
          },
        ],
      });
    }
  }

  async #getOrCreateShoppingCartId(tabId: number): Promise<number> {
    const shoppingCartId =
      this.#checkoutMetadataService.getShoppingCartId(tabId);

    if (shoppingCartId) {
      return shoppingCartId;
    }

    const shoppingCart = await this.createShoppingCart();
    this.#checkoutMetadataService.setShoppingCartId(tabId!, shoppingCart.id);
    return shoppingCart.id!;
  }

  async #getOrCreateRewardShoppingCartId(tabId: number): Promise<number> {
    const rewardShoppingCartId =
      this.#checkoutMetadataService.getRewardShoppingCartId(tabId);
    if (rewardShoppingCartId) {
      return rewardShoppingCartId;
    }

    const shoppingCart = await this.createShoppingCart();
    this.#checkoutMetadataService.setRewardShoppingCartId(
      tabId!,
      shoppingCart.id,
    );
    return shoppingCart.id!;
  }
}
