import { useCSRFFetch } from "@/fetchHelpers";
import type { CourseStatisticsType } from "@/gql/graphql";
import { graphqlClient } from "@/graphql/client";
import { COURSE_QUERY, COURSE_SESSION_DETAIL_QUERY } from "@/graphql/queries";
import {
  circleFlatChildren,
  circleFlatLearningContents,
  circleFlatLearningUnits,
} from "@/services/circle";
import { presignUpload, uploadFile } from "@/services/files";
import { useCompletionStore } from "@/stores/completion";
import { useCourseSessionsStore } from "@/stores/courseSessions";
import { useDashboardStore } from "@/stores/dashboard";
import { useUserStore } from "@/stores/user";
import type {
  ActionCompetence,
  Course,
  CourseCompletion,
  CourseCompletionStatus,
  CourseSession,
  CourseSessionDetail,
  LearningContentWithCompletion,
  LearningMentor,
  LearningPathType,
  LearningUnitPerformanceCriteria,
  PerformanceCriteria,
} from "@/types";
import { useQuery } from "@urql/vue";
import orderBy from "lodash/orderBy";
import log from "loglevel";
import type { ComputedRef } from "vue";
import { computed, onMounted, ref, watchEffect } from "vue";

export function useCurrentCourseSession() {
  /**
   * We often need the current course session in our components.
   * With this composable we can get it easily.
   */
  const store = useCourseSessionsStore();

  const result: ComputedRef<CourseSession> = computed(
    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
    () => {
      if (!store.currentCourseSession) {
        log.error(
          "currentCourseSession is only defined in pages with :courseSlug in the route"
        );
        throw new Error(
          `currentCourseSession is not defined in the store.
      It is only defined in pages with :courseSlug in the route`
        );
      }
      return store.currentCourseSession;
    }
  );
  return result;
}

export function useCourseSessionDetailQuery(courSessionId?: string) {
  if (!courSessionId) {
    courSessionId = useCurrentCourseSession().value.id;
  }
  const queryResult = useQuery({
    query: COURSE_SESSION_DETAIL_QUERY,
    variables: {
      courseSessionId: courSessionId,
    },
  });

  const courseSessionDetail = computed(() => {
    return queryResult.data.value?.course_session as CourseSessionDetail | undefined;
  });

  function findAssignmentByAssignmentId(assignmentId: string) {
    return (courseSessionDetail.value?.assignments ?? []).find((a) => {
      return a.learning_content?.content_assignment?.id === assignmentId;
    });
  }

  function findAssignment(learningContentId: string) {
    return (courseSessionDetail.value?.assignments ?? []).find((a) => {
      return a.learning_content?.id === learningContentId;
    });
  }

  function findEdoniqTest(learningContentId: string) {
    return (courseSessionDetail.value?.edoniq_tests ?? []).find((e) => {
      return e.learning_content?.id === learningContentId;
    });
  }

  function findAttendanceCourse(learningContentId: string) {
    return (courseSessionDetail.value?.attendance_courses ?? []).find((e) => {
      return e.learning_content?.id === learningContentId;
    });
  }

  function findUser(userId: string) {
    return (courseSessionDetail.value?.users ?? []).find((u) => {
      return u.user_id === userId;
    });
  }

  function findCurrentUser() {
    const userStore = useUserStore();
    const userId = userStore.id;
    return findUser(userId);
  }

  function filterMembers() {
    return (courseSessionDetail.value?.users ?? []).filter((u) => {
      return u.role === "MEMBER";
    });
  }

  function filterCircleExperts(circleSlug: string) {
    return (courseSessionDetail.value?.users ?? []).filter((u) => {
      return u.role === "EXPERT" && u.circles.map((c) => c.slug).includes(circleSlug);
    });
  }

  const dataLoaded = ref(false);

  function waitForData() {
    return new Promise((resolve) => {
      watchEffect(() => {
        if (queryResult.data.value) {
          dataLoaded.value = true;
          resolve(queryResult.data.value);
        }
      });
    });
  }

  return {
    ...queryResult,
    courseSessionDetail,
    waitForData,
    findAssignmentByAssignmentId,
    findAssignment,
    findEdoniqTest,
    findAttendanceCourse,
    findUser,
    findCurrentUser,
    filterMembers,
    filterCircleExperts,
  };
}

export function flatCircles(learningPath: LearningPathType) {
  return learningPath.topics.flatMap((t) => t.circles);
}

export function useCourseData(courseSlug: string) {
  const learningPath = ref<LearningPathType | undefined>(undefined);
  const actionCompetences = ref<ActionCompetence[]>([]);
  const course = ref<Course | undefined>(undefined);

  // urql.useQuery is not meant to be used programmatically, so we use graphqlClient.query instead
  const resultPromise = graphqlClient
    .query(COURSE_QUERY, { slug: `${courseSlug}` })
    .toPromise();

  resultPromise.then((result) => {
    if (result.error) {
      log.error(result.error);
    }

    course.value = result.data?.course as Course;
    actionCompetences.value = result.data?.course
      ?.action_competences as ActionCompetence[];
    learningPath.value = result.data?.course?.learning_path as LearningPathType;

    // attach circle information to learning contents
    if (learningPath.value) {
      flatCircles(learningPath.value).forEach((circle) => {
        circle.learning_sequences.forEach((ls, lsIndex) => {
          const circleData = {
            id: circle.id,
            slug: circle.slug,
            title: circle.title,
          };
          return ls.learning_units.forEach((lu, luIndex) => {
            lu.circle = Object.assign({}, circleData);
            lu.learning_contents.forEach((lc, lcIndex) => {
              lc.circle = Object.assign({}, circleData);
              lc.continueUrl = ls.frontend_url || circle.frontend_url;
              lc.firstInCircle = lcIndex === 0 && luIndex === 0 && lsIndex === 0;
              lc.parentLearningUnit = {
                id: lu.id,
                slug: lu.slug,
                title: lu.title,
              };
            });

            lu.performance_criteria.forEach((luPc) => {
              luPc.circle = Object.assign({}, circleData);
              const pc = findPerformanceCriterion(luPc.id);
              if (pc) {
                pc.circle = Object.assign({}, circleData);
              }
            });
          });
        });
      });
    }
  });

  const circles = computed(() => {
    if (learningPath.value) {
      return flatCircles(learningPath.value);
    }
    return undefined;
  });

  function findCircle(idOrSlug: string) {
    return (circles.value ?? []).find((c) => {
      return c.id === idOrSlug || c.slug.endsWith(idOrSlug);
    });
  }

  function findPerformanceCriterion(id: string) {
    return (actionCompetences.value ?? [])
      .flatMap((ac) => {
        return ac.performance_criteria;
      })
      .find((pc) => {
        return pc.id === id;
      }) as PerformanceCriteria | undefined;
  }

  function findLearningContent(
    learningContentIdOrSlug: string,
    circleIdOrSlug?: string
  ) {
    let filteredCircles = circles.value ?? [];
    if (circleIdOrSlug) {
      filteredCircles = filteredCircles.filter((c) => {
        return c.id === circleIdOrSlug || c.slug.endsWith(circleIdOrSlug);
      });
    }

    return filteredCircles
      .flatMap((c) => {
        return circleFlatLearningContents(c);
      })
      .find((lc) => {
        return (
          lc.id === learningContentIdOrSlug || lc.slug.endsWith(learningContentIdOrSlug)
        );
      });
  }

  function findLearningUnit(learningUnitIdOrSlug: string, circleIdOrSlug?: string) {
    let filteredCircles = circles.value ?? [];
    if (circleIdOrSlug) {
      filteredCircles = filteredCircles.filter((c) => {
        return c.id === circleIdOrSlug || c.slug.endsWith(circleIdOrSlug);
      });
    }

    return filteredCircles
      .flatMap((c) => {
        return circleFlatLearningUnits(c);
      })
      .find((lu) => {
        return lu.id === learningUnitIdOrSlug || lu.slug.endsWith(learningUnitIdOrSlug);
      });
  }

  const flatPerformanceCriteria = computed(() => {
    return (actionCompetences.value ?? []).flatMap((ac) => {
      return ac.performance_criteria;
    }) as PerformanceCriteria[];
  });

  return {
    resultPromise,
    course,
    learningPath,
    actionCompetences,
    circles,
    findCircle,
    findLearningContent,
    findLearningUnit,
    flatPerformanceCriteria,
  };
}

export function useCourseDataWithCompletion(
  courseSlug?: string,
  userId?: string,
  courseSessionId?: string
) {
  if (!courseSlug) {
    courseSlug = useCurrentCourseSession().value.course.slug;
  }
  if (!userId) {
    userId = useUserStore().id;
  }
  if (!courseSessionId) {
    courseSessionId = useCurrentCourseSession().value.id;
  }

  const courseResult = useCourseData(courseSlug);
  const completionStore = useCompletionStore();
  const nextLearningContent = ref<LearningContentWithCompletion | undefined>(undefined);
  const loaded = ref(false);

  function updateCompletionData() {
    if (userId && courseSessionId) {
      return completionStore.loadCourseSessionCompletionData(courseSessionId, userId);
    }
    return Promise.resolve([]);
  }

  function _parseCompletionData(completionData: CourseCompletion[]) {
    if (courseResult.circles.value) {
      courseResult.circles.value.forEach((circle) => {
        circleFlatChildren(circle).forEach((lc) => {
          const pageIndex = completionData.findIndex((e) => {
            return e.page_id === lc.id;
          });
          if (pageIndex >= 0) {
            lc.completion_status = completionData[pageIndex].completion_status;
          } else {
            lc.completion_status = "UNKNOWN";
          }
        });
      });
    }

    if (courseResult.actionCompetences.value) {
      courseResult.actionCompetences.value.forEach((ac) => {
        ac.performance_criteria.forEach((pc) => {
          const pageIndex = completionData.findIndex((e) => {
            return e.page_id === pc.id;
          });
          if (pageIndex >= 0) {
            pc.completion_status = completionData[pageIndex].completion_status;
          } else {
            pc.completion_status = "UNKNOWN";
          }
        });
      });
    }

    calcNextLearningContent(completionData);
  }

  function calcNextLearningContent(completionData: CourseCompletion[]) {
    const flatLearningContents = (courseResult.circles.value ?? []).flatMap((c) => {
      return circleFlatLearningContents(c);
    });
    const lastCompleted = findLastCompletedLearningContent(completionData);
    if (lastCompleted) {
      const lastCompletedIndex = flatLearningContents.findIndex((lc) => {
        return lc.id === lastCompleted.id;
      });
      if (flatLearningContents[lastCompletedIndex + 1]) {
        nextLearningContent.value = flatLearningContents[lastCompletedIndex + 1];
      } else {
        nextLearningContent.value = undefined;
      }
    } else {
      nextLearningContent.value = flatLearningContents[0];
    }
  }

  function findLastCompletedLearningContent(completionData: CourseCompletion[]) {
    const latestCompletion = orderBy(completionData ?? [], ["updated_at"], "desc").find(
      (c: CourseCompletion) => {
        return (
          c.completion_status === "SUCCESS" &&
          c.page_type.startsWith("learnpath.LearningContent")
        );
      }
    );
    if (latestCompletion) {
      return courseResult.findLearningContent(latestCompletion.page_id);
    }
  }

  async function markCompletion(
    page: LearningContentWithCompletion | LearningUnitPerformanceCriteria,
    completion_status: CourseCompletionStatus = "SUCCESS"
  ) {
    if (userId && courseSessionId) {
      page.completion_status = completion_status;
      const completionData = await completionStore.markPage(
        page,
        userId,
        courseSessionId
      );
      _parseCompletionData(completionData);
    }
  }

  async function _start() {
    return Promise.all([courseResult.resultPromise, updateCompletionData()]).then(
      // eslint-disable-next-line @typescript-eslint/no-unused-vars
      ([_queryResults, completionData]) => {
        _parseCompletionData(completionData);
        loaded.value = true;
      }
    );
  }

  const resultPromise = _start();

  return {
    ...courseResult,
    loaded,
    resultPromise,
    markCompletion,
    nextLearningContent,
  };
}

export function useCourseStatistics() {
  const dashboardStore = useDashboardStore();

  const statistics = computed(() => {
    return dashboardStore.currentDashBoardData as CourseStatisticsType;
  });

  const courseSessionName = (courseSessionId: string) => {
    return statistics.value.course_session_properties.sessions.find(
      (session) => session.id === courseSessionId
    )?.name;
  };

  const circleMeta = (circleId: string) => {
    return statistics.value.course_session_properties.circles.find(
      (circle) => circle.id === circleId
    );
  };

  return { courseSessionName, circleMeta };
}

export function useFileUpload() {
  const error = ref(false);
  const loading = ref(false);
  const fileInfo = ref({} as { id: string; name: string; url: string });

  async function upload(e: Event) {
    const { files } = e.target as HTMLInputElement;
    if (!files?.length) return;

    try {
      error.value = false;
      loading.value = true;
      const file = files[0];
      const presignData = await presignUpload(file);
      await uploadFile(presignData.pre_sign, file);
      fileInfo.value = presignData.file_info;
    } catch (e) {
      console.error(e);
      error.value = true;
    } finally {
      loading.value = false;
    }
  }

  return { upload, error, loading, fileInfo };
}

export function useLearningMentors() {
  const learningMentors = ref<LearningMentor[]>([]);
  const currentCourseSessionId = useCurrentCourseSession().value.id;
  const loading = ref(false);

  const fetchMentors = async () => {
    loading.value = true;
    const { data } = await useCSRFFetch(
      `/api/mentor/${currentCourseSessionId}/mentors`
    ).json();
    learningMentors.value = data.value;
    loading.value = false;
  };

  onMounted(fetchMentors);

  return {
    learningMentors,
    loading,
  };
}
