/*!
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
import { Box, HStack, Text } from "@chakra-ui/react";
import { keyframes } from "@emotion/react";
import dayjs from "dayjs";
import tz from "dayjs/plugin/timezone";
import utc from "dayjs/plugin/utc";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { FiChevronLeft, FiChevronRight } from "react-icons/fi";
import { useParams } from "react-router-dom";
import { useLocalStorage } from "usehooks-ts";

import { useCalendarServiceGetCalendar, useDagServiceGetDagDetails } from "openapi/queries";
import { ErrorAlert } from "src/components/ErrorAlert";
import { IconButton } from "src/components/ui";
import { ButtonGroupToggle } from "src/components/ui/ButtonGroupToggle";
import { CALENDAR_GRANULARITY_KEY, CALENDAR_VIEW_MODE_KEY } from "src/constants/localStorage";
import { useTimezone } from "src/context/timezone";

import { CalendarLegend } from "./CalendarLegend";
import { DailyCalendarView } from "./DailyCalendarView";
import { HourlyCalendarView } from "./HourlyCalendarView";
import { createCalendarScale } from "./calendarUtils";

const spin = keyframes`
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
`;

dayjs.extend(utc);
dayjs.extend(tz);

export const Calendar = () => {
  const { dagId = "" } = useParams();
  const { t: translate } = useTranslation("dag");
  const [selectedDate, setSelectedDate] = useState(dayjs());
  const [granularity, setGranularity] = useLocalStorage<"daily" | "hourly">(
    CALENDAR_GRANULARITY_KEY,
    "hourly",
  );
  const [viewMode, setViewMode] = useLocalStorage<"failed" | "total">(CALENDAR_VIEW_MODE_KEY, "total");

  const currentDate = dayjs();

  const { selectedTimezone } = useTimezone();

  const { data: dag } = useDagServiceGetDagDetails({ dagId });
  const isPartitioned = dag?.timetable_partitioned ?? false;

  // Compute the date range in the selected timezone, then convert to UTC for API
  const tzDate = selectedDate.tz(selectedTimezone, true);
  const startDate = granularity === "daily" ? tzDate.startOf("year") : tzDate.startOf("month");
  const endDate = granularity === "daily" ? tzDate.endOf("year") : tzDate.endOf("month");

  const gte = startDate.utc().format("YYYY-MM-DDTHH:mm:ss.SSS[Z]");
  const lte = endDate.utc().format("YYYY-MM-DDTHH:mm:ss.SSS[Z]");

  const { data, error, isLoading } = useCalendarServiceGetCalendar(
    {
      dagId,
      granularity,
      ...(isPartitioned
        ? { partitionDateGte: gte, partitionDateLte: lte }
        : { logicalDateGte: gte, logicalDateLte: lte }),
    },
    undefined,
    { enabled: Boolean(dagId) },
  );

  const scale = createCalendarScale(data?.dag_runs ?? [], {
    granularity,
    timezone: selectedTimezone,
    viewMode,
  });

  if (!data && !isLoading) {
    return (
      <Box p={4}>
        <Text>{translate("calendar.noData")}</Text>
      </Box>
    );
  }

  return (
    <Box data-testid="dag-calendar-root" p={6}>
      <ErrorAlert error={error} />
      <HStack data-testid="calendar-header" justify="space-between" mb={6}>
        <HStack gap={4} mb={4}>
          {granularity === "daily" ? (
            <HStack gap={2}>
              <IconButton
                aria-label={translate("calendar.navigation.previousYear")}
                onClick={() => setSelectedDate(selectedDate.subtract(1, "year"))}
              >
                <FiChevronLeft />
              </IconButton>
              <Text
                _hover={selectedDate.year() === currentDate.year() ? {} : { textDecoration: "underline" }}
                color={selectedDate.year() === currentDate.year() ? "fg.info" : "inherit"}
                cursor={selectedDate.year() === currentDate.year() ? "default" : "pointer"}
                data-testid="calendar-current-period"
                fontSize="xl"
                fontWeight="bold"
                minWidth="120px"
                onClick={() => {
                  if (selectedDate.year() !== currentDate.year()) {
                    setSelectedDate(currentDate.startOf("year"));
                  }
                }}
                textAlign="center"
              >
                {selectedDate.year()}
              </Text>
              <IconButton
                aria-label={translate("calendar.navigation.nextYear")}
                onClick={() => setSelectedDate(selectedDate.add(1, "year"))}
              >
                <FiChevronRight />
              </IconButton>
            </HStack>
          ) : (
            <HStack gap={2}>
              <IconButton
                aria-label={translate("calendar.navigation.previousMonth")}
                onClick={() => setSelectedDate(selectedDate.subtract(1, "month"))}
              >
                <FiChevronLeft />
              </IconButton>
              <Text
                _hover={
                  selectedDate.isSame(currentDate, "month") && selectedDate.isSame(currentDate, "year")
                    ? {}
                    : { textDecoration: "underline" }
                }
                color={
                  selectedDate.isSame(currentDate, "month") && selectedDate.isSame(currentDate, "year")
                    ? "fg.info"
                    : "inherit"
                }
                cursor={
                  selectedDate.isSame(currentDate, "month") && selectedDate.isSame(currentDate, "year")
                    ? "default"
                    : "pointer"
                }
                data-testid="calendar-current-period"
                fontSize="xl"
                fontWeight="bold"
                minWidth="120px"
                onClick={() => {
                  if (
                    !(selectedDate.isSame(currentDate, "month") && selectedDate.isSame(currentDate, "year"))
                  ) {
                    setSelectedDate(currentDate.startOf("month"));
                  }
                }}
                textAlign="center"
              >
                {selectedDate.format("MMM YYYY")}
              </Text>
              <IconButton
                aria-label={translate("calendar.navigation.nextMonth")}
                onClick={() => setSelectedDate(selectedDate.add(1, "month"))}
              >
                <FiChevronRight />
              </IconButton>
            </HStack>
          )}

          <ButtonGroupToggle
            onChange={setGranularity}
            options={[
              { label: translate("calendar.daily"), value: "daily" },
              { label: translate("calendar.hourly"), value: "hourly" },
            ]}
            value={granularity}
          />

          <ButtonGroupToggle
            onChange={setViewMode}
            options={[
              { label: translate("calendar.totalRuns"), value: "total" },
              { label: translate("overview.buttons.failedRun_other"), value: "failed" },
            ]}
            value={viewMode}
          />
        </HStack>
      </HStack>

      <Box position="relative">
        {isLoading ? (
          <Box
            alignItems="center"
            backdropFilter="blur(2px)"
            bg="bg/80"
            borderRadius="md"
            bottom="0"
            data-testid="calendar-loading-overlay"
            display="flex"
            justifyContent="center"
            left="0"
            position="absolute"
            right="0"
            top="0"
            zIndex={10}
          >
            <Box textAlign="center">
              <Box
                animation={`${spin} 1s linear infinite`}
                border="3px solid"
                borderColor={{ _dark: "none.600", _light: "brand.100" }}
                borderRadius="50%"
                borderTopColor="brand.500"
                height="24px"
                width="24px"
              />
            </Box>
          </Box>
        ) : undefined}
        {granularity === "daily" ? (
          <>
            <DailyCalendarView
              data={data?.dag_runs ?? []}
              data-testid="calendar-daily-view"
              scale={scale}
              selectedYear={selectedDate.year()}
              timezone={selectedTimezone}
              viewMode={viewMode}
            />
            <CalendarLegend scale={scale} viewMode={viewMode} />
          </>
        ) : (
          <HStack align="start" gap={2}>
            <Box>
              <HourlyCalendarView
                data={data?.dag_runs ?? []}
                scale={scale}
                selectedMonth={selectedDate.month()}
                selectedYear={selectedDate.year()}
                timezone={selectedTimezone}
                viewMode={viewMode}
              />
            </Box>
            <Box display="flex" flex="1" justifyContent="center" pt={16}>
              <CalendarLegend scale={scale} vertical viewMode={viewMode} />
            </Box>
          </HStack>
        )}
      </Box>
    </Box>
  );
};
