import { questionApi } from "@api/question";
import { quizApi } from "@api/quiz";
import { devlog } from "@frontend/kitui";
import { AnyTypedQuizQuestion, QuestionVariant } from "@frontend/squzanswerer";
import { questionToEditQuestionRequest } from "@model/question/edit";
import { QuestionType, RawQuestion, rawQuestionToQuestion } from "@model/question/question";
import { UntypedQuizQuestion, createQuestionOwnVariant, createQuestionVariant } from "@model/questionTypes/shared";
import { produce } from "immer";
import { nanoid } from "nanoid";
import { enqueueSnackbar } from "notistack";
import { defaultQuestionByType } from "../../constants/default";
import { isAxiosCanceledError } from "../../utils/isAxiosCanceledError";
import { replaceEmptyLinesToSpace } from "../../utils/replaceEmptyLinesToSpace";
import { RequestQueue } from "../../utils/requestQueue";
import { QuestionsStore, useQuestionsStore } from "./store";

export const setQuestions = (questions: RawQuestion[] | null | undefined) =>
  setProducedState(
    (state) => {
      const untypedResultQuestions = state.questions.filter((q) => q.type === null);

      state.questions = questions?.map(rawQuestionToQuestion) ?? [];
      state.questions.push(...untypedResultQuestions);
    },
    {
      type: "setQuestions",
      questions,
    }
  );

export const createUntypedQuestion = (quizId: number, insertAfterQuestionId?: string) => {
  const { questions } = useQuestionsStore.getState();

  const questionsAmount = questions.filter(({ type }) => type !== "result").length;

  if (questionsAmount >= 100) {
    return;
  }

  setProducedState(
    (state) => {
      const newUntypedQuestion = {
        id: nanoid(),
        quizId,
        type: null,
        title: "",
        description: "",
        deleted: false,
        expanded: true,
      };

      if (insertAfterQuestionId) {
        const index = state.questions.findIndex((q) => q.id === insertAfterQuestionId);
        if (index === -1) return;
        state.questions.splice(index + 1, 0, newUntypedQuestion);
        return;
      }

      state.questions.push(newUntypedQuestion);
      state.questions.sort((_, item) => (item.type === "result" ? -1 : 1));
    },
    {
      type: "createUntypedQuestion",
      quizId,
    }
  );
};

export const createUntypedQuestionForm = (quizId: number, insertAfterQuestionId?: string) => {
  const { questions } = useQuestionsStore.getState();

  const questionsAmount = questions.filter(({ type }) => type !== "result").length;

  if (questionsAmount >= 100) {
    return;
  }

  setProducedState(
    (state) => {
      const newUntypedQuestion = {
        id: nanoid(),
        quizId,
        type: null,
        title: "",
        description: "",
        deleted: false,
        expanded: true,
      };

      if (insertAfterQuestionId) {
        const index = state.questions.findIndex((q) => q.id === insertAfterQuestionId);
        if (index === -1) return;
        state.questions.splice(index + 1, 0, newUntypedQuestion);
        return;
      }

      state.questions.push(newUntypedQuestion);
    },
    {
      type: "createUntypedQuestion",
      quizId,
    }
  );
};

const removeQuestion = (questionId: string) =>
  setProducedState(
    (state) => {
      const index = state.questions.findIndex((q) => q.id === questionId);
      if (index === -1) return;

      state.questions.splice(index, 1);
    },
    {
      type: "removeQuestion",
      questionId,
    }
  );

export const updateUntypedQuestion = (questionId: string, updateFn: (question: UntypedQuizQuestion) => void) => {
  setProducedState(
    (state) => {
      const question = state.questions.find((q) => q.id === questionId);
      if (!question) return;
      if (question.type !== null) throw new Error("Cannot update typed question, use 'updateQuestion' instead");

      updateFn(question);
    },
    {
      type: "updateUntypedQuestion",
      questionId,
      updateFn: updateFn.toString(),
    }
  );
};

export const cleanQuestions = () =>
  setProducedState(
    (state) => {
      state.questions = [];
    },
    {
      type: "cleanQuestions",
    }
  );
export const reorderQuestionsForm = (sourceIndex: number, destinationIndex: number) => {
  if (sourceIndex === destinationIndex) return;

  if (sourceIndex === 0) return;

  if (destinationIndex === 0) return;

  setProducedState(
    (state) => {
      const [removed] = state.questions.splice(sourceIndex, 1);
      state.questions.splice(destinationIndex, 0, removed);
    },
    {
      type: "reorderQuestionsForm",
      sourceIndex,
      destinationIndex,
    }
  );

  updateQuestionOrders();
};

const updateQuestionOrders = () => {
  const questions = useQuestionsStore
    .getState()
    .questions.filter((question): question is AnyTypedQuizQuestion | UntypedQuizQuestion => question.type !== "result");

  questions.forEach((question, index) => {
    if (question.type) {
      updateQuestion(
        question.id,
        (question) => {
          question.page = index;
        },
        true
      );
    }
  });
};

export const reorderQuestions = (sourceIndex: number, destinationIndex: number) => {
  if (sourceIndex === destinationIndex) return;

  setProducedState(
    (state) => {
      const [removed] = state.questions.splice(sourceIndex, 1);
      state.questions.splice(destinationIndex, 0, removed);
    },
    {
      type: "reorderQuestions",
      sourceIndex,
      destinationIndex,
    }
  );

  updateQuestionOrders();
};

export const toggleExpandQuestion = (questionId: string) =>
  setProducedState(
    (state) => {
      const question = state.questions.find((q) => q.id === questionId);
      if (!question) return;

      question.expanded = !question.expanded;
    },
    {
      type: "toggleExpandQuestion",
      questionId,
    }
  );

export const collapseAllQuestions = () =>
  setProducedState((state) => {
    state.questions.forEach((question) => (question.expanded = false));
  }, "collapseAllQuestions");

const DELETE_TIMEOUT = 5000;

export const deleteQuestionWithTimeout = (questionId: string, deleteFn: (questionId: string) => void) =>
  setProducedState(
    (state) => {
      const question = state.questions.find((q) => q.id === questionId);
      if (!question) return;
      if (question.type === null || question.type === "result") {
        queueMicrotask(() => deleteFn(questionId));
        return;
      }

      question.deleted = true;
      clearTimeout(question.deleteTimeoutId);
      question.deleteTimeoutId = window.setTimeout(() => {
        deleteFn(questionId);
      }, DELETE_TIMEOUT);
    },
    {
      type: "deleteQuestionWithTimeout",
      questionId,
    }
  );

export const cancelQuestionDeletion = (questionId: string) =>
  setProducedState(
    (state) => {
      const question = state.questions.find((q) => q.id === questionId);
      if (!question || question.type === null || question.type === "result") return;

      question.deleted = false;
      clearTimeout(question.deleteTimeoutId);
    },
    {
      type: "cancelQuestionDeletion",
      questionId,
    }
  );

const requestQueue = new RequestQueue();
let rollbackQuestions: ReturnType<typeof useQuestionsStore.getState>;

export const updateQuestion = async <T = AnyTypedQuizQuestion>(
  questionId: string,
  updateFn: (question: T) => void,
  skipQueue = false
) => {
  if (!rollbackQuestions) rollbackQuestions = useQuestionsStore.getState();

  setProducedState(
    (state) => {
      const question =
        state.questions.find((q) => q.id === questionId) ||
        state.questions.find((q) => q.type !== null && q.content.id === questionId);
      if (!question) return;
      if (question.type === null)
        throw new Error("Cannot update untyped question, use 'updateUntypedQuestion' instead");
      updateFn(question as T);
    },
    {
      type: "updateQuestion",
      questionId,
      updateFn: updateFn.toString(),
    }
  );

  const request = async () => {
    const q =
      useQuestionsStore.getState().questions.find((q) => q.id === questionId) ||
      useQuestionsStore.getState().questions.find((q) => q.type !== null && q.content.id === questionId);
    if (!q) return;
    if (q.type === null) throw new Error("Cannot send update request for untyped question");
    const [response, editError] = await questionApi.edit(questionToEditQuestionRequest(replaceEmptyLinesToSpace(q)));

    if (editError) {
      useQuestionsStore.setState(rollbackQuestions);

      devlog("Error editing question", { editError, questionId });
      enqueueSnackbar(editError);
    }

    rollbackQuestions = useQuestionsStore.getState();

    //Если мы делаем листочек веточкой - удаляем созданный к нему результ
    const questionResult = useQuestionsStore
      .getState()
      .questions.find(
        (questionResult) => questionResult.type === "result" && questionResult.content.rule.parentId === q.content.id
      );

    if (questionResult && q.content.rule.default.length !== 0) {
      deleteQuestion(String(questionResult.quizId));
    }

    if (q.backendId !== response?.updated) {
      console.warn(`Question backend id has changed from ${q.backendId} to ${response?.updated}`);
    }
  };

  if (skipQueue) {
    request();
    return;
  }

  requestQueue.enqueue(`updateQuestion-${questionId}`, request);
};

export const addQuestionVariant = (questionId: string) => {
  updateQuestion(questionId, (question) => {
    switch (question.type) {
      case "variant":
      case "emoji":
      case "select":
      case "images":
      case "varimg":
        question.content.variants.push(createQuestionVariant());
        break;
      default:
        throw new Error(`Cannot add variant to question of type "${question.type}"`);
    }
  });
};
export const addQuestionOwnVariant = (questionId: string) => {
  updateQuestion(questionId, (question) => {
    switch (question.type) {
      case "variant":
      case "emoji":
      case "images":
      case "varimg":
        question.content.variants.push(createQuestionOwnVariant());
        break;
      default:
        throw new Error(`Cannot add variant to question of type "${question.type}"`);
    }
  });
};

export const deleteQuestionVariant = (questionId: string, variantId: string) => {
  updateQuestion(questionId, (question) => {
    if (!("variants" in question.content)) return;

    const variantIndex = question.content.variants.findIndex((variant) => variant.id === variantId);
    if (variantIndex === -1) return;

    question.content.variants.splice(variantIndex, 1);
  });
};

export const setQuestionVariantField = (
  questionId: string,
  variantId: string,
  field: keyof QuestionVariant,
  value: QuestionVariant[keyof QuestionVariant]
) => {
  updateQuestion(questionId, (question) => {
    if (!("variants" in question.content)) return;

    const variantIndex = question.content.variants.findIndex((variant) => variant.id === variantId);
    if (variantIndex === -1) return;

    const variant = question.content.variants[variantIndex];
    variant[field] = value;
  });
};

export const clearQuestionImages = (questionId: string, variantId: string) => {
  updateQuestion(questionId, (question) => {
    if (!("variants" in question.content)) return;

    const variantIndex = question.content.variants.findIndex((variant) => variant.id === variantId);
    if (variantIndex === -1) return;

    const variant = question.content.variants[variantIndex];
    variant.extendedText = "";
    variant.originalImageUrl = "";
  });
};

export const reorderQuestionVariants = (questionId: string, sourceIndex: number, destinationIndex: number) => {
  if (sourceIndex === destinationIndex) return;

  updateQuestion(questionId, (question) => {
    if (!("variants" in question.content)) return;

    const [removed] = question.content.variants.splice(sourceIndex, 1);
    question.content.variants.splice(destinationIndex, 0, removed);
  });
};

export const uploadQuestionImage = async (
  questionId: string,
  quizQid: string | undefined,
  blob: Blob,
  updateFn: (question: AnyTypedQuizQuestion, imageId: string) => void
) => {
  const question = useQuestionsStore.getState().questions.find((q) => q.id === questionId);
  if (!question || !quizQid) return;

  const [images, addImagesError] = await quizApi.addImages(question.quizId, blob);

  if (addImagesError || !images) {
    devlog("Error uploading question image", addImagesError);
    enqueueSnackbar("Не удалось загрузить изображение");

    return;
  }

  const values = Object.values(images);
  if (values.length !== 1) {
    console.warn("Error uploading image");
    return;
  }

  const imageId = values[0];
  const imageUrl = `https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/${quizQid}/${imageId}`;

  updateQuestion(questionId, (question) => {
    updateFn(question, imageUrl);
  });

  return imageUrl;
};

export const setQuestionInnerName = (questionId: string, name: string) => {
  updateQuestion(questionId, (question) => {
    question.content.innerName = name;
  });
};

export const changeQuestionType = (questionId: string, type: QuestionType) => {
  updateQuestion(questionId, (question) => {
    const oldId = question.content.id;
    const oldRule = question.content.rule;
    oldRule.main = [];
    question.type = type;
    question.content = JSON.parse(JSON.stringify(defaultQuestionByType[type].content));
    question.content.id = oldId;
    question.content.rule = oldRule;
    if ("editedUrlImagesList" in question.content) question.content.editedUrlImagesList = null;
  });
};

export const createTypedQuestion = async (questionId: string, type: QuestionType) =>
  requestQueue.enqueue(`createTypedQuestion-${questionId}`, async () => {
    const questions = useQuestionsStore.getState().questions;
    const question = questions.find((q) => q.id === questionId);
    if (!question) return;
    if (question.type !== null) throw new Error("Cannot upgrade already typed question");

    const untypedOrResultQuestionsLength = questions.filter((q) => q.type === "result" || q.type === null).length;

    const [createdQuestion, createdQuestionError] = await questionApi.create({
      quiz_id: question.quizId,
      type,
      title: question.title,
      description: question.description,
      page: questions.length - untypedOrResultQuestionsLength,
      required: false,
      content: JSON.stringify(defaultQuestionByType[type].content),
    });

    if (createdQuestionError || !createdQuestion) {
      devlog("Error creating question", createdQuestionError);
      enqueueSnackbar(createdQuestionError);

      return;
    }

    setProducedState(
      (state) => {
        const questionIndex = state.questions.findIndex((q) => q.id === questionId);
        if (questionIndex !== -1) state.questions.splice(questionIndex, 1, rawQuestionToQuestion(createdQuestion));
      },
      {
        type: "createTypedQuestion",
        question,
      }
    );

    updateQuestionOrders();
  });

export const deleteQuestion = async (questionId: string) =>
  requestQueue.enqueue(`deleteQuestion-${questionId}`, async () => {
    const question = useQuestionsStore.getState().questions.find((q) => q.id === questionId);
    if (!question) return;

    if (question.type === null) {
      removeQuestion(questionId);
      return;
    }

    const [_, deleteError] = await questionApi.delete(question.backendId);

    if (deleteError) {
      devlog("Error deleting question", deleteError);
      enqueueSnackbar("Не удалось удалить вопрос");
      return;
    }

    removeQuestion(questionId);

    updateQuestionOrders();
  });

export const copyQuestion = async (questionId: string, quizId: number) => {
  const { questions } = useQuestionsStore.getState();

  const questionsAmount = questions.filter(({ type }) => type !== "result").length;

  if (questionsAmount >= 100) {
    return;
  }

  return requestQueue.enqueue(`copyQuestion-${quizId}-${questionId}`, async () => {
    const question = useQuestionsStore.getState().questions.find((q) => q.id === questionId);
    if (!question) return;

    const frontId = nanoid();
    if (question.type === null) {
      const copiedQuestion = structuredClone(question);
      copiedQuestion.id = frontId;

      setProducedState(
        (state) => {
          state.questions.push(copiedQuestion);
        },
        {
          type: "copyQuestion",
          questionId,
          quizId,
        }
      );

      return;
    }

    const [copiedQuestionResult, copiedQuestionError] = await questionApi.copy(question.backendId, quizId);

    if (copiedQuestionError || !copiedQuestionResult) {
      devlog("Error copying question", copiedQuestionError);
      enqueueSnackbar(copiedQuestionError);

      return;
    }

    const copiedQuestion = structuredClone(question);
    copiedQuestion.backendId = copiedQuestionResult.updated;
    copiedQuestion.id = frontId;
    copiedQuestion.content.id = frontId;
    copiedQuestion.content.rule = {
      main: [],
      parentId: "",
      default: "",
      children: [],
    };

    setProducedState(
      (state) => {
        state.questions.push(copiedQuestion);
      },
      {
        type: "copyQuestion",
        questionId,
        quizId,
      }
    );

    updateQuestionOrders();
  });
};

function setProducedState<A extends string | { type: string }>(recipe: (state: QuestionsStore) => void, action?: A) {
  useQuestionsStore.setState((state) => produce(state, recipe), false, action);
}

export const getQuestionById = (questionId: string | null) => {
  if (questionId === null) return null;
  return useQuestionsStore.getState().questions.find((q) => q.id === questionId) || null;
};
export const getQuestionByContentId = (questionContentId: string | null) => {
  if (questionContentId === null) return null;
  return (
    useQuestionsStore.getState().questions.find((q) => {
      if (q.type === null) return false;

      return q.content.id === questionContentId;
    }) || null
  );
};

export const clearRuleForAll = () => {
  const { questions } = useQuestionsStore.getState();
  return Promise.allSettled(
    questions.map((question) => {
      if (
        question.type !== null &&
        (question.content.rule.main.length > 0 ||
          question.content.rule.default.length > 0 ||
          question.content.rule.parentId.length > 0) &&
        question.type !== "result"
      ) {
        updateQuestion(question.content.id, (question) => {
          question.content.rule.parentId = "";
          question.content.rule.main = [];
          question.content.rule.default = "";
        });
      }
    })
  );
};

export const createResult = async (quizId: number | null | undefined, parentContentId?: string) =>
  requestQueue.enqueue(`createResult-${quizId}`, async () => {
    if (!quizId || !parentContentId) {
      console.error("Нет данных для создания результата. quizId: ", quizId, ", quizId: ", parentContentId);
    }

    //Мы получили запрос на создание резулта. Анализируем существует ли такой. Если да - просто делаем его активным
    const question = useQuestionsStore
      .getState()
      .questions.find((q) => q.type !== null && q?.content.rule.parentId === parentContentId);

    if (question) {
      //существует, делаем активным

      updateQuestion(question.id, (q) => {
        q.content.usage = true;
      });
    } else {
      //не существует, создаём
      const content = JSON.parse(JSON.stringify(defaultQuestionByType["result"].content));
      content.rule.parentId = parentContentId;

      const [createdQuestion, createdQuestionError] = await questionApi.create({
        quiz_id: quizId,
        type: "result",
        title: "",
        description: "",
        page: 101,
        required: true,
        content: JSON.stringify(content),
      });

      if (createdQuestionError || !createdQuestion) {
        throw new Error(createdQuestionError);

        devlog("Error creating question", createdQuestionError);
        enqueueSnackbar(createdQuestionError);
      }

      setProducedState(
        (state) => {
          state.questions.push(rawQuestionToQuestion(createdQuestion));
        },
        { type: "createBackResult", createdQuestion }
      );

      return createdQuestion;
    }
  });
