import { useEffect, useMemo } from 'react';
import { type FieldValues, type UseFormReturn } from 'react-hook-form';
import { useQuery } from 'react-query';

import { useAuth } from 'src/contexts/JWTContext';
import { profileKeys } from 'src/pages/dashboard/profile/queries';
import { dynamicFieldsResponse, getProfileById } from 'src/services/users';
import { type Step } from 'src/types/forms';
import { getQuestionName } from 'src/utils/form';

import { panalabMonthlyLimitExpensesData } from './constants';
import {
  isPanalabSpecialForm,
  isPanalabSpecialLimitForm,
  isViaticumAmountField,
  isViaticumTotalAmountField,
} from './utils';

type Props = {
  formTitle: string;
  currentStep: Step;
  currentStepIdx: number;
  form: UseFormReturn<FieldValues>;
};

/**
 * Panalab-specific behavior for viaticum (expense report) forms.
 *
 * Only active on the Panalab instance (and the QA testing instance). For the
 * configured set of "special" expense forms it does two things per step:
 *   1. Sums the individual "Monto de X" amount fields and writes the result
 *      into the step's read-only "Monto Total de X" field so the user sees a
 *      live total.
 *   2. For the subset of forms that enforce a monthly cap, compares that total
 *      against the per-expense limit stored in the user's profile data and
 *      flags `invalidExpenseAmount` so the form layer can block submission.
 *
 * For non-Panalab instances or non-special forms this hook is effectively a
 * no-op and `invalidExpenseAmount` is always false.
 */
const usePanalabSpecialForm = ({
  formTitle,
  form,
  currentStep,
  currentStepIdx,
}: Props) => {
  const { instance, user } = useAuth();
  const { setValue, watch, getValues } = form;
  // Subscribing to all fields here is what causes this hook (and its consumer)
  // to re-render on every keystroke. `formValues` is also fed into the
  // `invalidExpenseAmount` deps below to force the limit check to re-run as
  // the user edits amount fields.
  const formValues = watch();
  const currentExpenseName = currentStep?.title;
  const isSpecialForm = isPanalabSpecialForm(instance!.id, formTitle);

  const { data: userData, isLoading: isLoadingUserData } = useQuery(
    ['userProfile', user!.id],
    () => getProfileById(user!.id),
  );

  const { data: profileFields, isLoading: isLoadingProfileFields } = useQuery(
    profileKeys.profileFields(instance!.id),
    () => dynamicFieldsResponse(instance!.id),
    {
      select: response => {
        return [
          ...Object.entries(response.data).map(([key, v]) => ({
            id: key,
            name: v.name,
          })),
        ];
      },
    },
  );

  // Names of every "Monto de X" input in the current step. The form layer
  // registers each question under a composite name
  // `${stepIdx}-${qIdx}-${questionName}-${stepId}`, so we reconstruct the same
  // key here in order to read/write the field via react-hook-form.
  const currentExpenseAmountFieldNames = useMemo(() => {
    const result: string[] = [];

    if (!currentStep || !isSpecialForm) return result;

    for (let i = 0; i < currentStep.questions.length; i++) {
      const question = currentStep.questions[i];

      if (isViaticumAmountField(question.description, instance!.id)) {
        const name = `${currentStepIdx}-${i}-${getQuestionName(question)}-${currentStep.id}`;
        result.push(name);
      }
    }
    return result;
  }, [currentStep, currentStepIdx, instance, isSpecialForm]);

  // Name of the read-only "Monto Total de X" field in the current step, if any.
  // We auto-fill this field with the sum of the amount fields above.
  const currentExpenseTotalAmountField = useMemo(() => {
    if (!currentStep || !isSpecialForm) return null;

    const totalFieldIndex = currentStep.questions.findIndex(q =>
      isViaticumTotalAmountField(q.description, instance!.id),
    );
    if (totalFieldIndex !== -1) {
      const totalField = currentStep.questions[totalFieldIndex];
      return `${currentStepIdx}-${totalFieldIndex}-${getQuestionName(totalField)}-${currentStep.id}`;
    }

    return null;
  }, [currentStep, isSpecialForm, currentStepIdx, instance]);

  // Translates the current step title (the human-readable expense name) into
  // the profile-field identifier that stores its monthly limit. The mapping
  // lives in `panalabMonthlyLimitExpensesData` and is the bridge between the
  // form step and the user's profile data.
  const currentProfileFieldExpenseName = useMemo(() => {
    if (!currentExpenseName) return null;
    return (
      panalabMonthlyLimitExpensesData.find(
        expense => expense.name === currentExpenseName,
      )?.profileFieldName || null
    );
  }, [currentExpenseName]);

  // Flattens the instance's dynamic profile fields and joins each one with the
  // value stored on the current user. Produces the lookup table that we walk
  // to resolve the user's monthly limit for the current expense.
  const profileData = useMemo(() => {
    if (!profileFields) return null;

    return profileFields.map(elem => ({
      id: elem.id,
      name: elem.name,
      value:
        (userData?.data.profileData as Record<string, any>)?.[elem.id] || null,
    }));
  }, [profileFields, userData]);

  // The user's allowed monthly amount for the current expense, taken from
  // their profile. `null` means we don't know yet (data still loading or no
  // mapping exists); 0 means "no allowance configured" and will block any
  // positive total.
  const allowedAmountCurrentExpense: number | null = useMemo(() => {
    if (!currentProfileFieldExpenseName || !profileData) return null;
    return (
      profileData.find(
        profileValue => profileValue.name === currentProfileFieldExpenseName,
      )?.value || 0
    );
  }, [currentProfileFieldExpenseName, profileData]);

  // Not every Panalab special form enforces a cap. Only the "limit" subset
  // (`panalabLimitForms`) triggers the profile-based validation.
  const hasToCheckAllowedAmount =
    isSpecialForm && isPanalabSpecialLimitForm(instance!.id, formTitle);

  // Running sum of every "Monto de X" field in the current step. Used both to
  // populate the read-only total field and to compare against the monthly cap.
  const currentTotal = useMemo(() => {
    if (
      !currentExpenseAmountFieldNames ||
      currentExpenseAmountFieldNames.length === 0
    )
      return 0;

    let total = 0;
    for (let i = 0; i < currentExpenseAmountFieldNames.length; i++) {
      const expenseName = currentExpenseAmountFieldNames[i];
      const val = getValues(expenseName);
      if (val && !Number.isNaN(val)) {
        total += Number(val);
      }
    }

    return total;
  }, [currentExpenseAmountFieldNames, getValues, formValues]);

  // Mirror the running total into the step's read-only "Monto Total" field so
  // the user (and the backend, on submit) sees it without having to type it.
  useEffect(() => {
    if (currentExpenseTotalAmountField) {
      setValue(currentExpenseTotalAmountField, currentTotal.toString());
    }
  }, [currentTotal, currentExpenseTotalAmountField, setValue]);

  // True when the user has exceeded their monthly cap for the current expense.
  // `formValues` is intentionally kept as a dep so the check re-evaluates on
  // every form edit, even though the body only reads `currentTotal` directly.
  const invalidExpenseAmount = useMemo(() => {
    if (hasToCheckAllowedAmount && allowedAmountCurrentExpense != null) {
      return currentTotal > Number(allowedAmountCurrentExpense);
    }
    return false;
  }, [
    allowedAmountCurrentExpense,
    hasToCheckAllowedAmount,
    formValues,
    currentTotal,
  ]);

  return {
    isSpecialForm,
    isLoading: isLoadingProfileFields || isLoadingUserData,
    invalidExpenseAmount,
  };
};

export default usePanalabSpecialForm;
