import {
  activeSubjectsFromLearnPayload,
  assessmentBelongsToActiveSubjects,
  filterAssessmentsForActiveSubjects,
  type OverviewSubject,
} from "./utils";

interface PrefItem {
  name: string;
  value: string;
}

import { getUserInfo } from "@/seqta/ui/AddBetterSEQTAElements";
import { settingsState } from "@/seqta/utils/listeners/SettingsState";
import { getMockAssessmentsData } from "@/seqta/ui/dev/hideSensitiveContent";
import { isSeqtaEngageExperience } from "@/seqta/utils/isSeqtaEngage";
import {
  getEngageAssessmentsData,
} from "./engageApi";

let cache: { time: number; engageAll?: boolean; studentId: number; data: any } | null =
  null;
const CACHE_MS = 10 * 60 * 1000;

async function fetchJSON(url: string, body: any) {
  const res = await fetch(`${location.origin}${url}`, {
    method: "POST",
    credentials: "include",
    headers: { "Content-Type": "application/json; charset=utf-8" },
    body: JSON.stringify(body),
  });
  if (!res.ok) {
    throw new Error(`HTTP ${res.status} for ${url}`);
  }
  return res.json();
}

async function loadSubjects(): Promise<OverviewSubject[]> {
  const res = await fetchJSON("/seqta/student/load/subjects?", {});
  return activeSubjectsFromLearnPayload(res.payload);
}

async function loadPrefs(student: number) {
  const res = await fetchJSON("/seqta/student/load/prefs?", {
    request: "userPrefs",
    asArray: true,
    user: student,
  });
  const colors: Record<string, string> = {};
  res.payload.forEach((p: PrefItem) => {
    if (p.name.startsWith("timetable.subject.colour.")) {
      const code = p.name.replace("timetable.subject.colour.", "");
      colors[code] = p.value;
    }
  });
  return colors;
}

async function loadUpcoming(student: number) {
  const res = await fetchJSON("/seqta/student/assessment/list/upcoming?", {
    student,
  });
  return res.payload;
}

function normalizeAssessmentDates(t: any, subject: OverviewSubject): any {
  const normalized = { ...t };
  if (!normalized.due && (t.date || t.dueDate || t.created || t.submittedDate)) {
    normalized.due = t.date || t.dueDate || t.created || t.submittedDate;
  }
  if (!normalized.programmeID) normalized.programmeID = subject.programme;
  if (!normalized.metaclassID) normalized.metaclassID = subject.metaclass;
  if (!normalized.code && t.subject) normalized.code = t.subject;
  return normalized;
}

async function loadPast(student: number, subjects: OverviewSubject[]) {
  const map: Record<number, any> = {};
  await Promise.all(
    subjects.map(async (s) => {
      const res = await fetchJSON("/seqta/student/assessment/list/past?", {
        programme: s.programme,
        metaclass: s.metaclass,
        student,
      });
      const processAssessment = (t: any) => {
        if (t && t.id) {
          const merged = {
            ...t,
            programmeID: t.programmeID || t.programme || s.programme,
            metaclassID: t.metaclassID || t.metaclass || s.metaclass,
            code: t.code || t.subject || s.code,
          };
          map[t.id] = normalizeAssessmentDates(merged, s);
        }
      };
      if (res.payload?.pending && Array.isArray(res.payload.pending)) {
        res.payload.pending.forEach(processAssessment);
      }
      if (res.payload?.tasks && Array.isArray(res.payload.tasks)) {
        res.payload.tasks.forEach(processAssessment);
      }
    }),
  );
  return map;
}

async function loadSubmissions(student: number, assessments: any[]) {
  const submissionMap: Record<number, boolean> = {};

  await Promise.all(
    assessments.map(async (assessment) => {
      try {
        const res = await fetchJSON(
          "/seqta/student/assessment/submissions/get",
          {
            assessment: assessment.id,
            metaclass: assessment.metaclassID,
            student,
          },
        );

        submissionMap[assessment.id] = res.payload && res.payload.length > 0;
      } catch (error) {
        console.warn(
          `Failed to fetch submission for assessment ${assessment.id}:`,
          error,
        );
        submissionMap[assessment.id] = false;
      }
    }),
  );

  return submissionMap;
}

async function getLearnAssessmentsData(studentId: number) {
  const [subjects, colors, upcoming] = await Promise.all([
    loadSubjects(),
    loadPrefs(studentId),
    loadUpcoming(studentId),
  ]);
  const pastMap = await loadPast(studentId, subjects);
  const map: Record<number, any> = {};
  upcoming.forEach((a: any) => {
    if (assessmentBelongsToActiveSubjects(a, subjects)) {
      map[a.id] = { ...a };
    }
  });
  Object.values(pastMap).forEach((t: any) => {
    if (!assessmentBelongsToActiveSubjects(t, subjects)) return;
    if (map[t.id]) Object.assign(map[t.id], t);
    else map[t.id] = t;
  });

  const allAssessments = filterAssessmentsForActiveSubjects(
    Object.values(map),
    subjects,
  );
  const submissions = await loadSubmissions(studentId, allAssessments);

  allAssessments.forEach((assessment: any) => {
    assessment.submitted = submissions[assessment.id] || false;
  });

  return { assessments: allAssessments, subjects, colors, studentId };
}

export async function getAssessmentsData() {
  if (settingsState.hideSensitiveContent) {
    return getMockAssessmentsData();
  }

  if (isSeqtaEngageExperience()) {
    if (cache && Date.now() - cache.time < CACHE_MS && cache.engageAll) {
      return cache.data;
    }

    const data = await getEngageAssessmentsData();
    cache = { time: Date.now(), studentId: 0, engageAll: true, data };
    return data;
  }

  const studentId = (await getUserInfo()).id;

  if (
    cache &&
    Date.now() - cache.time < CACHE_MS &&
    cache.studentId === studentId
  ) {
    return cache.data;
  }

  const data = await getLearnAssessmentsData(studentId);

  cache = { time: Date.now(), studentId, data };
  return data;
}
