import { computed, inject, Injectable, resource, signal } from '@angular/core';
import { AvailabilityService } from '@isa/availability/data-access';
import {
  CatalougeSearchService,
  Price as CatalogPrice,
} from '@isa/catalogue/data-access';
import {
  OrderTypeFeature,
  Price as AvailabilityPrice,
} from '@isa/checkout/data-access';
import { logger } from '@isa/core/logging';

/**
 * Input item for availability check - contains EAN, orderType and optional branchId
 */
export interface ItemWithOrderType {
  ean: string;
  orderType: OrderTypeFeature;
  branchId?: number;
}

/**
 * Result containing price from availability and redemption points from catalog
 */
export interface PriceAndRedemptionPointsResult {
  ean: string;
  availabilityPrice?: AvailabilityPrice;
  catalogPrice?: CatalogPrice;
  redemptionPoints?: number;
}

/**
 * Resource for fetching combined price and redemption points data.
 *
 * This resource:
 * 1. Fetches catalog items by EAN to get redemption points
 * 2. Groups items by order type
 * 3. Fetches availability data for each order type group
 * 4. Combines catalog redemption points with availability prices
 *
 * @example
 * ```typescript
 * const resource = inject(PriceAndRedemptionPointsResource);
 *
 * // Load data for items
 * resource.loadPriceAndRedemptionPoints([
 *   { ean: '1234567890', orderType: OrderType.Delivery },
 *   { ean: '0987654321', orderType: OrderType.Pickup }
 * ]);
 *
 * // Access results
 * const results = resource.priceAndRedemptionPoints();
 * ```
 */
@Injectable({ providedIn: 'root' })
export class PriceAndRedemptionPointsResource {
  #catalogueSearchService = inject(CatalougeSearchService);
  #availabilityService = inject(AvailabilityService);
  #logger = logger(() => ({ resource: 'PriceAndRedemptionPoints' }));

  #items = signal<ItemWithOrderType[] | undefined>(undefined);

  #priceAndRedemptionPointsResource = resource({
    params: computed(() => ({ items: this.#items() })),
    loader: async ({
      params,
      abortSignal,
    }): Promise<PriceAndRedemptionPointsResult[]> => {
      if (!params?.items || params.items.length === 0) {
        return [];
      }

      // Extract unique EANs for catalog lookup
      const eans = [...new Set(params.items.map((item) => item.ean))];

      // Fetch catalog items to get redemption points
      const catalogItems = await this.#catalogueSearchService.searchByEans(
        eans,
        abortSignal,
      );

      // Create a map for quick catalog lookup by EAN
      const catalogByEan = new Map(
        catalogItems.map((item) => [item.product.ean, item]),
      );

      // Fetch availability for each item individually (in parallel)
      const availabilityPromises = params.items.map(async (checkItem) => {
        const catalogItem = catalogByEan.get(checkItem.ean);

        // Skip items without catalog entry
        if (!catalogItem?.id) {
          return { ean: checkItem.ean, price: undefined };
        }

        try {
          // Call getAvailability for single item
          // InStore (Rücklage) has different schema: uses itemId instead of item object
          const params =
            checkItem.orderType === OrderTypeFeature.InStore
              ? {
                  orderType: checkItem.orderType,
                  branchId: checkItem.branchId,
                  itemId: catalogItem.id,
                }
              : {
                  orderType: checkItem.orderType,
                  branchId: checkItem.branchId,
                  item: {
                    itemId: catalogItem.id,
                    ean: checkItem.ean,
                    quantity: 1,
                    price: catalogItem.catalogAvailability?.price,
                  },
                };

          const availability = await this.#availabilityService.getAvailability(
            params as any,
            abortSignal,
          );

          return {
            ean: checkItem.ean,
            price: availability?.price as AvailabilityPrice | undefined,
          };
        } catch (error) {
          this.#logger.error(
            'Failed to fetch availability for item',
            error as Error,
            () => ({
              ean: checkItem.ean,
              orderType: checkItem.orderType,
              branchId: checkItem.branchId,
            }),
          );
          return { ean: checkItem.ean, price: undefined };
        }
      });

      // Wait for all availability requests to complete
      const availabilityResults = await Promise.all(availabilityPromises);

      // Build price map from results
      const pricesByEan = new Map(
        availabilityResults.map((result) => [result.ean, result.price]),
      );

      // Build final result: combine catalog prices, availability prices and redemption points
      const results: PriceAndRedemptionPointsResult[] = eans.map((ean) => ({
        ean,
        availabilityPrice: pricesByEan.get(ean),
        catalogPrice: catalogByEan.get(ean)?.catalogAvailability?.price,
        redemptionPoints: catalogByEan.get(ean)?.redemptionPoints,
      }));

      return results;
    },
    defaultValue: [],
  });

  readonly priceAndRedemptionPoints =
    this.#priceAndRedemptionPointsResource.value.asReadonly();
  readonly loading = this.#priceAndRedemptionPointsResource.isLoading;
  readonly error = computed(
    () => this.#priceAndRedemptionPointsResource.error()?.message ?? null,
  );

  loadPriceAndRedemptionPoints(items: ItemWithOrderType[] | undefined) {
    this.#items.set(items);
  }

  refresh() {
    this.#priceAndRedemptionPointsResource.reload();
  }
}
