import { inject, Injectable } from '@angular/core';
import { StockService } from '@generated/swagger/inventory-api';
import { firstValueFrom } from 'rxjs';
import { Stock, StockInfo } from '../models';
import { FetchStockInStock, FetchStockInStockSchema } from '../schemas';
import { ResponseArgsError, takeUntilAborted } from '@isa/common/data-access';
import { logger } from '@isa/core/logging';
import { InFlight, Cache, CacheTimeToLive } from '@isa/common/decorators';

/**
 * Service responsible for managing remission stock operations.
 * Handles fetching assigned stock and stock information from the inventory API.
 *
 * @class RemissionStockService
 * @injectable
 *
 * @example
 * // Inject the service
 * constructor(private stockService: RemissionStockService) {}
 *
 * // Fetch assigned stock
 * const assignedStock = await this.stockService.fetchAssignedStock();
 *
 * // Fetch stock info for specific items
 * const stockInfo = await this.stockService.fetchStock({
 *   assignedStockId: 'stock123',
 *   itemIds: ['item1', 'item2']
 * });
 */
@Injectable({ providedIn: 'root' })
export class RemissionStockService {
  #stockService = inject(StockService);
  #logger = logger(() => ({ service: 'RemissionStockService' }));

  /**
   * Fetches the currently assigned stock for the user.
   * Results are cached in memory to reduce API calls.
   *
   * @async
   * @param {AbortSignal} [abortSignal] - Optional signal to abort the request
   * @returns {Promise<Stock>} The assigned stock object
   * @throws {ResponseArgsError} When the API request fails or returns an error
   *
   * @example
   * const controller = new AbortController();
   * try {
   *   const stock = await service.fetchAssignedStock(controller.signal);
   *   console.log('Assigned stock:', stock.id);
   * } catch (error) {
   *   console.error('Failed to fetch assigned stock:', error);
   * }
   *
   * @todo Remove caching from data-access services
   */
  @Cache({ ttl: CacheTimeToLive.fiveMinutes })
  @InFlight()
  async fetchAssignedStock(abortSignal?: AbortSignal): Promise<Stock> {
    this.#logger.info('Fetching assigned stock from API');
    let req$ = this.#stockService.StockCurrentStock();

    if (abortSignal) {
      this.#logger.debug('Request configured with abort signal');
      req$ = req$.pipe(takeUntilAborted(abortSignal));
    }

    const res = await firstValueFrom(req$);

    if (res.error || !res.result) {
      this.#logger.error(
        'Failed to fetch assigned stock',
        new Error(res.message || 'Unknown error'),
      );
      throw new ResponseArgsError(res);
    }

    const result = res.result;
    if (result.id === undefined) {
      const error = new Error('Assigned stock has no ID');
      this.#logger.error('Invalid stock response', error);
      throw error;
    }

    this.#logger.debug('Successfully fetched assigned stock', () => ({
      stockId: result.id,
    }));

    // TypeScript cannot narrow StockDTO to Stock based on the id check above,
    // so we use a minimal type assertion after runtime validation
    return result as Stock;
  }

  async fetchStock(
    branchId: number,
    abortSignal?: AbortSignal,
  ): Promise<Stock | undefined> {
    let req$ = this.#stockService.StockGetStocks();

    if (abortSignal) {
      this.#logger.debug('Request configured with abort signal');
      req$ = req$.pipe(takeUntilAborted(abortSignal));
    }

    const res = await firstValueFrom(req$);

    if (res.error || !res.result) {
      this.#logger.error(
        'Failed to fetch stocks',
        new Error(res.message || 'Unknown error'),
      );
      throw new ResponseArgsError(res);
    }

    const stock = res.result.find((s) => s.branch?.id === branchId);
    if (!stock) {
      return undefined;
    }

    if (stock.id === undefined) {
      this.#logger.warn('Found stock without ID for branch', () => ({ branchId }));
      return undefined;
    }

    // TypeScript cannot narrow StockDTO to Stock based on the id check above,
    // so we use a minimal type assertion after runtime validation
    return stock as Stock;
  }

  /**
   * Fetches stock information for specific items in the assigned stock.
   * Validates input parameters using FetchStockInStockSchema.
   *
   * @async
   * @param {FetchStockInStock} params - Parameters for the stock query
   * @param {string} params.assignedStockId - ID of the assigned stock
   * @param {string[]} params.itemIds - Array of item IDs to fetch stock info for
   * @param {AbortSignal} [abortSignal] - Optional signal to abort the request
   * @returns {Promise<StockInfo[]>} Array of stock information for the requested items
   * @throws {ResponseArgsError} When the API request fails or returns an error
   * @throws {z.ZodError} When parameter validation fails
   *
   * @example
   * const stockInfo = await service.fetchStock({
   *   assignedStockId: 'stock123',
   *   itemIds: ['item1', 'item2', 'item3']
   * });
   *
   * stockInfo.forEach(info => {
   *   console.log(`Item ${info.itemId}: ${info.quantity} in stock`);
   * });
   */
  async fetchStockInfos(
    params: FetchStockInStock,
    abortSignal?: AbortSignal,
  ): Promise<StockInfo[]> {
    this.#logger.debug('Fetching stock info', () => ({ params }));
    const parsed = FetchStockInStockSchema.parse(params);

    let assignedStockId: number;

    if ('stockId' in parsed && parsed.stockId) {
      assignedStockId = parsed.stockId;
    } else if ('branchId' in parsed) {
      const stock = await this.fetchStock(parsed.branchId, abortSignal);
      if (!stock) {
        this.#logger.warn('No stock found for branch', () => ({
          branchId: parsed.branchId,
        }));
        return [];
      }
      assignedStockId = stock.id;
    } else {
      assignedStockId = await this.fetchAssignedStock(abortSignal).then(
        (s) => s.id,
      );
    }

    this.#logger.info('Fetching stock info from API', () => ({
      stockId: assignedStockId,
      itemCount: parsed.itemIds.length,
    }));

    let req$ = this.#stockService.StockInStock({
      stockId: assignedStockId,
      articleIds: parsed.itemIds,
    });

    if (abortSignal) {
      this.#logger.debug('Request configured with abort signal');
      req$ = req$.pipe(takeUntilAborted(abortSignal));
    }

    const res = await firstValueFrom(req$);

    if (res.error || !res.result) {
      this.#logger.error(
        'Failed to fetch stock info',
        new Error(res.message || 'Unknown error'),
      );
      throw new ResponseArgsError(res);
    }

    this.#logger.debug('Successfully fetched stock info', () => ({
      itemCount: res.result?.length || 0,
    }));

    return res.result as StockInfo[];
  }
}
