import { isValid, parseISO } from 'date-fns';

import {
  type AssignedShiftInfo,
  type AssignShiftParams,
  type BulkDeleteShiftParams,
  type ShiftTemplateParams,
} from 'src/services/shifts';
import {
  type ShiftAssignCellVC,
  type ShiftCalendarUser,
} from 'src/types/shifts';

import {
  type CollaboratorOption,
  type ShiftFormData,
  type ShiftTemplateFormData,
} from '../form';

export type ShiftFilters = {
  search?: string;
  userIds?: string;
  siteId?: number | null;
  segmentationItemIds?: string;
  startDate?: string;
  endDate?: string;
};

type AssignmentParams = {
  data: ShiftFormData;
  formatDate: (date: Date, format: string) => string;
  selectedDates?: Set<string>;
  saveAsTemplate?: boolean;
  selectedCells?: ShiftAssignCellVC[];
  excludeCells?: ShiftAssignCellVC[];
  filters?: ShiftFilters;
  excludeShiftIds?: number[];
};

type BulkDeleteParams = {
  selectedDates?: Set<string>;
  selectedCells?: ShiftAssignCellVC[];
  excludeCells?: ShiftAssignCellVC[];
  filters?: ShiftFilters;
  assignedShiftIds?: number[];
  excludeAssignedShiftIds?: number[];
};

/**
 * Checks if all dates in selectedCells have the same set of userIds
 */
const checkIfDatesAreSharedAcrossAllElements = (
  selectedCells: ShiftAssignCellVC[],
): boolean => {
  if (selectedCells.length === 0) return true;
  const cellsByDate = selectedCells.reduce<Record<string, Set<number>>>(
    (acc, cell) => {
      if (!acc[cell.date]) acc[cell.date] = new Set();
      acc[cell.date].add(cell.userId);
      return acc;
    },
    {},
  );
  const dates = Object.keys(cellsByDate);
  if (dates.length <= 1) return true;
  const referenceUserSet = cellsByDate[dates[0]];
  return dates.every(date => {
    const currentUserSet = cellsByDate[date];
    if (currentUserSet.size !== referenceUserSet.size) return false;
    for (const userId of referenceUserSet) {
      if (!currentUserSet.has(userId)) return false;
    }
    return true;
  });
};

type ConcileFormDataAndCellsParams = {
  collaborators: ShiftFormData['collaborators'];
  dates: string[];
  selectedCells: ShiftAssignCellVC[];
};

const concileFormDataAndCells = ({
  collaborators,
  dates,
  selectedCells,
}: ConcileFormDataAndCellsParams) => {
  if (dates.length > 1) return selectedCells;
  const selectedCellDates = [...new Set(selectedCells.map(cell => cell.date))];
  const cellDates = selectedCellDates.length <= 1 ? dates : selectedCellDates;
  const collabsShareAllDates =
    checkIfDatesAreSharedAcrossAllElements(selectedCells);
  if (collabsShareAllDates) {
    return cellDates.reduce<ShiftAssignCellVC[]>((acc, date) => {
      for (const collaborator of collaborators) {
        acc.push({ userId: collaborator.value, date });
      }
      return acc;
    }, []);
  }
  return selectedCells;
};

export const mapFilters = (filters?: ShiftFilters) => {
  if (!filters) return undefined;
  return {
    startDate: filters.startDate || '',
    endDate: filters.endDate || '',
    segmentationItemIds: filters.segmentationItemIds
      ? filters.segmentationItemIds
          .split(',')
          .map(id => parseInt(id.trim(), 10))
          .filter(id => !Number.isNaN(id))
      : undefined,
    siteId: filters.siteId || undefined,
    searchType: 'ALL' as const,
    userIds: filters.userIds
      ? filters.userIds
          .split(',')
          .map(id => parseInt(id.trim(), 10))
          .filter(id => !Number.isNaN(id))
      : [],
    search: filters.search || undefined,
  };
};

const parseShiftData = (
  data: ShiftFormData,
  completedTimeSlots: ShiftFormData['timeSlots'],
  formatDate: (date: Date, format: string) => string,
  saveAsTemplate?: boolean,
): AssignedShiftInfo => {
  const isDayOff = completedTimeSlots.length === 0;
  return isDayOff
    ? { isRest: true }
    : {
        name: data.shiftName,
        color: data.color,
        description: data.notes,
        timeSlots: completedTimeSlots
          .map(timeSlot => {
            const start = timeSlot.startTime;
            const end = timeSlot.endTime;
            if (!start || !end) return null;
            return [formatDate(start, 'HH:mm'), formatDate(end, 'HH:mm')] as [
              string,
              string,
            ];
          })
          .filter((ts): ts is [string, string] => ts !== null),
        saveAsTemplate,
      };
};

export const getAssignmentParams = ({
  data: formData,
  formatDate,
  selectedDates,
  saveAsTemplate,
  selectedCells,
  excludeCells,
  filters,
}: AssignmentParams): AssignShiftParams => {
  const atLeastOneDateSelected = selectedDates && selectedDates.size > 0;
  const hasSelectedCells = selectedCells && selectedCells.length > 0;
  const completedTimeSlots = formData.timeSlots.filter(
    timeSlot => timeSlot.startTime && timeSlot.endTime,
  );
  const cellApplicableDates =
    selectedDates && selectedDates.size <= 1 && formData.date
      ? [formatDate(formData.date, 'yyyy-MM-dd')]
      : Array.from(selectedDates || []);

  const commonParams: AssignShiftParams = {
    allRows: atLeastOneDateSelected,
    dates: Array.from(selectedDates || []),
    cells:
      atLeastOneDateSelected || hasSelectedCells
        ? selectedCells || []
        : concileFormDataAndCells({
            collaborators: formData.collaborators,
            dates: cellApplicableDates,
            selectedCells: selectedCells || [],
          }),
    excludeCells: excludeCells,
    filters: mapFilters(filters),
    shift: parseShiftData(
      formData,
      completedTimeSlots,
      formatDate,
      saveAsTemplate,
    ),
  };

  if (selectedCells?.length === 0 && selectedDates?.size === 0) {
    return {
      ...commonParams,
      allRows: selectedDates && selectedDates.size > 0,
      dates: selectedDates ? Array.from(selectedDates) : [],
    };
  }
  return commonParams;
};

export const getBulkDeleteParams = ({
  selectedDates,
  selectedCells,
  excludeCells,
  filters,
  assignedShiftIds,
  excludeAssignedShiftIds,
}: BulkDeleteParams): BulkDeleteShiftParams => {
  const hasDateOrCellSelection =
    (selectedDates?.size ?? 0) > 0 || (selectedCells?.length ?? 0) > 0;
  const mappedFilters = hasDateOrCellSelection
    ? mapFilters(filters)
    : undefined;

  const commonParams: BulkDeleteShiftParams = {
    allRows: (selectedDates?.size ?? 0) > 0,
    dates: selectedDates ? Array.from(selectedDates) : [],
    cells: selectedCells,
    excludeCells: excludeCells,
    filters: mappedFilters,
    assignedShiftIds: assignedShiftIds || [],
    excludeAssignedShiftIds: excludeAssignedShiftIds || [],
  };

  if (selectedCells && selectedCells.length > 0) {
    return {
      ...commonParams,
      allRows: (selectedDates?.size ?? 0) > 0,
      dates: selectedDates ? Array.from(selectedDates) : [],
    };
  }
  return commonParams;
};

export const parseShiftTemplateData = (
  data: ShiftTemplateFormData,
  formatDate: (date: Date, format: string) => string,
): ShiftTemplateParams => ({
  name: data.shiftName,
  description: data.notes,
  timeSlots: data.timeSlots.reduce<[string, string][]>((acc, ts) => {
    if (ts.startTime && ts.endTime) {
      acc.push([
        formatDate(ts.startTime, 'HH:mm'),
        formatDate(ts.endTime, 'HH:mm'),
      ]);
    }
    return acc;
  }, []),
  color: data.color,
});

type PreFillData = {
  date: Date | null;
  collaborators: CollaboratorOption[] | undefined;
};

export const parsePreFillData = (
  selectedDates: Set<string>,
  selectedCells: ShiftAssignCellVC[],
  visibleUsers: ShiftCalendarUser[],
): PreFillData => {
  if (selectedDates?.size > 0 && selectedCells?.length === 0) {
    const firstDate = Array.from(selectedDates)[0];
    const parsedDate = parseISO(firstDate);
    if (isValid(parsedDate)) {
      return { date: parsedDate, collaborators: undefined };
    }
  } else if (selectedCells && selectedCells.length > 0) {
    const uniqueUserIds = new Set(selectedCells.map(cell => cell.userId));
    const collaboratorsFromCells =
      visibleUsers
        ?.filter(user => uniqueUserIds.has(user.userId))
        .map(user => ({
          value: user.userId,
          label: user.fullName,
          userInfo: {
            id: user.userId,
            fullName: user.fullName,
            profilePicture: user.profilePicture,
            employeeInternalId: user.employeeInternalId,
          },
        })) || [];
    const firstDate = selectedCells[0].date;
    const parsedDate = parseISO(firstDate);
    return {
      date: isValid(parsedDate) ? parsedDate : null,
      collaborators: collaboratorsFromCells,
    };
  }
  return { date: null, collaborators: undefined };
};
