import { useQuery } from "@tanstack/react-query";
import { DateTime, Duration } from "luxon";

const BASE_URL = import.meta.env.VITE_API_URL;

const makePostRequest = async (
  url: string,
  body: object,
  error = "Une erreur est survenue",
  method = "POST"
) => {
  const response = await fetch(BASE_URL + url, {
    method,
    headers: {
      "Content-Type": "application/json",
      Accept: "application/json",
    },
    body: JSON.stringify(body),
    credentials: "include", // Include cookies for authentication
  });

  const data = await response.json();
  if (!response.ok) throw new Error(data.error || error);
  return data?.data || data;
};

const makeRequest = async (url: string, error = "Une erreur est survenue") => {
  const response = await fetch(BASE_URL + url, { credentials: "include" });

  const data = await response.json();
  if (!response.ok) throw new Error(data.error || error);
  return data?.data || data;
};

/**
 * === AUTH API ===
 */
export const requestLogin = async (email: string, token: string) => {
  return makePostRequest(
    "/auth/request",
    { email, token },
    "Échec de la demande de connexion"
  );
};

export const verifyOtp = async ({
  otpCode,
  email,
}: {
  otpCode: string;
  email: string;
}) => {
  return makePostRequest(
    "/auth/verify",
    { email, code: otpCode },
    "Code de vérification invalide"
  );
};

export const getClasses = async () => {
  try {
    const res = await fetch("/classes.json");
    return res.json();
  } catch (error) {
    console.error("Error fetching classes:", error);
    return [];
  }
};

export const registerUser = async (
  userId: number,
  className: string,
  token: string
) => {
  return makePostRequest(
    "/auth/register",
    { userId, className, token },
    "Échec de l'inscription"
  );
};

export const getNames = async (className: string) => {
  return makeRequest(
    `/auth/autocomplete?className=${encodeURIComponent(className)}`,
    "Échec de la récupération des noms"
  );
};

export const getStudentMatch = async (
  firstName: string,
  lastName: string,
  classId: string
) => {
  return makeRequest(
    `/users/match-student?firstName=${firstName}&lastName=${lastName}&classId=${classId}`
  );
};

export const mergeStudent = async (studentId: string) => {
  return makePostRequest("/users/merge-student", { pastStudentId: studentId });
};

/**
 * === USER API ===
 */
const fetchUser = async () => {
  return makeRequest(
    "/users/@me",
    "Échec de la récupération des informations utilisateur"
  );
};

const defaultUser = {
  id: 0,
  firstName: "",
  lastName: "",
  fullName: "",
  email: "",
  className: "",
  preferences: [] as UserPreferences,
  pastStudentId: null as string | null,
};

export type User = typeof defaultUser;

export const AUTH_ERROR = "Unauthorized access";

export const useUser = () => {
  const { data, ...props } = useQuery({
    queryKey: ["user"],
    queryFn: fetchUser,
    staleTime: Duration.fromObject({
      minutes: 5, // 5 minutes
    }).toMillis(),
    gcTime: Duration.fromObject({
      days: 3, // 3 days
    }).toMillis(),
  });
  return {
    user: (data ? Object.assign(defaultUser, data) : defaultUser) as User,
    ...props,
  };
};

export const logout = async () => {
  return makePostRequest("/auth/logout", {}, "Échec de la déconnexion", "POST");
};

export const updateUserPreferences = async (
  preferences: { name: string; emoji: string; color: string }[]
) => {
  return makePostRequest(
    "/users/@me",
    { preferences },
    "Échec de la mise à jour des préférences utilisateur",
    "POST"
  );
};

interface UserPreference {
  name: string;
  emoji: string;
  color: string;
}
export type UserPreferences = UserPreference[];

/**
 * === COLLES API ===
 */
export const getColles = async (startDate: DateTime) => {
  return makeRequest(
    `/colles?startDate=${startDate.toISODate()}`,
    "Échec de la récupération des colles"
  );
};

export interface Colle {
  relatives: Colle[];
  id: number;
  date: string; // ISO date string
  subject: {
    id: number;
    name: string;
  };
  examiner: {
    id: number;
    name: string;
  };
  room: {
    id: number;
    name: string;
  };
  student: User;
  grade?: number; // Nullable grade
  bjsecret?: string; // Optional field
  bjid?: string; // Optional field
  content?: string; // Optional field
  comment?: string; // Optional field
  attachments?: Attachment[]; // Optional field, array of attachment URLs
}

interface CollePayload {
  classColles: Colle[];
  studentColles: Colle[];
  favoriteColles: Colle[];
  healthyUntil: Date;
  lastSync: Date;
}

export const useColles = (startDate: DateTime) => {
  // Check if the start date is the current week
  // This is used to determine if the data is "actual" or not
  const isFuture = startDate > DateTime.now();

  const options = (true || isFuture)
    ? {
        refetchOnWindowFocus: true,
        refetchOnReconnect: true,
        staleTime: 0,
        gcTime: 0,
      }
    : {
        staleTime: Duration.fromObject({
          hours: 1, // 1 hour
        }).toMillis(),
        gcTime: Duration.fromObject({
          days: 3, // 3 days
        }).toMillis(),
      };

  const { data, ...props } = useQuery({
    queryKey: ["colles", startDate.toISODate()],
    queryFn: () => getColles(startDate),
    ...options,
  });
  const mergedData = Object.assign(
    {
      classColles: [],
      studentColles: [],
      favoriteColles: [],
      healthyUntil: new Date(0),
      lastSync: new Date(0),
    },
    data || {}
  ) as CollePayload;

  return {
    ...mergedData,
    ...props,
  };
};

const fetchColle = async (id: number) => {
  return makeRequest(`/colles/${id}`, "Échec de la récupération de la colle");
};

const defaultColle = {
  id: 0,
  date: "",
  subject: {
    id: 0,
    name: "",
  },
  examiner: {
    id: 0,
    name: "",
  },
  room: {
    id: 0,
    name: "",
  },
  student: defaultUser,
  attachments: [],
};

export const useColle = (id: number) => {
  const { data, ...props } = useQuery({
    queryKey: ["colle", id],
    queryFn: () => fetchColle(id),
    staleTime: Duration.fromObject({
      seconds: 30, // 30 seconds
    }).toMillis(),
    gcTime: Duration.fromObject({
      days: 3, // 3 days
    }).toMillis(),
  });
  return {
    colle: (data || defaultColle) as Colle,
    ...props,
  };
};

export const refreshColle = async (id: number) => {
  return makePostRequest(
    `/colles/${id}/refresh`,
    {},
    "Échec de la demande de rafraîchissement de la colle",
    "POST"
  );
};

export interface Attachment {
  path: string;
  name: string;
}

/**
 * === SUBJECTS API ===
 */
export const getSubjects = async () => {
  return makeRequest("/subjects", "Échec de la récupération des matières");
};
export const useSubjects = () => {
  const { data, ...props } = useQuery({
    queryKey: ["subjects"],
    queryFn: getSubjects,
    staleTime: Duration.fromObject({
      hours: 1, // 1 day
    }).toMillis(),
    gcTime: Duration.fromObject({
      days: 3, // 3 days
    }).toMillis(),
  });
  return {
    subjects: (data as string[]) || [],
    ...props,
  };
};

/**
 * === NOTIFICATIONS API ===
 */
export const subscribe = async (data: any) => {
  return makePostRequest(
    "/notifications",
    data,
    "Échec de l'abonnement aux notifications"
  );
};

export const unsubscribe = async (id: string) => {
  return makePostRequest(
    `/notifications/${id}/unsubscribe`,
    {},
    "Échec de la désinscription des notifications"
  );
};

export const updateSubscription = async (id: string, events: string[]) => {
  return makePostRequest(
    `/notifications/${id}`,
    { events },
    "Échec de la mise à jour de l'abonnement aux notifications"
  );
};

export const getNotifications = async () => {
  return makeRequest(
    "/notifications",
    "Échec de la récupération des notifications"
  );
};

export interface Event {
  id: string;
  enabled: boolean;
}

interface Subscription {
  id: string;
  device: string;
  events: Event[];
  enabled: boolean;
}

export const useNotifications = () => {
  const { data, ...props } = useQuery({
    queryKey: ["notifications"],
    queryFn: getNotifications,
    staleTime: Duration.fromObject({
      hours: 0, // 1 hour
    }).toMillis(),
    gcTime: Duration.fromObject({
      days: 3, // 3 days
    }).toMillis(),
  });
  return {
    notifications: (data as Subscription[]) || [],
    ...props,
  };
};

export const testNotification = async (id: string) => {
  return makePostRequest(
    `/notifications/${id}/test`,
    {},
    "Échec de l'envoi de la notification de test"
  );
};

/**
 * === GRADES API ===
 */
export const getGrades = async (period: string) => {
  return makeRequest(
    `/grades?period=${encodeURIComponent(period)}`,
    "Échec de la récupération des notes"
  );
};
export const useGrades = (period: string) => {
  const { data, ...props } = useQuery({
    queryKey: ["grades", period],
    queryFn: () => getGrades(period),
    staleTime: Duration.fromObject({
      hours: 0, // 1 hour
    }).toMillis(),
    gcTime: Duration.fromObject({
      days: 3, // 3 days
    }).toMillis(),
  });
  const averages = (data?.averages as PeriodResult[]) || [];
  const subjects = (data?.subjects as string[]) || [];
  const grades = (data?.grades as Grade[]) || [];
  return {
    averages,
    subjects,
    grades,
    ...props,
  };
};

interface SubjectPerformance {
  subject: string;
  average: number;
}

export interface PeriodResult {
  period: string;
  average: number;
}

export interface Grade {
  subject: string;
  date: number; // timestamp
  grade: string;
}

export const getAverages = async (period: string) => {
  return makeRequest(
    `/averages?period=${encodeURIComponent(period)}`,
    "Échec de la récupération des moyennes"
  );
};
export const useAverages = (period: string) => {
  const { data, ...props } = useQuery({
    queryKey: ["averages", period],
    queryFn: () => getAverages(period),
    staleTime: Duration.fromObject({
      hours: 0, // 1 hour
    }).toMillis(),
    gcTime: Duration.fromObject({
      days: 3, // 3 days
    }).toMillis(),
  });
  const subjectAverages = (data?.subjectAverages as SubjectPerformance[]) || [];
  const globalAverage = data?.globalAverage || 0;
  return {
    subjectAverages,
    globalAverage,
    ...props,
  };
};

/**
 * === REPAS API ===
 */
const fetchMeals = async () => {
  return makeRequest(`/meals`, "Échec de la récupération des menus");
};
export const useMeals = () => {
  const { data, ...props } = useQuery({
    queryKey: ["meals"],
    queryFn: fetchMeals,
    staleTime: Duration.fromObject({
      hours: 0, // 6 hours
    }).toMillis(),
    gcTime: Duration.fromObject({
      days: 1, // 1 day
    }).toMillis(),
  });
  return {
    meals: (data as Meal[]) || [],
    ...props,
  };
};

export interface Meal {
  id: number;
  date: string;
  name: string;
  courses: { name: string; description: string }[];
  submittable: boolean;
  isRegistered?: boolean; // Optional field to indicate if the user is registered for this meal
}

export const registerForMeal = async (
  mealId: number,
  username: string,
  password: string
) => {
  return makePostRequest(
    `/meals/${mealId}`,
    { username, password },
    "Échec de l'inscription au repas",
    "POST"
  );
};

export const testCredentials = async (username: string, password: string) => {
  return makePostRequest(
    `/auth/test`,
    { username, password },
    "Échec de la vérification des identifiants",
    "POST"
  );
};

export const getCredentialsStatus = async () => {
  return makeRequest(
    `/auth/status`,
    "Échec de la récupération du statut des identifiants"
  );
};
