import { inject, Injectable } from '@angular/core';
import {
  QuerySettings,
  RemissionListType,
  RemissionListTypeKey,
  ReturnItem,
  ReturnSuggestion,
  ValueTupleOfStringAndInteger,
} from '../models';
import { RemiService } from '@generated/swagger/inventory-api';
import {
  FetchQuerySettings,
  FetchQuerySettingsSchema,
  FetchRequiredCapacity,
  FetchRequiredCapacitySchema,
  RemissionQueryTokenInput,
  RemissionQueryTokenSchema,
} from '../schemas';
import { firstValueFrom } from 'rxjs';
import {
  BatchResponseArgs,
  ListResponseArgs,
  ResponseArgsError,
  takeUntilAborted,
} from '@isa/common/data-access';
import { logger } from '@isa/core/logging';
import { Item } from '@isa/catalogue/data-access';
import { RemissionStockService } from './remission-stock.service';
import { getAssortmentFromItem, getRetailPriceFromItem } from '../helpers';

/**
 * Service responsible for remission search operations.
 * Handles fetching remission lists, query settings, and managing remission list types.
 *
 * @class RemissionSearchService
 * @injectable
 *
 * @example
 * // Inject the service
 * constructor(private searchService: RemissionSearchService) {}
 *
 * // Get available remission list types
 * const listTypes = this.searchService.remissionListType();
 *
 * // Fetch remission list
 * const items = await this.searchService.fetchList({
 *   assignedStockId: 'stock123',
 *   supplierId: 'supplier456',
 *   take: 20,
 *   skip: 0
 * });
 */
@Injectable({ providedIn: 'root' })
export class RemissionSearchService {
  #remiService = inject(RemiService);
  #remiStockService = inject(RemissionStockService);

  #logger = logger(() => ({ service: 'RemissionSearchService' }));

  /**
   * Returns all available remission list types as key-value pairs.
   * This method provides a mapping between RemissionListTypeKey and RemissionListType values.
   *
   * @returns {Array<{key: RemissionListTypeKey, value: RemissionListType}>} Array of remission list type mappings
   *
   * @example
   * const types = service.remissionListType();
   * types.forEach(type => {
   *   console.log(`Type ${type.key}: ${type.value}`);
   * });
   */
  remissionListType(): Array<{
    key: RemissionListTypeKey;
    value: RemissionListType;
  }> {
    this.#logger.debug('Getting remission list types');
    return (Object.keys(RemissionListType) as RemissionListTypeKey[]).map(
      (key) => ({
        key,
        value: RemissionListType[key],
      }),
    );
  }

  /**
   * Fetches the required capacity for remission based on departments and supplier ID.
   * Validates input parameters using FetchRequiredCapacitySchema.
   *
   * @async
   * @param {FetchRequiredCapacity} params - Parameters for fetching required capacity
   * @param {string[]} params.departments - List of department names
   * @param {number} params.supplierId - ID of the supplier
   * @param {number} params.stockId - ID of the stock
   * @returns {Promise<ValueTupleOfStringAndInteger[]>} Required capacity data as an array of key-value pairs
   * @throws {Error} When the API request fails or returns an error
   * @throws {z.ZodError} When parameter validation fails
   *
   * @example
   * const capacity = await service.fetchRequiredCapacity({
   *   departments: ['Department1', 'Department2'],
   *   supplierId: 123,
   *   stockId: 456
   * });
   */
  async fetchRequiredCapacity(
    params: FetchRequiredCapacity,
  ): Promise<ValueTupleOfStringAndInteger[]> {
    this.#logger.debug('Fetching required capacity', () => ({ params }));
    const parsed = FetchRequiredCapacitySchema.parse(params);

    this.#logger.info('Fetching required capacity from API', () => ({
      stockId: parsed.stockId,
      departments: parsed.departments,
      supplierId: parsed.supplierId,
    }));

    const req$ = this.#remiService.RemiGetRequiredCapacities({
      stockId: parsed.stockId,
      payload: {
        departments: parsed.departments,
        supplierId: parsed.supplierId,
      },
    });

    const res = await firstValueFrom(req$);

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

    this.#logger.debug('Successfully fetched required capacity');
    return (res?.result ?? []) as ValueTupleOfStringAndInteger[];
  }

  /**
   * Fetches query settings for mandatory remission articles.
   * Validates input parameters using FetchQuerySettingsSchema.
   *
   * @async
   * @param {FetchQuerySettings} params - Parameters for the query settings request
   * @param {string} params.supplierId - ID of the supplier
   * @param {string} params.assignedStockId - ID of the assigned stock
   * @returns {Promise<QuerySettings>} Query settings for the specified supplier and stock
   * @throws {Error} When the API request fails or returns an error
   * @throws {z.ZodError} When parameter validation fails
   *
   * @example
   * const settings = await service.fetchQuerySettings({
   *   supplierId: 'supplier123',
   *   assignedStockId: 'stock456'
   * });
   */
  async fetchQuerySettings(params: FetchQuerySettings): Promise<QuerySettings> {
    this.#logger.debug('Fetching query settings', () => ({ params }));
    const parsed = FetchQuerySettingsSchema.parse(params);

    this.#logger.info('Fetching query settings from API', () => ({
      supplierId: parsed.supplierId,
      stockId: parsed.assignedStockId,
    }));

    const req$ = this.#remiService.RemiPflichtremissionsartikelSettings({
      supplierId: parsed.supplierId,
      stockId: parsed.assignedStockId,
    });

    const res = await firstValueFrom(req$);

    if (res.error || !res.result) {
      const error = new Error(res.message || 'Failed to fetch Query Settings');
      this.#logger.error('Failed to fetch query settings', error);
      throw error;
    }

    this.#logger.debug('Successfully fetched query settings');
    return res.result as QuerySettings;
  }

  /**
   * Fetches query settings for department overflow remission articles.
   * Validates input parameters using FetchQuerySettingsSchema.
   *
   * @async
   * @param {FetchQuerySettings} params - Parameters for the department query settings request
   * @param {string} params.supplierId - ID of the supplier
   * @param {string} params.assignedStockId - ID of the assigned stock
   * @returns {Promise<QuerySettings>} Department query settings for the specified supplier and stock
   * @throws {Error} When the API request fails or returns an error
   * @throws {z.ZodError} When parameter validation fails
   *
   * @example
   * const departmentSettings = await service.fetchQueryDepartmentSettings({
   *   supplierId: 'supplier123',
   *   assignedStockId: 'stock456'
   * });
   */
  async fetchQueryDepartmentSettings(
    params: FetchQuerySettings,
  ): Promise<QuerySettings> {
    this.#logger.debug('Fetching department query settings', () => ({
      params,
    }));
    const parsed = FetchQuerySettingsSchema.parse(params);

    this.#logger.info('Fetching department query settings from API', () => ({
      supplierId: parsed.supplierId,
      stockId: parsed.assignedStockId,
    }));

    const req$ = this.#remiService.RemiUeberlaufSettings({
      supplierId: parsed.supplierId,
      stockId: parsed.assignedStockId,
    });

    const res = await firstValueFrom(req$);

    if (res.error || !res.result) {
      const error = new Error(
        res.message || 'Failed to fetch Query Department Settings',
      );
      this.#logger.error('Failed to fetch department query settings', error);
      throw error;
    }

    this.#logger.debug('Successfully fetched department query settings');
    return res.result as QuerySettings;
  }

  /**
   * Fetches a paginated list of mandatory remission return items.
   * Validates input parameters using RemissionQueryTokenSchema.
   *
   * @async
   * @param {RemissionQueryTokenInput} params - Query parameters for the list request
   * @param {string} params.assignedStockId - ID of the assigned stock
   * @param {string} params.supplierId - ID of the supplier
   * @param {string} [params.filter] - Optional filter string
   * @param {Object} [params.input] - Optional input parameters for filtering
   * @param {string} [params.orderBy] - Optional field to order results by
   * @param {number} [params.take] - Number of items to fetch (pagination)
   * @param {number} [params.skip] - Number of items to skip (pagination)
   * @returns {Promise<ListResponseArgs<ReturnItem>>} Paginated list response with return items
   * @throws {Error} When the API request fails or returns an error
   * @throws {z.ZodError} When parameter validation fails
   *
   * @example
   * const response = await service.fetchList({
   *   assignedStockId: 'stock123',
   *   supplierId: 'supplier456',
   *   orderBy: 'itemName'
   * });
   * console.log(`Total items: ${response.totalCount}`);
   *
   * @todo After fetching, StockInStock should be called in the old DomainRemissionService
   */
  // TODO: Im alten DomainRemissionService wird danach StockInStock abgerufen
  async fetchList(
    params: RemissionQueryTokenInput,
    abortSignal?: AbortSignal,
  ): Promise<ListResponseArgs<ReturnItem>> {
    this.#logger.debug('Fetching remission list', () => ({ params }));
    const parsed = RemissionQueryTokenSchema.parse(params);

    this.#logger.info('Fetching remission list from API', () => ({
      stockId: parsed.assignedStockId,
      supplierId: parsed.supplierId,
    }));

    let req$ = this.#remiService.RemiPflichtremissionsartikel({
      queryToken: {
        stockId: parsed.assignedStockId,
        supplierId: parsed.supplierId,
        filter: parsed.filter,
        input: parsed.input,
        orderBy: parsed.orderBy,
      },
    });

    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) {
      const error = new Error(res.message || 'Failed to fetch Remission List');
      this.#logger.error('Failed to fetch remission list', error);
      throw error;
    }

    this.#logger.debug('Successfully fetched remission list', () => ({
      itemCount: res.result?.length || 0,
      totalCount: (res as any).totalCount,
    }));

    return res as ListResponseArgs<ReturnItem>;
  }

  /**
   * Fetches a paginated list of department overflow remission suggestions.
   * Validates input parameters using RemissionQueryTokenSchema.
   *
   * @async
   * @param {RemissionQueryTokenInput} params - Query parameters for the department list request
   * @param {string} params.assignedStockId - ID of the assigned stock
   * @param {string} params.supplierId - ID of the supplier
   * @param {string} [params.filter] - Optional filter string
   * @param {Object} [params.input] - Optional input parameters for filtering
   * @param {string} [params.orderBy] - Optional field to order results by
   * @param {number} [params.take] - Number of items to fetch (pagination)
   * @param {number} [params.skip] - Number of items to skip (pagination)
   * @returns {Promise<ListResponseArgs<ReturnSuggestion>>} Paginated list response with return suggestions
   * @throws {Error} When the API request fails or returns an error
   * @throws {z.ZodError} When parameter validation fails
   *
   * @example
   * const departmentResponse = await service.fetchDepartmentList({
   *   assignedStockId: 'stock123',
   *   supplierId: 'supplier456',
   * });
   *
   * @todo After fetching, StockInStock should be called in the old DomainRemissionService
   */
  async fetchDepartmentList(
    params: RemissionQueryTokenInput,
    abortSignal?: AbortSignal,
  ): Promise<ListResponseArgs<ReturnSuggestion>> {
    this.#logger.debug('Fetching department remission list', () => ({
      params,
    }));
    const parsed = RemissionQueryTokenSchema.parse(params);

    this.#logger.info('Fetching department remission list from API', () => ({
      stockId: parsed.assignedStockId,
      supplierId: parsed.supplierId,
    }));

    let req$ = this.#remiService.RemiUeberlauf({
      queryToken: {
        stockId: parsed.assignedStockId,
        supplierId: parsed.supplierId,
        filter: parsed.filter,
        input: parsed.input,
        orderBy: parsed.orderBy,
      },
    });

    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) {
      const error = new Error(
        res.message || 'Failed to fetch Remission Department List',
      );
      this.#logger.error('Failed to fetch department remission list', error);
      throw error;
    }

    this.#logger.debug(
      'Successfully fetched department remission list',
      () => ({
        suggestionCount: res.result?.length || 0,
        totalCount: (res as any).totalCount,
      }),
    );

    return res as ListResponseArgs<ReturnSuggestion>;
  }

  async canAddItemToRemiList(
    items: { item: Item; quantity: number; reason: string }[],
    abortSignal?: AbortSignal,
  ): Promise<BatchResponseArgs<ReturnItem>> {
    const stock = await this.#remiStockService.fetchAssignedStock(abortSignal);

    if (!stock) {
      this.#logger.error('No assigned stock found for remission items');
      throw new Error('No assigned stock found');
    }

    let req = this.#remiService.RemiCanAddReturnItem({
      data: items.map((i) => ({
        product: i.item.product,
        assortment: getAssortmentFromItem(i.item),
        predefinedReturnQuantity: i.quantity,
        retailPrice: getRetailPriceFromItem(i.item),
        source: 'manually-added',
        returnReason: i.reason,
        stock: { id: stock.id },
      })),
    });

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

    const res = await firstValueFrom(req);
    if (res.error) {
      const error = new ResponseArgsError(res);
      this.#logger.error('Failed to check item addition', error);
      throw error;
    }

    return res as BatchResponseArgs<ReturnItem>;
  }

  async addToList(
    items: { item: Item; quantity: number; reason: string }[],
    abortSignal?: AbortSignal,
  ): Promise<ReturnItem[]> {
    const stock = await this.#remiStockService.fetchAssignedStock(abortSignal);

    if (!stock) {
      this.#logger.error('No assigned stock found for remission items');
      throw new Error('No assigned stock found');
    }

    const req$ = this.#remiService.RemiCreateReturnItem({
      data: items.map((i) => ({
        product: {
          ...i.item.product,
          catalogProductNumber: String(i.item.id),
        },
        assortment: getAssortmentFromItem(i.item),
        predefinedReturnQuantity: i.quantity,
        retailPrice: getRetailPriceFromItem(i.item),
        source: 'manually-added',
        returnReason: i.reason,
        stock: { id: stock.id },
      })),
    });

    const res = await firstValueFrom(req$);

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

    return res.successful?.map((r) => r.value) as ReturnItem[];
  }
}
