import React, { useCallback, useState } from "react";
import { useSearchParams } from "react-router-dom";

import { DateTime } from "luxon";

import { FilterPreset, Granularity, isGranularity } from "../types/dashboard";

import { useRouter } from "./RouterContext";

export interface DashboardFilter {
  startDate: DateTime;
  endDate: DateTime;
  granularity: Granularity;
  preset?: FilterPreset;
}

export interface DashboardFilterContext {
  filter: DashboardFilter;
  setFilter: (filter: DashboardFilter) => void;
}

/**
 * The default filter for the dashboard,used when the filter is not present in the URL.
 * Might as well be a function to avoid stale dates.
 */
export function defaultFilter(): DashboardFilter {
  return dashboardFilterFromPreset("last_7_days");
}

export function dashboardFilterFromPreset(preset: FilterPreset): DashboardFilter {
  switch (preset) {
    case "last_7_days":
      return {
        preset: "last_7_days",
        granularity: "day",
        startDate: DateTime.now().minus({ days: 6 }).startOf("day"),
        endDate: DateTime.now().endOf("day"),
      };
    case "last_30_days":
      return {
        preset: "last_30_days",
        granularity: "day",
        startDate: DateTime.now().minus({ days: 29 }).startOf("day"),
        endDate: DateTime.now().endOf("day"),
      };
    case "today":
      return {
        preset: "today",
        granularity: "day",
        startDate: DateTime.now().startOf("day"),
        endDate: DateTime.now().endOf("day"),
      };
    case "yesterday":
      return {
        preset: "yesterday",
        granularity: "day",
        startDate: DateTime.now().minus({ days: 1 }).startOf("day"),
        endDate: DateTime.now().minus({ days: 1 }).endOf("day"),
      };
    case "this_week":
      return {
        preset: "this_week",
        granularity: "day",
        startDate: DateTime.now().startOf("week"),
        endDate: DateTime.now().endOf("day"),
      };
    case "last_week":
      return {
        preset: "last_week",
        granularity: "day",
        startDate: DateTime.now().minus({ weeks: 1 }).startOf("week"),
        endDate: DateTime.now().minus({ weeks: 1 }).endOf("week"),
      };
    case "this_month":
      return {
        preset: "this_month",
        granularity: "day",
        startDate: DateTime.now().startOf("month"),
        endDate: DateTime.now().endOf("day"),
      };
    case "last_month":
      return {
        preset: "last_month",
        granularity: "day",
        startDate: DateTime.now().minus({ months: 1 }).startOf("month"),
        endDate: DateTime.now().minus({ months: 1 }).endOf("month"),
      };
    case "this_year":
      return {
        preset: "this_year",
        granularity: "month",
        startDate: DateTime.now().startOf("year"),
        endDate: DateTime.now().endOf("day"),
      };
    case "last_year":
      return {
        preset: "last_year",
        granularity: "month",
        startDate: DateTime.now().minus({ years: 1 }).startOf("year"),
        endDate: DateTime.now().minus({ years: 1 }).endOf("year"),
      };
  }
  throw new TypeError(`Unknown filter preset: ${preset}`);
}

export const filterDateRangeLabel = (filter: DashboardFilter) => {
  const { startDate, endDate } = filter;
  const isSameDay = startDate.valueOf() == endDate.startOf("day").valueOf();
  const startFormat = startDate.year === endDate.year ? "dd LLL" : "dd LLL, yyyy";
  return isSameDay
    ? startDate.toFormat("dd LLL, yyyy")
    : `${startDate.toFormat(startFormat)} - ${endDate.toFormat("dd LLL, yyyy")}`;
};

export const presetDateRangeLabel = (preset: FilterPreset) => {
  return filterDateRangeLabel(dashboardFilterFromPreset(preset));
};

/**
 * Load the initial filter from session storage.
 * If the filter is not present or invalid, return `defaultFilter`.
 */
export function dashboardFilterFromSearchParams(searchParams: URLSearchParams): DashboardFilter {
  // The URL always has to store the inclusive date since the backend expects it.
  const searchEndDate = searchParams.get("end_date");
  const searchStartDate = searchParams.get("start_date");
  const granularity = searchParams.get("granularity");

  if (searchStartDate == null || searchEndDate == null) return defaultFilter();
  if (!isGranularity(granularity)) return defaultFilter();

  const startDate = DateTime.fromISO(searchStartDate);
  const endDate = DateTime.fromISO(searchEndDate).minus({ days: 1 }).endOf("day");

  return {
    startDate,
    endDate,
    granularity,
    preset: "custom", // TODO: Detect current preset, e.g. preset query param
  };
}

export const DashboardFilterContext = React.createContext<DashboardFilterContext>(
  undefined as unknown as DashboardFilterContext,
);

export const useDashboardFilter = () => React.useContext(DashboardFilterContext);

export interface DashboardFilterContextProviderProps {
  children: React.ReactNode;
}

/**
 * Convert `DashboardFilter` to a query string that can be used in the URL.
 * Uses the inclusive `end_date` to make sure the backend always get the correct date.
 */
export function dashboardFilterToSearchParams({
  startDate,
  endDate,
  granularity,
}: DashboardFilter): Record<string, string> | null {
  const start_date = startDate.toISO({ suppressSeconds: true, suppressMilliseconds: true });
  const end_date = endDate
    .plus({ days: 1 })
    .startOf("day")
    .toISO({ suppressSeconds: true, suppressMilliseconds: true });
  if (start_date == null || end_date == null) return null;

  return { start_date, end_date, granularity };
}

const DashboardFilterContextProvider: React.FC<DashboardFilterContextProviderProps> = ({
  children,
}) => {
  const { navigate } = useRouter();
  const [searchParams] = useSearchParams();
  const [filter, setFilterState] = useState<DashboardFilter>(
    dashboardFilterFromSearchParams(searchParams),
  );

  const setFilter = useCallback((newFilter: DashboardFilter) => {
    setFilterState(newFilter);
    const query = dashboardFilterToSearchParams(newFilter);
    if (query == null) {
      console.error("[DASHBOARD_FILTER_CONTEXT] filter", newFilter);
      return;
    }
    navigate(undefined, { query, replace: true });
  }, []);

  return (
    <DashboardFilterContext.Provider value={{ filter, setFilter }}>
      {children}
    </DashboardFilterContext.Provider>
  );
};

export default DashboardFilterContextProvider;
