import { inject, Injectable } from '@angular/core';
import { AvailabilityService as GeneratedAvailabilityService } from '@generated/swagger/availability-api';
import { ResponseArgsError, takeUntilAborted } from '@isa/common/data-access';
import { LogisticianService, Logistician } from '@isa/oms/data-access';
import { logger } from '@isa/core/logging';
// TODO: [Next Sprint - Architectural] Abstract cross-domain dependency
// Current: Direct dependency on remission domain (BranchService)
// Issue: availability domain cannot be used without remission domain
// Recommended approach:
//   1. Create abstract DefaultBranchProvider in availability domain
//   2. Inject provider instead of concrete BranchService
//   3. Implement RemissionBranchProvider at app level
//   4. Benefits: Domain independence, better testability, cleaner boundaries
// See: docs/architecture/domain-boundaries.md (if exists)
import {
  BranchService,
  RemissionStockService,
} from '@isa/remission/data-access';
import { SupplierService } from '@isa/checkout/data-access';
import {
  GetAvailabilityParamsSchema,
  GetAvailabilityInputParams,
  GetInStoreAvailabilityParams,
  GetPickupAvailabilityParams,
  GetDeliveryAvailabilityParams,
  GetDigDeliveryAvailabilityParams,
  GetB2bDeliveryAvailabilityParams,
  GetDownloadAvailabilityParams,
  GetSingleItemAvailabilityParamsSchema,
  GetSingleItemAvailabilityInputParams,
} from '../schemas';
import { Availability } from '../models';
import { AvailabilityRequestAdapter } from '../adapters/availability-request.adapter';
import {
  transformAvailabilitiesToDictionary,
  transformAvailabilitiesToDictionaryWithFieldFilter,
  transformDownloadAvailabilitiesToDictionary,
  transformStockToAvailability,
  executeAvailabilityApiCall,
  convertSingleItemToBatchParams,
  extractItemIdFromSingleParams,
} from '../helpers';

/**
 * Service for checking product availability across multiple order types.
 *
 * Supports:
 * - InStore (Rücklage): Branch-based in-store availability
 * - Pickup (Abholung): Branch-based pickup availability
 * - Delivery (Versand): Standard shipping availability
 * - DIG-Versand: Digital shipping for webshop customers
 * - B2B-Versand: Business-to-business shipping with logistician override
 * - Download: Digital download availability
 */
@Injectable({ providedIn: 'root' })
export class AvailabilityService {
  #stockService = inject(RemissionStockService);
  #availabilityService = inject(GeneratedAvailabilityService);
  #logisticianService = inject(LogisticianService);
  #branchService = inject(BranchService);
  #supplierService = inject(SupplierService);
  #logger = logger(() => ({ service: 'AvailabilityService' }));

  /**
   * Checks availability for multiple items based on order type.
   *
   * @param params Availability parameters (will be validated with Zod)
   * @param abortSignal Optional abort signal for request cancellation
   * @returns Dictionary mapping itemId to Availability
   */
  async getAvailabilities(
    params: GetAvailabilityInputParams,
    abortSignal?: AbortSignal,
  ): Promise<{ [itemId: string]: Availability }> {
    // Validate params with Zod schema
    const validated = GetAvailabilityParamsSchema.parse(params);

    this.#logger.info('Checking availability', () => ({ params }));
    // Route to appropriate handler based on order type
    switch (validated.orderType) {
      case 'Rücklage':
        return this.#getInStoreAvailability(validated, abortSignal);
      case 'Abholung':
        return this.#getPickupAvailability(validated, abortSignal);
      case 'Versand':
        return this.#getDeliveryAvailability(validated, abortSignal);
      case 'DIG-Versand':
        return this.#getDigDeliveryAvailability(validated, abortSignal);
      case 'B2B-Versand':
        return this.#getB2bDeliveryAvailability(validated, abortSignal);
      case 'Download':
        return this.#getDownloadAvailability(validated, abortSignal);
      default: {
        const _exhaustive: never = validated;
        throw new Error(
          `Unsupported order type: ${JSON.stringify(_exhaustive)}`,
        );
      }
    }
  }

  /**
   * Checks availability for a single item.
   *
   * This is more practical than getAvailabilities when you only need to check one item,
   * as it avoids array wrapping and dictionary extraction.
   *
   * @param params Single item availability parameters (will be validated with Zod)
   * @param abortSignal Optional abort signal for request cancellation
   * @returns Availability for the item, or undefined if not available
   */
  async getAvailability(
    params: GetSingleItemAvailabilityInputParams,
    abortSignal?: AbortSignal,
  ): Promise<Availability | undefined> {
    // Validate single-item params with Zod schema
    const validated = GetSingleItemAvailabilityParamsSchema.parse(params);

    this.#logger.info('Checking availability for single item', () => validated);

    // Convert to batch format and call batch method
    const batchParams = convertSingleItemToBatchParams(validated);
    const results = await this.getAvailabilities(batchParams, abortSignal);

    // Extract and return the single item result
    const itemId = extractItemIdFromSingleParams(validated);
    return results[itemId];
  }

  // TODO: [Service Refactoring - High Priority] Eliminate order type handler duplication
  // Current: 6 nearly identical methods, 180+ lines duplicated (Complexity: 7/10)
  // Target: Template Method + Strategy pattern with handler registry
  //
  // Proposed architecture:
  //   1. Create AvailabilityHandler interface:
  //      - prepareRequest(params): AvailabilityRequestDTO[]
  //      - getEndpoint(service): Observable
  //      - requiresSpecialHandling(): boolean
  //      - postProcess?(availabilities): Promise<Dict<Availability>>
  //
  //   2. Implement concrete handlers:
  //      - StandardShippingHandler (Versand, DIG-Versand, B2B-Versand)
  //      - StoreAvailabilityHandler (Rücklage, Abholung)
  //      - B2bHandler (extends Store, adds logistician post-processing)
  //      - DownloadHandler (extends Standard, adds validation)
  //
  //   3. Registry pattern:
  //      - #handlers = new Map<OrderType, AvailabilityHandler>()
  //      - Single executeHandler() method with common workflow
  //
  //   4. Special cases use post-processing hook:
  //      - B2B: Override logisticianId
  //      - Download: Validate availability status
  //
  // Benefits:
  //   - Eliminates 180+ lines of duplication
  //   - Bug fixes apply to all order types automatically
  //   - Easy to add new order types (implement handler interface)
  //   - Clear separation: request prep → execution → post-processing
  //   - Single place for error handling and logging
  //
  // Effort: ~6 hours | Impact: High | Risk: Medium
  // See: complexity-analysis.md (Code Review Section 2, Option 1)
  /**
   * InStore availability - uses store endpoint with branch context
   */
  async #getInStoreAvailability(
    params: GetInStoreAvailabilityParams,
    abortSignal?: AbortSignal,
  ): Promise<{ [itemId: string]: Availability }> {
    const stock = params.branchId
      ? await this.#stockService.fetchStock(params.branchId, abortSignal)
      : undefined;

    if (!stock?.id) {
      this.#logger.warn(
        'Branch has no stock ID, cannot fetch in-store availability',
        () => ({ branchId: params.branchId }),
      );
      return {};
    }

    // Fetch supplier and stock info in parallel
    const [supplier, stockInfos] = await Promise.all([
      this.#supplierService.getTakeAwaySupplier(abortSignal),
      this.#stockService.fetchStockInfos(
        { itemIds: params.itemsIds, stockId: stock.id },
        abortSignal,
      ),
    ]);

    return transformStockToAvailability(stockInfos, params.itemsIds, supplier);
  }

  /**
   * Pickup availability - uses store endpoint with branch context
   */
  async #getPickupAvailability(
    params: GetPickupAvailabilityParams,
    abortSignal?: AbortSignal,
  ): Promise<{ [itemId: string]: Availability }> {
    const request = AvailabilityRequestAdapter.toPickupRequest(params);

    const availabilities = await executeAvailabilityApiCall(
      this.#availabilityService.AvailabilityStoreAvailability(request),
      abortSignal,
      {
        orderType: 'Abholung',
        itemIds: params.items.map((i) => i.itemId),
        additional: { branchId: params.branchId },
      },
    );

    const result = transformAvailabilitiesToDictionary(
      (availabilities || []) as Availability[],
      params.items,
    );

    return result;
  }

  /**
   * Standard delivery availability - uses shipping endpoint
   *
   * Note: Uses special transformation that excludes supplier/logistician fields
   * to prevent backend from auto-changing orderType to "DIG-Versand"
   */
  async #getDeliveryAvailability(
    params: GetDeliveryAvailabilityParams,
    abortSignal?: AbortSignal,
  ): Promise<{ [itemId: string]: Availability }> {
    const request = AvailabilityRequestAdapter.toDeliveryRequest(params);

    const availabilities = await executeAvailabilityApiCall(
      this.#availabilityService.AvailabilityShippingAvailability(request),
      abortSignal,
      {
        orderType: 'Versand',
        itemIds: params.items.map((i) => i.itemId),
      },
    );

    const result = transformAvailabilitiesToDictionaryWithFieldFilter(
      (availabilities || []) as Availability[],
      params.items,
    );

    return result;
  }

  /**
   * DIG delivery availability - uses shipping endpoint (same as standard delivery)
   */
  async #getDigDeliveryAvailability(
    params: GetDigDeliveryAvailabilityParams,
    abortSignal?: AbortSignal,
  ): Promise<{ [itemId: string]: Availability }> {
    const request = AvailabilityRequestAdapter.toDigDeliveryRequest(params);

    const availabilities = await executeAvailabilityApiCall(
      this.#availabilityService.AvailabilityShippingAvailability(request),
      abortSignal,
      {
        orderType: 'DIG-Versand',
        itemIds: params.items.map((i) => i.itemId),
      },
    );

    const result = transformAvailabilitiesToDictionary(
      (availabilities || []) as Availability[],
      params.items,
    );

    return result;
  }

  /**
   * B2B delivery availability - uses store endpoint with logistician override
   *
   * Special handling:
   * - Fetches default branch automatically (no shopId required in params)
   * - Fetches logistician '2470'
   * - Uses store availability API (not shipping)
   * - Overrides logistician in response
   */
  async #getB2bDeliveryAvailability(
    params: GetB2bDeliveryAvailabilityParams,
    abortSignal?: AbortSignal,
  ): Promise<{ [itemId: string]: Availability }> {
    // Fetch default branch and logistician in parallel
    const [defaultBranch, logistician] = await Promise.all([
      this.#branchService.getDefaultBranch(abortSignal),
      this.#getLogistician2470(abortSignal),
    ]);

    if (!defaultBranch?.id) {
      const error = new Error('Default branch has no ID');
      this.#logger.error('Failed to get default branch for B2B', error);
      throw error;
    }

    const request = AvailabilityRequestAdapter.toB2bRequest(
      params,
      defaultBranch.id,
    );

    const apiAvailabilities = await executeAvailabilityApiCall(
      this.#availabilityService.AvailabilityStoreAvailability(request),
      abortSignal,
      {
        orderType: 'B2B-Versand',
        itemIds: params.items.map((i) => i.itemId),
        additional: { shopId: defaultBranch.id },
      },
    );

    const result = transformAvailabilitiesToDictionary(
      (apiAvailabilities || []) as Availability[],
      params.items,
    );

    // Override logistician for all availabilities
    if (logistician.id !== undefined) {
      Object.values(result).forEach((availability) => {
        if (availability) {
          availability.logisticianId = logistician.id;
        }
      });
    } else {
      this.#logger.warn('Logistician 2470 has no ID, cannot override', () => ({
        logistician,
      }));
    }

    return result;
  }

  /**
   * Download availability - uses shipping endpoint with quantity forced to 1
   *
   * Special validation:
   * - Supplier ID 16 with 0 stock = unavailable
   * - Must have valid availability type code
   */
  async #getDownloadAvailability(
    params: GetDownloadAvailabilityParams,
    abortSignal?: AbortSignal,
  ): Promise<{ [itemId: string]: Availability }> {
    const request = AvailabilityRequestAdapter.toDownloadRequest(params);

    const availabilities = await executeAvailabilityApiCall(
      this.#availabilityService.AvailabilityShippingAvailability(request),
      abortSignal,
      {
        orderType: 'Download',
        itemIds: params.items.map((i) => i.itemId),
      },
    );

    const result = transformDownloadAvailabilitiesToDictionary(
      (availabilities || []) as Availability[],
      params.items,
    );

    return result;
  }

  /**
   * Fetches logistician '2470' for B2B availability.
   * Delegates to LogisticianService which handles caching.
   */
  async #getLogistician2470(abortSignal?: AbortSignal): Promise<Logistician> {
    return this.#logisticianService.getLogistician2470(abortSignal);
  }
}
