import {
  compareDesc,
  getISODay,
  isAfter,
  isSameDay,
  parseISO,
  startOfToday,
} from "date-fns";

import { getClient } from "@/app/client";
import { cachedUntilOsloMidnight } from "@/lib/revalidation";
import { graphql, unmaskFragment } from "@/gql";
import type {
  OpeningHoursRangeBlockFragment as OpeningHoursRangeBlock,
  OpeningHoursSetFragment as OpeningHoursSet,
} from "@/gql/graphql";

const MISSING_OPENING_HOURS = {
  name: "Åpningstider mangler",
  effectiveFrom: "",
  effectiveTo: null,
  announcement: "Åpningstider mangler",
  items: [],
};

const WEEKDAYS = [
  "monday",
  "tuesday",
  "wednesday",
  "thursday",
  "friday",
  "saturday",
  "sunday",
];
const WEEKDAYS_NORWEGIAN = [
  "mandag",
  "tirsdag",
  "onsdag",
  "torsdag",
  "fredag",
  "lørdag",
  "søndag",
];

const openingHoursQuery = graphql(`
  query openingHoursSets {
    openingHoursSets {
      ...OpeningHoursSet
    }
  }
`);

export async function fetchOpeningHoursSets() {
  const { data, error } = await getClient().query(
    openingHoursQuery,
    {},
    // The footer renders today's hours on every page, so this TTL doubles as
    // the sitewide midnight refresh for futureEvents rollover
    cachedUntilOsloMidnight()
  );
  const sets = (data?.openingHoursSets ?? []) as OpeningHoursSet[];
  return sets;
}

export async function getOpeningHours() {
  const today = startOfToday();
  const sets = await fetchOpeningHoursSets();
  const validSets = sets
    .filter((set) => {
      const from = parseISO(set.effectiveFrom);
      return isAfter(today, from) || isSameDay(today, from);
    })
    .filter((set) => {
      if (!set.effectiveTo) {
        return true;
      }
      const to = parseISO(set.effectiveTo);
      return isAfter(to, today) || isSameDay(today, to);
    });
  if (validSets.length === 0) {
    return MISSING_OPENING_HOURS as OpeningHoursSet;
  }
  if (validSets.length === 1) {
    return validSets[0];
  }
  // pick the set that msot recently took effect
  return validSets.sort((a, b) =>
    compareDesc(a.effectiveFrom, b.effectiveFrom),
  )[0];
}

type OpeningHoursGroup = {
  days: string[];
  timeFrom: string | null;
  timeTo: string | null;
  custom: string | null;
};

type OpeningHoursPerDay = Record<string, OpeningHoursRangeBlock>;

export function groupOpeningHours(
  week: OpeningHoursPerDay,
): OpeningHoursGroup[] {
  const grouped: OpeningHoursGroup[] = [];
  let previous: string | null = null;

  for (const day of WEEKDAYS) {
    if (!week.hasOwnProperty(day)) {
      continue;
    }

    const hours = week[day];
    if (
      hours === null ||
      previous === null ||
      week[previous]?.timeFrom !== hours.timeFrom ||
      week[previous]?.timeTo !== hours.timeTo ||
      week[previous]?.custom !== hours.custom
    ) {
      grouped.push({
        days: [day],
        timeFrom: hours.timeFrom ?? null,
        timeTo: hours.timeTo ?? null,
        custom: hours.custom ?? null,
      });
    } else {
      grouped[grouped.length - 1].days.push(day);
    }
    previous = day;
  }
  return grouped;
}

export type PrettyOpeningHours = {
  range: string;
  time?: string;
  custom?: string;
};

function formatGroupedHours(
  grouped: OpeningHoursGroup[],
): PrettyOpeningHours[] {
  return grouped.map((group) => {
    const startDayIndex = WEEKDAYS.indexOf(group.days[0]);
    const endDayIndex = WEEKDAYS.indexOf(group.days[group.days.length - 1]);

    const startDayName = WEEKDAYS_NORWEGIAN[startDayIndex];
    const endDayName =
      group.days.length > 1 ? WEEKDAYS_NORWEGIAN[endDayIndex] : "";

    const rangeName = startDayName + (endDayName ? "—" + endDayName : "");

    const formattedRange = {
      range: rangeName,
      ...(group.timeFrom && group.timeTo
        ? {
            time: `${group.timeFrom.slice(0, 5)}—${group.timeTo.slice(0, 5)}`,
          }
        : {}),
      ...(group.custom ? { custom: group.custom } : {}),
    };

    return formattedRange;
  });
}

export function getOpeningHoursForFunction(
  openingHours: OpeningHoursSet,
  name: string,
) {
  const item = openingHours.items?.find((x) => x?.function === name);
  if (!item || !Array.isArray(item?.week) || item?.week.length !== 1) {
    return;
  }
  const maskedWeek = item.week[0];
  if (maskedWeek?.__typename !== "OpeningHoursWeekBlock") {
    return;
  }
  const week = unmaskFragment(
    OpeningHoursWeekBlockFragmentDefinition,
    maskedWeek,
  );
  return week;
}

export function getPrettyOpeningHoursForFunction(
  openingHours: OpeningHoursSet,
  name: string,
) {
  const week = getOpeningHoursForFunction(openingHours, name);
  if (!week) {
    return [];
  }
  // just trying to satisfy the type checker, this is crap
  const perDay: OpeningHoursPerDay = {
    monday: week.monday as OpeningHoursRangeBlock,
    tuesday: week.tuesday as OpeningHoursRangeBlock,
    wednesday: week.wednesday as OpeningHoursRangeBlock,
    thursday: week.thursday as OpeningHoursRangeBlock,
    friday: week.friday as OpeningHoursRangeBlock,
    saturday: week.saturday as OpeningHoursRangeBlock,
    sunday: week.sunday as OpeningHoursRangeBlock,
  };
  const grouped = groupOpeningHours(perDay);
  return formatGroupedHours(grouped);
}

export function getTodaysOpeningHoursForFunction(
  openingHours: OpeningHoursSet,
  name: string,
): string {
  const week: any = getOpeningHoursForFunction(openingHours, name);
  if (!week) {
    return "?";
  }
  const weekdayIndex = getISODay(startOfToday()) - 1;
  const weekday = WEEKDAYS[weekdayIndex];
  const hours = week[weekday];
  if (hours.timeFrom && hours.timeTo) {
    return `${hours.timeFrom.slice(0, 5)}—${hours.timeTo.slice(0, 5)}`;
  }
  if (hours.custom && hours.custom.length) {
    return hours.custom;
  }
  return "Stengt";
}

const OpeningHoursSetFragmentDefinition = graphql(`
  fragment OpeningHoursSet on OpeningHoursSet {
    name
    effectiveFrom
    effectiveTo
    announcement
    items {
      id
      function
      week {
        __typename
        ... on OpeningHoursWeekBlock {
          ...OpeningHoursWeekBlock
        }
      }
    }
  }
`);

const OpeningHoursRangeBlockFragmentDefinition = graphql(`
  fragment OpeningHoursRangeBlock on OpeningHoursRangeBlock {
    timeFrom
    timeTo
    custom
  }
`);

export const OpeningHoursWeekBlockFragmentDefinition = graphql(`
  fragment OpeningHoursWeekBlock on OpeningHoursWeekBlock {
    monday {
      ... on OpeningHoursRangeBlock {
        ...OpeningHoursRangeBlock
      }
    }
    tuesday {
      ... on OpeningHoursRangeBlock {
        ...OpeningHoursRangeBlock
      }
    }
    wednesday {
      ... on OpeningHoursRangeBlock {
        ...OpeningHoursRangeBlock
      }
    }
    thursday {
      ... on OpeningHoursRangeBlock {
        ...OpeningHoursRangeBlock
      }
    }
    friday {
      ... on OpeningHoursRangeBlock {
        ...OpeningHoursRangeBlock
      }
    }
    saturday {
      ... on OpeningHoursRangeBlock {
        ...OpeningHoursRangeBlock
      }
    }
    sunday {
      ... on OpeningHoursRangeBlock {
        ...OpeningHoursRangeBlock
      }
    }
  }
`);
