import fs from "fs/promises";
import path from "path";

import { parse } from "date-fns";
import matter from "gray-matter";
import { MDXRemoteSerializeResult } from "next-mdx-remote";
import { serialize } from "next-mdx-remote/serialize";

import type { Props } from "../pages/events/[year]/[term]/index";
// do not use alias "@/utils" as generate-calendar imports a function from this file and ts-node is not compatible
import {
  Term,
  TERMS,
  isTerm,
  DATE_FORMAT,
  getLocalDateFromEST,
} from "../utils";

const EVENTS_PATH = path.join("content", "events");

export async function getEventYears(): Promise<string[]> {
  return (await fs.readdir(EVENTS_PATH, { withFileTypes: true }))
    .filter((dirent) => dirent.isDirectory())
    .map((dirent) => dirent.name)
    .sort();
}

export async function getEventTermsByYear(year: string): Promise<Term[]> {
  return (
    await fs.readdir(path.join(EVENTS_PATH, year), { withFileTypes: true })
  )
    .filter((dirent) => dirent.isDirectory() && isTerm(dirent.name))
    .map((dirent) => dirent.name as Term)
    .sort((a, b) => TERMS.indexOf(a) - TERMS.indexOf(b));
}

interface RawMetadata {
  name: string;
  poster?: string;
  short: string;
  date: string;
  online?: boolean;
  location: string;
  registerLink?: string;
}

interface Metadata {
  name: string;
  poster?: string;
  short: string;
  date: string;
  online: boolean;
  location: string;
  permaLink: string;
  registerLink?: string;
  year: string;
  term: string;
  slug: string;
}

export interface Event {
  content: MDXRemoteSerializeResult<Record<string, unknown>>;
  metadata: Metadata;
}

export async function getEventBySlug(
  year: string,
  term: Term,
  slug: string
): Promise<Event> {
  const file = await fs.readFile(
    path.join(EVENTS_PATH, year, term, `${slug}.md`),
    "utf-8"
  );
  const { content, data } = matter(file);
  const raw = data as RawMetadata;

  return {
    content: await serialize(content),
    metadata: {
      ...raw,
      online: raw.online ?? false,
      date: getLocalDateFromEST(
        parse(raw.date, DATE_FORMAT, new Date())
      ).toString(),
      // permaLink is based on the directory structure in /pages
      permaLink: `/events/${year}/${term}/${slug}`,
      year: year,
      term: term,
      slug: slug,
    } as Metadata,
  };
}

export async function getEventsByTerm(
  year: string,
  term: Term
): Promise<string[]> {
  try {
    return (await fs.readdir(path.join(EVENTS_PATH, year, term)))
      .filter((name) => name.endsWith(".md"))
      .map((name) => name.slice(0, -".md".length));
  } catch {
    return [];
  }
}

export async function getUpcomingEvents(): Promise<Event[]> {
  const today = new Date();
  const currentYear = today.getFullYear();
  const currentTerm = Math.trunc(today.getMonth() / 4);
  const nextYear = currentTerm < 2 ? currentYear : currentYear + 1;
  const nextTerm = (currentTerm + 1) % 3;

  const events: Event[] = (
    await Promise.all(
      [
        { year: currentYear.toString(), term: currentTerm },
        { year: nextYear.toString(), term: nextTerm },
      ].map(async ({ year, term }) => {
        try {
          const eventsInTerm = await getEventsByTerm(year, TERMS[term]);
          return await Promise.all(
            eventsInTerm.map((slug) => getEventBySlug(year, TERMS[term], slug))
          );
        } catch (error) {
          return [];
        }
      })
    )
  ).flat();

  return events
    .filter((ev) => new Date(ev.metadata.date).getTime() >= Date.now())
    .sort((a, b) => {
      return (
        new Date(a.metadata.date).getTime() -
        new Date(b.metadata.date).getTime()
      );
    });
}

export async function getAllEvents(): Promise<Event[]> {
  const events: Event[] = [];

  for (const year of await getEventYears()) {
    for (const term of await getEventTermsByYear(year)) {
      for (const slug of await getEventsByTerm(year, term)) {
        events.push(await getEventBySlug(year, term, slug));
      }
    }
  }

  return events;
}

export async function getEventsPageProps({
  year,
  term,
}: {
  year: string;
  term: Term;
}): Promise<Props> {
  const eventNames = await getEventsByTerm(year, term);

  const events: Event[] = (
    await Promise.all(
      eventNames.map((file: string) => getEventBySlug(year, term, file))
    )
  ).sort(
    (a, b) =>
      new Date(a.metadata.date).getTime() - new Date(b.metadata.date).getTime()
  );

  const currentDate = Date.now();

  const pastEvents = events
    .filter((event) => new Date(event.metadata.date).getTime() < currentDate)
    .reverse();

  const futureEvents = events.filter(
    (event) => new Date(event.metadata.date).getTime() >= currentDate
  );

  const current = getCurrentTerm();

  const eventYears = await getEventYears();

  const minYear = eventYears[0];
  const pastTerms: { year: string; term: Term }[] = [];
  let curPastYear = year;
  let curPastTerm = term;
  while (parseInt(curPastYear) >= parseInt(minYear) && pastTerms.length < 2) {
    const pastTerm = getPastTerm(curPastYear, curPastTerm);
    curPastYear = pastTerm.year;
    curPastTerm = pastTerm.term;
    if ((await getEventsByTerm(curPastYear, curPastTerm)).length !== 0) {
      pastTerms.push(pastTerm);
    }
  }
  pastTerms.reverse();

  const maxYear = eventYears[eventYears.length - 1];
  const futureTerms: { year: string; term: Term }[] = [];
  let curFutureYear = year;
  let curFutureTerm = term;
  while (
    parseInt(curFutureYear) <= parseInt(maxYear) &&
    futureTerms.length < 2
  ) {
    const futureTerm = getFutureTerm(curFutureYear, curFutureTerm);
    curFutureYear = futureTerm.year;
    curFutureTerm = futureTerm.term;
    if ((await getEventsByTerm(curFutureYear, curFutureTerm)).length !== 0) {
      futureTerms.push(futureTerm);
    }
  }

  return {
    year: year,
    term: term,
    pastEvents: pastEvents,
    futureEvents: futureEvents,
    isCurrentTerm: term === current.term && year === current.year,
    pastTerms: pastTerms,
    futureTerms: futureTerms,
  };
}

export function getCurrentTerm(): { year: string; term: Term } {
  const today = new Date().toLocaleDateString("en-CA", {
    timeZone: "EST",
    year: "numeric",
    month: "numeric",
    day: "numeric",
  });

  const [year] = today.split("-");

  let term = "";

  if (`${year}-01-01` <= today) {
    term = "winter";
  }

  if (`${year}-05-01` <= today) {
    term = "spring";
  }

  if (`${year}-09-01` <= today) {
    term = "fall";
  }

  if (!isTerm(term)) {
    throw new Error("Error setting the current term");
  }

  return { year, term };
}

function getPastTerm(year: string, term: Term): { year: string; term: Term } {
  const index = TERMS.indexOf(term);

  if (index === -1) {
    throw new Error(`[getPastTerm] Not a valid term: "${term}" "${year}"`);
  }

  return index === 0
    ? {
        year: (parseInt(year) - 1).toString(),
        term: TERMS[TERMS.length - 1],
      }
    : {
        year: year,
        term: TERMS[index - 1],
      };
}

function getFutureTerm(year: string, term: Term): { year: string; term: Term } {
  const index = TERMS.indexOf(term);

  if (index === -1) {
    throw new Error(`[getFutureTerm] Not a valid term: "${term}" "${year}"`);
  }

  return index === TERMS.length - 1
    ? {
        year: (parseInt(year) + 1).toString(),
        term: TERMS[0],
      }
    : {
        year: year,
        term: TERMS[index + 1],
      };
}
