import { inject, Injectable } from '@angular/core';
import {
  EligibleForReturn,
  EligibleForReturnState,
  Receipt,
  ReturnInfo,
  ReturnProcess,
  ReturnProcessAnswers,
  ReturnProcessQuestion,
  ReturnProcessQuestionType,
} from '../models';
import { ProductCategory } from '../questions';
import {
  ReturnProcessChecklistAnswerSchema,
  ReturnReceiptValues,
  ReturnReceiptValuesSchema,
} from '../schemas';
import { logger } from '@isa/core/logging';
import {
  PropertyIsEmptyError,
  PropertyNullOrUndefinedError,
} from '@isa/common/data-access';
import { ReturnProcessIsNotCompleteError } from '../errors/return-process';
import {
  activeReturnProcessQuestions,
  allReturnProcessQuestionsAnswered,
  calculateLongestQuestionDepth,
  getReturnInfo,
  getReturnProcessQuestions,
  isElektronischeGeraeteEligibleForReturn,
  isTolinoEligibleForReturn,
  isTonDatentraegerEligibleForReturn,
  returnReceiptValuesMapping,
} from '../helpers/return-process';
import { isEmpty, isNil } from 'lodash';
import {
  ReceiptService,
  ReturnReceiptValuesDTO,
} from '@generated/swagger/oms-api';
import { firstValueFrom } from 'rxjs';
import { PrintReceiptsService } from './print-receipts.service';
import { z } from 'zod';

/**
 * Service responsible for managing the return process workflow.
 * Handles questions, validation, and eligibility determination for product returns.
 * Uses Zod schemas for data validation throughout the return process.
 */
@Injectable({ providedIn: 'root' })
export class ReturnProcessService {
  #logger = logger();

  #receiptService = inject(ReceiptService);
  #printReceiptsService = inject(PrintReceiptsService);

  /**
   * Gets active questions in the return process based on previously provided answers.
   * Handles question branching logic and detects cyclic dependencies.
   *
   * This method:
   * 1. Determines which questions to show based on previous answers
   * 2. Validates answers using Zod schemas to ensure data integrity
   * 3. Handles different question types (Select, Product, Checklist)
   * 4. Follows the branching logic defined by nextQuestion properties
   *
   * @param {ReturnProcess} process - The return process containing answers and product information.
   * @returns {ReturnProcessQuestion[] | undefined} Active questions in the process or undefined if no questions apply.
   * @throws {Error} If cyclic question dependencies are detected in the question flow.
   */
  activeReturnProcessQuestions(
    process: ReturnProcess,
  ): ReturnProcessQuestion[] | undefined {
    const questions = getReturnProcessQuestions(process);
    const answers: ReturnProcessAnswers = process.answers || {};

    if (!questions) {
      throw new PropertyNullOrUndefinedError('questions');
    }

    if (!answers) {
      throw new PropertyNullOrUndefinedError('answers');
    }

    return activeReturnProcessQuestions(questions, answers);
  }

  /**
   * Calculates the progress of the return process questions.
   * Returns a tuple with the current question index and the total number of questions.
   *
   * @param {ReturnProcess} returnProcess - The return process containing answers and product information.
   * @returns {{ answered: number; total: number } | undefined} Progress information or undefined if no questions apply.
   */
  returnProcessQuestionsProgress(
    returnProcess: ReturnProcess,
  ): { answered: number; total: number } | undefined {
    const questions = getReturnProcessQuestions(returnProcess);

    if (!questions) {
      return undefined;
    }

    const activeQuestions = this.activeReturnProcessQuestions(returnProcess);

    if (!activeQuestions) {
      return undefined;
    }

    // Unterstützt nun auch Gruppenfragen (siehe Tolino)
    const answered = activeQuestions.reduce((acc, q) => {
      if (q.type === ReturnProcessQuestionType.Group) {
        // Group-Frage: zähle alle Unterfragen, die schon eine Antwort haben
        const subAnswered = q.questions.reduce((subAcc, subQ) => {
          return subQ.key in returnProcess.answers ? subAcc + 1 : subAcc;
        }, 0);
        return acc + subAnswered;
      }

      // Einzel-Frage: ganz normal zählen
      return q.key in returnProcess.answers ? acc + 1 : acc;
    }, 0);

    const totalCount = calculateLongestQuestionDepth(
      questions,
      returnProcess.answers,
    );

    return { answered, total: totalCount };
  }

  /**
   * Determines whether a product is eligible for return based on provided answers.
   * Uses schema validation to verify answers and determines eligibility status.
   *
   * The method has been refactored to:
   * 1. Use schema validation instead of custom validator functions
   * 2. Return undefined if questions are not answered completely
   * 3. Always return eligible status for complete and validated answers
   *
   * @param {ReturnProcess} returnProcess - The return process containing answers and product information.
   * @returns {EligibleForReturn | undefined} Object indicating eligibility state or undefined if answers incomplete.
   */
  eligibleForReturn(
    returnProcess: ReturnProcess,
  ): EligibleForReturn | undefined {
    if (
      !returnProcess?.productCategory ||
      returnProcess.productCategory === 'unknown'
    ) {
      return { state: EligibleForReturnState.Unknown };
    }

    const questions = this.activeReturnProcessQuestions(returnProcess);

    if (!questions) {
      return undefined;
    }

    // Use the helper method to check if all questions are answered
    const allQuestionsAnswered = this._areAllQuestionsAnswered(
      questions,
      returnProcess,
    );

    if (!allQuestionsAnswered) {
      return undefined;
    }

    switch (returnProcess.productCategory) {
      case ProductCategory.ElektronischeGeraete:
        return isElektronischeGeraeteEligibleForReturn(
          returnProcess,
          questions,
        );
      case ProductCategory.TonDatentraeger:
        return isTonDatentraegerEligibleForReturn(returnProcess, questions);
      case ProductCategory.Tolino:
        return isTolinoEligibleForReturn(returnProcess, questions);
    }

    return { state: EligibleForReturnState.Eligible };
  }

  /**
   * Checks if all provided questions have valid answers within the return process.
   * Handles specific logic for different question types like Checklist.
   *
   * @param {ReturnProcessQuestion[]} questions - The list of questions to check.
   * @param {ReturnProcess} returnProcess - The return process containing the answers.
   * @returns {boolean} True if all questions are answered correctly, false otherwise.
   * @private
   */
  private _areAllQuestionsAnswered(
    questions: ReturnProcessQuestion[],
    returnProcess: ReturnProcess,
  ): boolean {
    return questions.every((q) => {
      switch (q.type) {
        case ReturnProcessQuestionType.Checklist: {
          // Validate Checklist answers: must have options selected or 'other' text filled
          const answer = ReturnProcessChecklistAnswerSchema.optional().parse(
            returnProcess.answers[q.key],
          );
          return Boolean(
            answer && (answer.options?.length > 0 || answer.other),
          );
        }

        case ReturnProcessQuestionType.Info: {
          return true; // Info questions are always considered answered
        }

        case ReturnProcessQuestionType.Group: {
          // Group: nicht q.key selbst, sondern alle Unter-Fragen prüfen
          return this._areAllQuestionsAnswered(q.questions, returnProcess);
        }

        default: {
          // For other types, simply check if an answer exists for the question key
          return q.key in returnProcess.answers;
        }
      }
    });
  }

  /**
   * Retrieves consolidated return information from a return process.
   *
   * @param process - The return process to extract information from.
   * @returns The consolidated return information or undefined if unable to generate.
   * @throws {PropertyNullOrUndefinedError} If questions cannot be found.
   */
  getReturnInfo(process: ReturnProcess): ReturnInfo | undefined {
    const questions = getReturnProcessQuestions(process);
    if (!questions) {
      throw new PropertyNullOrUndefinedError('questions');
    }
    const answers = process.answers || {};

    return getReturnInfo({
      questions,
      answers,
    });
  }

  /**
   * Completes the return process for the provided return processes and generates receipts.
   *
   * This method:
   * 1. Validates that all processes are complete with all required questions answered
   * 2. Filters out processes that are not eligible for return
   * 3. Creates a payload with return information for each eligible process
   * 4. Validates the payload using Zod schema
   * 5. Submits the return request to the receipt service
   * 6. Triggers printing of the return receipts
   *
   * @param {ReturnProcess[]} processes - An array of return processes to complete
   * @returns {Promise<Receipt[]>} A promise that resolves to the generated receipts
   * @throws {PropertyNullOrUndefinedError} If processes is null or undefined
   * @throws {PropertyIsEmptyError} If processes array is empty
   * @throws {PropertyNullOrUndefinedError} If questions for a process cannot be found
   * @throws {ReturnProcessIsNotCompleteError} If any process has unanswered questions
   * @throws {PropertyNullOrUndefinedError} If returnInfo cannot be generated for a process
   */
  async completeReturnProcess(processes: ReturnProcess[]): Promise<Receipt[]> {
    if (isNil(processes)) {
      throw new PropertyNullOrUndefinedError('processes');
    }
    if (isEmpty(processes)) {
      throw new PropertyIsEmptyError('processes');
    }

    const incompleteProcess = processes.find((process) => {
      const questions = getReturnProcessQuestions(process);
      if (!questions) {
        throw new PropertyNullOrUndefinedError('questions');
      }
      return !allReturnProcessQuestionsAnswered({
        questions,
        answers: process.answers,
      });
    });

    if (incompleteProcess) {
      const error = new ReturnProcessIsNotCompleteError(
        incompleteProcess.processId,
        incompleteProcess.id,
      );
      this.#logger.error('Incomplete return process detected', error);
      throw error;
    }

    // Filter eligible processes
    const eligibleProcesses = processes.filter((p) => {
      return (
        this.eligibleForReturn(p)?.state === EligibleForReturnState.Eligible
      );
    });

    const payload: ReturnReceiptValues[] = eligibleProcesses.map(
      (process) => returnReceiptValuesMapping(process) as ReturnReceiptValues,
    );

    const parsedPayload = z.array(ReturnReceiptValuesSchema).safeParse(payload);

    if (!parsedPayload.success) {
      this.#logger.error('Payload validation failed', parsedPayload.error);
      return [];
    }

    const response = await firstValueFrom(
      this.#receiptService.ReceiptCreateReturnReceipt({
        payload: parsedPayload.data as ReturnReceiptValuesDTO[],
      }),
    );

    const receipts = response.result as Receipt[];
    const receiptIds = receipts.map((receipt) => receipt.id);
    await this.#printReceiptsService.printReturnReceipts({
      returnReceiptIds: receiptIds,
    });

    return receipts;
  }
}
