import { type FC, useCallback, useEffect, useState } from 'react';
import { type UseFormReset } from 'react-hook-form';
import { useMutation } from 'react-query';

import { isBefore, isDate } from 'date-fns';
import { IconPlus } from '@material-hu/icons/tabler';
import Stack from '@material-hu/mui/Stack';
import Typography from '@material-hu/mui/Typography';
import { AdapterDateFns } from '@material-hu/mui/x-date-pickers/AdapterDateFns';
import { LocalizationProvider } from '@material-hu/mui/x-date-pickers/LocalizationProvider';

import Button from '@material-hu/components/design-system/Buttons/Button';
import useHuSnackbar from '@material-hu/components/design-system/Snackbar';
import HuTitle from '@material-hu/components/design-system/Title';

import { useAuth } from 'src/contexts/JWTContext';
import useGeneralError from 'src/hooks/useGeneralError';
import {
  CREATING_ENTRY_ID,
  EMPTY_ENTRY,
} from 'src/pages/dashboard/timeTracking/constants';
import { refetchDaySummaries } from 'src/pages/dashboard/timeTracking/queries';
import { modifyEntries } from 'src/services/timeTracking';
import {
  type CycleUpdateParam,
  type DaySummary,
  type DirtyEntryFormFields,
  type EntryFormFields,
  EntrySource,
  EntryType,
  type SiteCoordinates,
  type SiteLocation,
  type TimePairs,
  type TimeTrackingEntry,
  type UserSite,
} from 'src/types/timeTracking';
import { insertIf } from 'src/utils/arrays';
import { formatDateInTimezone } from 'src/utils/date';
import { useLokaliseTranslation } from 'src/utils/i18n';
import { getCurrentLocale } from 'src/utils/locale';
import {
  buildCycleUpdateParams,
  endIsNightShifted,
  pairEntries,
  parseEntriesIntoTimezone,
  updatedEntry,
  updateEntrySourceIfTimeChanged,
} from 'src/utils/timeTracking';

import EntryAccordion from './EntryAccordion';

type Props = {
  autoCloseMode?: DaySummary['autoCloseMode'];
  entries: TimeTrackingEntry[];
  summaryId: number;
  userIsMarking: boolean;
  loadingSummaries: boolean;
  summaryDay: string;
  enableNightShift: boolean;
  userId: number;
  userSites: UserSite[];
  onSiteClick?: (
    siteId: number | null,
    siteName: string,
    location: SiteLocation,
    markingLocation: SiteCoordinates | null,
    entrySource: EntrySource,
    entryType: EntryType,
  ) => void;
  onSelectedSiteChange?: (siteId: number, entryType: EntryType) => void;
  onFormValuesChange?: (
    formValues: EntryFormFields,
    entryPairId: string,
    siteIds: { start: { siteId: number }; end: { siteId: number } },
  ) => void;
  onCloseMap?: (newOpenAccordion?: string) => void;
  onOpenMap?: (openAccordion?: string) => void;
  defaultOpenAccordion?: string;
  isDeactivated?: boolean;
  enableRemoteWork?: boolean;
};

const Entries: FC<Props> = props => {
  const {
    autoCloseMode,
    entries,
    summaryId,
    userIsMarking,
    loadingSummaries,
    summaryDay,
    enableNightShift,
    userId,
    userSites,
    onSiteClick,
    onSelectedSiteChange,
    onFormValuesChange,
    onCloseMap,
    onOpenMap,
    defaultOpenAccordion,
    isDeactivated,
    enableRemoteWork,
  } = props;
  const { t } = useLokaliseTranslation('time_tracker');
  const [openAccordion, setOpenAccordion] = useState<string | undefined>(
    defaultOpenAccordion ||
      pairEntries(entries)?.[pairEntries(entries).length - 1]?.id ||
      undefined,
  );
  const [editingTimeSlot, setEditingTimeSlot] = useState<string | undefined>(
    undefined,
  );

  // Parent-driven: open a specific accordion (e.g. drawer opened on a day-entry).
  useEffect(() => {
    if (defaultOpenAccordion) {
      setOpenAccordion(defaultOpenAccordion);
    }
  }, [defaultOpenAccordion]);

  // Close map when accordion changes to a different one
  const handleAccordionToggle = useCallback(
    (id: string | undefined) => {
      const isChangingToNewAccordion = id !== openAccordion && id !== undefined;

      // Update accordion first
      setOpenAccordion(id);

      // Close map and pass the new accordion ID so it can be restored after remount
      if (isChangingToNewAccordion) {
        onCloseMap?.(id);
      }
    },
    [onCloseMap, openAccordion],
  );

  // Keep accordion open without triggering map close (used when exiting edit mode)
  const handleKeepAccordionOpen = (id: string) => {
    setOpenAccordion(id);
  };

  // Wrap onSiteClick to also record which accordion is open, so the map's
  // close button can restore it.
  const handleSiteClick = useCallback(
    (
      siteId: number | null,
      siteName: string,
      location: SiteLocation,
      markingLocation: SiteCoordinates | null,
      entrySource: EntrySource,
      entryType: EntryType,
    ) => {
      onOpenMap?.(openAccordion);
      onSiteClick?.(
        siteId,
        siteName,
        location,
        markingLocation,
        entrySource,
        entryType,
      );
    },
    [onSiteClick, onOpenMap, openAccordion],
  );
  const [pairedEntries, setPairedEntries] = useState(
    pairEntries(entries, 'ASC'),
  );
  const { instance, user } = useAuth();
  const showGeneralError = useGeneralError();
  const { enqueueSnackbar } = useHuSnackbar();

  const modifyEntriesMutation = useMutation(
    (cycle: CycleUpdateParam) => modifyEntries(cycle),
    {
      onSuccess: response => {
        enqueueSnackbar({
          title: t('updated_entries_successfully'),
          description: t('updated_entries_successfully_description'),
          variant: 'success',
        });
        refetchDaySummaries();
        const entriesWithSites = response.data.timeTrackingEntries.map(e => {
          const timezonedEntry = {
            ...e,
            time: formatDateInTimezone(e.time, instance?.timezone || ''),
          };
          const matchingSite =
            timezonedEntry?.siteId &&
            userSites.find(s => s.id === timezonedEntry.siteId);
          return matchingSite
            ? { ...timezonedEntry, siteName: matchingSite.name }
            : timezonedEntry;
        });
        setPairedEntries(pairEntries(entriesWithSites));
        setEditingTimeSlot(undefined);
        setOpenAccordion(undefined);
        // Close the map once the change is persisted; the shown location may no
        // longer reflect the saved entry.
        onCloseMap?.();
      },
      onError: err => {
        showGeneralError({
          errorData: err,
          errorMessage: t('update_entries_error'),
          errorDescription: t('update_entries_error_description'),
        });
      },
    },
  );

  const commonEntryData: Partial<TimeTrackingEntry> = {
    userId: Number(userId),
    summaryId,
  };

  const handleAddEntry = () => {
    setPairedEntries([
      {
        id: CREATING_ENTRY_ID,
        start: {
          ...EMPTY_ENTRY,
          type: EntryType.START,
          source: EntrySource.MANUAL,
        },
        end: {
          ...EMPTY_ENTRY,
          type: EntryType.END,
          source: EntrySource.MANUAL,
        },
      },
      ...pairedEntries,
    ]);
    setEditingTimeSlot(CREATING_ENTRY_ID);
    setOpenAccordion(CREATING_ENTRY_ID);
    // Give the new (locationless) register the full pane. Safe now that closing
    // the map no longer remounts the list, so the freshly added row persists.
    onCloseMap?.();
  };

  const handleEntryUpdate = (
    updatedEntries: EntryFormFields,
    dirtyFields: DirtyEntryFormFields,
    resetFormFields: UseFormReset<EntryFormFields>,
  ) => {
    const currentPair = pairedEntries.find(pair => pair.id === editingTimeSlot);
    const otherEntries = entries.filter(
      e => e.id !== currentPair?.start?.id && e.id !== currentPair?.end?.id,
    );
    const { end: endValues, start: startValues } = buildCycleUpdateParams(
      updatedEntries,
      dirtyFields,
    );
    const nightShiftedEndEntry =
      updatedEntries.end.time && updatedEntries.start.time
        ? endIsNightShifted(updatedEntries.end.time, updatedEntries.start.time)
        : false;
    const finalEntries = [
      ...insertIf(isDate(updatedEntries.end.time), {
        ...commonEntryData,
        ...updatedEntry({
          values: endValues,
          currentEntry: currentPair?.end,
          summaryDay: `${summaryDay}T00:00:00`,
          sites: userSites,
          timezone: instance?.timezone || '',
          applyNightShift: enableNightShift && nightShiftedEndEntry,
        }),
        id: currentPair?.end?.id,
        type: EntryType.END,
        source: updateEntrySourceIfTimeChanged(
          Boolean(dirtyFields.end?.time),
          currentPair?.end?.source,
        ),
      }),
      {
        ...commonEntryData,
        ...updatedEntry({
          values: startValues,
          currentEntry: currentPair?.start,
          summaryDay: `${summaryDay}T00:00:00`,
          sites: userSites,
          timezone: instance?.timezone || '',
        }),
        id: currentPair?.start?.id,
        type: EntryType.START,
        source: updateEntrySourceIfTimeChanged(
          Boolean(dirtyFields.start?.time),
          currentPair?.start?.source,
        ),
      },
      ...parseEntriesIntoTimezone(otherEntries, instance?.timezone || ''),
    ];
    modifyEntriesMutation.mutate(
      {
        summaryId,
        entries: finalEntries,
      },
      {
        onSuccess: () => {
          // Need to reset the form fields to the updated values in case the user changes a fields and after success changes another
          resetFormFields(updatedEntries);
        },
      },
    );
  };

  const handleEntryDelete = (pairId: string) => {
    if (pairId === CREATING_ENTRY_ID) {
      setPairedEntries([...pairedEntries.filter(pair => pair.id !== pairId)]);
      setEditingTimeSlot(undefined);
      setOpenAccordion(undefined);
    } else {
      const deletedPair = pairedEntries.find(pair => pair.id === pairId);
      const finalEntries = parseEntriesIntoTimezone(
        entries.filter(
          e => e.id !== deletedPair?.start?.id && e.id !== deletedPair?.end?.id,
        ),
        instance?.timezone || '',
      );
      modifyEntriesMutation.mutate({
        summaryId,
        entries: finalEntries,
      });
    }
  };

  const reservedCycles: TimePairs[] = pairedEntries
    .filter(p => p.id !== CREATING_ENTRY_ID)
    .map(p => ({
      id: p.id,
      start: p?.start?.time || '',
      end: p?.end?.time || '',
    }));

  const addEntryEnabled =
    !isDeactivated &&
    !editingTimeSlot &&
    !loadingSummaries &&
    isBefore(new Date(`${summaryDay}T00:00:00`), new Date());

  return (
    // Using the localization provider here because we need it time picker components not included in x-date-pickers
    <LocalizationProvider
      dateAdapter={AdapterDateFns}
      adapterLocale={getCurrentLocale(user)}
    >
      <Stack sx={{ mt: 3 }}>
        <Stack
          sx={{
            alignItems: 'center',
            flexDirection: 'row',
            justifyContent: 'space-between',
            mb: 2,
          }}
        >
          <HuTitle
            variant="S"
            title={t('entry_list')}
          />
          <Button
            variant="tertiary"
            onClick={handleAddEntry}
            startIcon={<IconPlus size={16} />}
            sx={{ fontSize: 14 }}
            disableRipple
            disabled={!addEntryEnabled}
          >
            {t('new_register')}
          </Button>
        </Stack>
        {pairedEntries.length === 0 && (
          <Stack
            sx={{
              backgroundColor: ({ palette }) =>
                palette.new.background.layout.default,
              borderRadius: ({ spacing }) => spacing(2),
              p: 2,
            }}
          >
            <Typography
              variant="globalS"
              sx={{
                color: ({ palette }) => palette.new.text.neutral.lighter,
                textAlign: 'center',
              }}
            >
              {t('no_entries')}
            </Typography>
          </Stack>
        )}
        {pairedEntries.length > 0 && (
          <Stack sx={{ gap: 2 }}>
            {pairedEntries.map((pair, index) => (
              <EntryAccordion
                key={pair.id}
                autoCloseMode={autoCloseMode}
                entryPair={pair}
                summaryDay={summaryDay}
                disabled={!!editingTimeSlot && editingTimeSlot !== pair.id}
                handleEditInit={setEditingTimeSlot}
                editingTimeSlot={editingTimeSlot}
                ongoingEntry={
                  pair.id !== CREATING_ENTRY_ID && userIsMarking && index === 0
                }
                isOpen={openAccordion === pair.id}
                handleAccordionToggle={handleAccordionToggle}
                onKeepAccordionOpen={handleKeepAccordionOpen}
                onEntryUpdate={handleEntryUpdate}
                isLoading={modifyEntriesMutation.isLoading}
                onEntryDeletion={handleEntryDelete}
                reservedCycles={reservedCycles}
                enableNightShift={enableNightShift}
                userId={userId}
                userSites={userSites}
                onSiteClick={handleSiteClick}
                onSelectedSiteChange={onSelectedSiteChange}
                onFormValuesChange={onFormValuesChange}
                enableRemoteWork={enableRemoteWork}
                isDeactivated={isDeactivated}
              />
            ))}
          </Stack>
        )}
      </Stack>
    </LocalizationProvider>
  );
};

export default Entries;
