import { inject, Injectable } from '@angular/core';
import {
  ReceiptService,
  ReturnReceiptValuesDTO,
} from '@generated/swagger/oms-api';
import { ReturnReceiptValues, ReturnReceiptValuesSchema } from '../schemas';
import { debounceTime, firstValueFrom, map } from 'rxjs';
import { CanReturn, ReturnProcess } from '../models';
import {
  allReturnProcessQuestionsAnswered,
  getReturnProcessQuestions,
  returnReceiptValuesMapping,
} from '../helpers/return-process';
import { isReturnProcessTypeGuard } from '../guards';
import { takeUntilAborted } from '@isa/common/data-access';

/**
 * Service for determining if a return process can proceed based on
 * provided process data or mapped receipt values.
 *
 * - Validates input using Zod schemas.
 * - Handles both ReturnProcess and ReturnReceiptValues as input.
 * - Calls backend API to check if return is possible.
 *
 * @remarks
 * - Uses memoization to cache results for identical inputs.
 * - Throws errors for invalid payloads or failed API calls.
 */
@Injectable({ providedIn: 'root' })
export class ReturnCanReturnService {
  #receiptService = inject(ReceiptService);

  /**
   * Determines if a return process can proceed.
   *
   * @param returnProcess - The return process object to evaluate.
   * @returns A promise resolving to a CanReturn result or undefined if the process should continue.
   */
  async canReturn(
    returnProcess: ReturnProcess,
    abortSignal?: AbortSignal,
  ): Promise<CanReturn | undefined>;
  /**
   * Determines if a return can proceed based on mapped receipt values.
   *
   * @param returnValues - The mapped return receipt values.
   * @returns A promise resolving to a CanReturn result.
   */
  async canReturn(
    returnValues: ReturnReceiptValues,
    abortSignal?: AbortSignal,
  ): Promise<CanReturn>;

  /**
   * Determines if a return can proceed, accepting either a ReturnProcess or ReturnReceiptValues.
   *
   * @param input - The return process or mapped receipt values.
   * @returns A promise resolving to a CanReturn result or undefined if the process should continue.
   * @throws Error if payload validation fails or the backend call fails.
   */
  async canReturn(
    input: ReturnProcess | ReturnReceiptValues,
    abortSignal?: AbortSignal,
  ): Promise<CanReturn | undefined> {
    let data: ReturnReceiptValues | undefined = undefined;

    if (isReturnProcessTypeGuard(input)) {
      data = this._canReturnFromReturnProcess(input);
    } else {
      data = this._canReturnFromReturnReceiptValues(input);
    }

    if (!data) {
      return undefined; // Prozess soll weitergehen, daher kein Error
    }

    let req$ = this.#receiptService.ReceiptCanReturn(
      data as ReturnReceiptValuesDTO,
    );

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

    try {
      return await firstValueFrom(
        req$.pipe(
          debounceTime(50),
          map((res) => res as CanReturn),
        ),
      );
    } catch (error) {
      throw new Error(`ReceiptCanReturn failed: ${String(error)}`);
    }
  }

  /**
   * Maps a ReturnProcess to ReturnReceiptValues and validates the payload.
   *
   * @param returnProcess - The return process to map and validate.
   * @returns The validated ReturnReceiptValues, or undefined if questions are unanswered.
   * @throws Error if payload validation fails.
   */
  private _canReturnFromReturnProcess(
    returnProcess: ReturnProcess,
  ): ReturnReceiptValues | undefined {
    if (!returnProcess) {
      throw new Error('No return process provided');
    }

    const questions = getReturnProcessQuestions(returnProcess);

    if (!questions) {
      return undefined; // Prozess soll weitergehen, daher kein Error
    }

    const allQuestionsAnswered = allReturnProcessQuestionsAnswered({
      questions,
      answers: returnProcess.answers,
    });

    if (!allQuestionsAnswered) {
      return undefined; // Prozess soll weitergehen, daher kein Error
    }

    const returnReceiptValues = returnReceiptValuesMapping(
      returnProcess,
    ) as ReturnReceiptValues;

    const parsedPayload =
      ReturnReceiptValuesSchema.safeParse(returnReceiptValues);

    if (!parsedPayload.success) {
      throw new Error('CanReturn Payload validation failed');
    }

    return parsedPayload.data;
  }

  /**
   * Validates the provided ReturnReceiptValues payload.
   *
   * @param returnReceiptValues - The values to validate.
   * @returns The validated ReturnReceiptValues.
   * @throws Error if payload validation fails.
   */
  private _canReturnFromReturnReceiptValues(
    returnReceiptValues: ReturnReceiptValues,
  ): ReturnReceiptValues | undefined {
    const parsedPayload =
      ReturnReceiptValuesSchema.safeParse(returnReceiptValues);

    if (!parsedPayload.success) {
      throw new Error('CanReturn Payload validation failed');
    }

    return parsedPayload.data;
  }
}
