import { css, cx } from '@emotion/css';
import { concat, uniq, upperFirst, without } from 'lodash';
import { useEffect, useState } from 'react';
import { useFieldArray, useFormContext } from 'react-hook-form';

import { GrafanaTheme2 } from '@grafana/data';
import { Button, Field, FieldSet, Icon, InlineSwitch, Input, Stack, useStyles2 } from '@grafana/ui';

import { useAlertmanager } from '../../state/AlertmanagerContext';
import { MuteTimingFields } from '../../types/mute-timing-form';
import { DAYS_OF_THE_WEEK, defaultTimeInterval, MONTHS, validateArrayField } from '../../utils/mute-timings';

import { MuteTimingTimeRange } from './MuteTimingTimeRange';
import { TimezoneSelect } from './timezones';

export const MuteTimingTimeInterval = () => {
  const styles = useStyles2(getStyles);
  const { formState, register, setValue } = useFormContext<MuteTimingFields>();
  const {
    fields: timeIntervals,
    append: addTimeInterval,
    remove: removeTimeInterval,
  } = useFieldArray({
    name: 'time_intervals',
  });
  const { isGrafanaAlertmanager } = useAlertmanager();

  return (
    <FieldSet label="Time intervals">
      <>
        <p>
          A time interval is a definition for a moment in time. All fields are lists, and at least one list element must
          be satisfied to match the field. If a field is left blank, any moment of time will match the field. For an
          instant of time to match a complete time interval, all fields must match. A mute timing can contain multiple
          time intervals.
        </p>
        <Stack direction="column" gap={2}>
          {timeIntervals.map((timeInterval, timeIntervalIndex) => {
            const errors = formState.errors;

            // manually register the "location" field, react-hook-form doesn't handle nested field arrays well and will refuse to set
            // the default value for the field when using "useFieldArray"
            register(`time_intervals.${timeIntervalIndex}.location`);

            return (
              <div key={timeInterval.id} className={styles.timeIntervalSection}>
                <MuteTimingTimeRange intervalIndex={timeIntervalIndex} />
                <Field
                  label="Location"
                  invalid={Boolean(errors.time_intervals?.[timeIntervalIndex]?.location)}
                  error={errors.time_intervals?.[timeIntervalIndex]?.location?.message}
                >
                  <TimezoneSelect
                    prefix={<Icon name="map-marker" />}
                    width={50}
                    onChange={(selectedTimezone) => {
                      setValue(`time_intervals.${timeIntervalIndex}.location`, selectedTimezone.value);
                    }}
                    // @ts-ignore react-hook-form doesn't handle nested field arrays well
                    defaultValue={{ label: timeInterval.location, value: timeInterval.location }}
                    data-testid="mute-timing-location"
                  />
                </Field>
                <Field label="Days of the week">
                  <DaysOfTheWeek
                    onChange={(daysOfWeek) => {
                      setValue(`time_intervals.${timeIntervalIndex}.weekdays`, daysOfWeek);
                    }}
                    // @ts-ignore react-hook-form doesn't handle nested field arrays well
                    defaultValue={timeInterval.weekdays}
                  />
                </Field>
                <Field
                  label="Days of the month"
                  description="The days of the month, 1-31, of a month. Negative values can be used to represent days which begin at the end of the month"
                  invalid={!!errors.time_intervals?.[timeIntervalIndex]?.days_of_month}
                  error={errors.time_intervals?.[timeIntervalIndex]?.days_of_month?.message}
                >
                  <Input
                    {...register(`time_intervals.${timeIntervalIndex}.days_of_month`, {
                      validate: (value) =>
                        validateArrayField(
                          value,
                          (day) => {
                            const parsedDay = parseInt(day, 10);
                            return (parsedDay > -31 && parsedDay < 0) || (parsedDay > 0 && parsedDay < 32);
                          },
                          'Invalid day'
                        ),
                    })}
                    width={50}
                    // @ts-ignore react-hook-form doesn't handle nested field arrays well
                    defaultValue={timeInterval.days_of_month}
                    placeholder="Example: 1, 14:16, -1"
                    data-testid="mute-timing-days"
                  />
                </Field>
                <Field
                  label="Months"
                  description="The months of the year in either numerical or the full calendar month"
                  invalid={!!errors.time_intervals?.[timeIntervalIndex]?.months}
                  error={errors.time_intervals?.[timeIntervalIndex]?.months?.message}
                >
                  <Input
                    {...register(`time_intervals.${timeIntervalIndex}.months`, {
                      validate: (value) =>
                        validateArrayField(
                          value,
                          (month) => MONTHS.includes(month) || (parseInt(month, 10) < 13 && parseInt(month, 10) > 0),
                          'Invalid month'
                        ),
                    })}
                    width={50}
                    placeholder="Example: 1:3, may:august, december"
                    // @ts-ignore react-hook-form doesn't handle nested field arrays well
                    defaultValue={timeInterval.months}
                    data-testid="mute-timing-months"
                  />
                </Field>
                <Field
                  label="Years"
                  invalid={!!errors.time_intervals?.[timeIntervalIndex]?.years}
                  error={errors.time_intervals?.[timeIntervalIndex]?.years?.message ?? ''}
                >
                  <Input
                    {...register(`time_intervals.${timeIntervalIndex}.years`, {
                      validate: (value) => validateArrayField(value, (year) => /^\d{4}$/.test(year), 'Invalid year'),
                    })}
                    width={50}
                    placeholder="Example: 2021:2022, 2030"
                    // @ts-ignore react-hook-form doesn't handle nested field arrays well
                    defaultValue={timeInterval.years}
                    data-testid="mute-timing-years"
                  />
                </Field>
                <Stack direction="row" gap={2}>
                  <Button
                    type="button"
                    variant="destructive"
                    fill="outline"
                    icon="trash-alt"
                    onClick={() => removeTimeInterval(timeIntervalIndex)}
                  >
                    Remove time interval
                  </Button>
                  {/*
                    This switch is only available for Grafana Alertmanager, as for now, Grafana alert manager doesn't support this feature
                    It hanldes empty list as undefined making impossible the use of an empty list for disabling time interval
                  */}
                  {!isGrafanaAlertmanager && (
                    <InlineSwitch
                      id={`time_intervals.${timeIntervalIndex}.disable`}
                      label="Disable"
                      showLabel
                      transparent
                      {...register(`time_intervals.${timeIntervalIndex}.disable`)}
                    />
                  )}
                </Stack>
              </div>
            );
          })}
        </Stack>
        <Button
          type="button"
          variant="secondary"
          className={styles.removeTimeIntervalButton}
          onClick={() => {
            addTimeInterval(defaultTimeInterval);
          }}
          icon="plus"
        >
          Add another time interval
        </Button>
      </>
    </FieldSet>
  );
};

interface DaysOfTheWeekProps {
  defaultValue?: string;
  onChange: (input: string) => void;
}

const parseDays = (input: string): string[] => {
  const parsedDays = input
    .split(',')
    .map((day) => day.trim())
    // each "day" could still be a range of days, so we parse the range
    .flatMap((day) => (day.includes(':') ? parseWeekdayRange(day) : day))
    .map((day) => day.toLowerCase())
    // remove invalid weekdays
    .filter((day) => DAYS_OF_THE_WEEK.includes(day));

  return uniq(parsedDays);
};

// parse monday:wednesday to ["monday", "tuesday", "wednesday"]
function parseWeekdayRange(input: string): string[] {
  const [start = '', end = ''] = input.split(':');

  const startIndex = DAYS_OF_THE_WEEK.indexOf(start);
  const endIndex = DAYS_OF_THE_WEEK.indexOf(end);

  return DAYS_OF_THE_WEEK.slice(startIndex, endIndex + 1);
}

const DaysOfTheWeek = ({ defaultValue = '', onChange }: DaysOfTheWeekProps) => {
  const styles = useStyles2(getStyles);
  const defaultValues = parseDays(defaultValue);
  const [selectedDays, setSelectedDays] = useState<string[]>(defaultValues);

  const toggleDay = (day: string) => {
    selectedDays.includes(day)
      ? setSelectedDays((selectedDays) => without(selectedDays, day))
      : setSelectedDays((selectedDays) => concat(selectedDays, day));
  };

  useEffect(() => {
    onChange(selectedDays.join(', '));
  }, [selectedDays, onChange]);

  return (
    <div data-testid="mute-timing-weekdays">
      <Stack gap={1}>
        {DAYS_OF_THE_WEEK.map((day) => {
          const style = cx(styles.dayOfTheWeek, selectedDays.includes(day) && 'selected');
          const abbreviated = day.slice(0, 3);

          return (
            <button type="button" key={day} className={style} onClick={() => toggleDay(day)}>
              {upperFirst(abbreviated)}
            </button>
          );
        })}
      </Stack>
    </div>
  );
};

const getStyles = (theme: GrafanaTheme2) => ({
  input: css({
    width: '400px',
  }),
  timeIntervalSection: css({
    backgroundColor: theme.colors.background.secondary,
    padding: theme.spacing(2),
  }),
  removeTimeIntervalButton: css({
    marginTop: theme.spacing(2),
  }),
  dayOfTheWeek: css({
    cursor: 'pointer',
    userSelect: 'none',
    padding: `${theme.spacing(1)} ${theme.spacing(3)}`,

    border: `solid 1px ${theme.colors.border.medium}`,
    background: 'none',
    borderRadius: theme.shape.radius.default,

    color: theme.colors.text.secondary,

    '&.selected': {
      fontWeight: theme.typography.fontWeightBold,
      color: theme.colors.primary.text,
      borderColor: theme.colors.primary.border,
      background: theme.colors.primary.transparent,
    },
  }),
});
