// src/client/widgets/lib/useScheduleData.ts
// Composable for schedule grid data management and API operations

import { ref, computed, watch, onMounted, onUnmounted } from 'vue';
import { socket } from '@client/shared/api';
import { scheduleRepository } from '@client/shared/api/repositories';
import { usePermissionsStore } from '@client/entities/permissions';
import { AppPermission } from '@shared/contracts/permissions';
import dayjs from 'dayjs';
import utc from 'dayjs/plugin/utc.js';
import timezone from 'dayjs/plugin/timezone.js';

dayjs.extend(utc);
dayjs.extend(timezone);

// Month names for Russian locale
const monthNames = [
  'Январь', 'Февраль', 'Март', 'Апрель', 'Май', 'Июнь',
  'Июль', 'Август', 'Сентябрь', 'Октябрь', 'Ноябрь', 'Декабрь'
];

export interface User {
  id: string;
  username: string;
  firstName: string;
  lastName: string;
  fullName: string;
  email?: string | null;
  role: string | null;
  isMaidAlso: boolean;
}

export interface Shift {
  id: string;
  date: string;
  userId: string;
  roleAtShift: string;
  status: string;
  isApproved: boolean;
  createdBy: string;
  createdAt: string;
  updatedAt: string;
  archivedAt: string | null;
}

export interface EmploymentPeriod {
  id: string;
  userId: string;
  startDate: string;
  endDate: string | null;
  isMaid: boolean;
  createdAt: string;
}

export interface ShiftsGridData {
  managers: User[];
  maids: User[];
  shifts: Shift[];
  employmentPeriods: EmploymentPeriod[];
}

export interface UseScheduleDataProps {
  month: number;
  year: number;
  currentUserId?: string;
  canManageSchedule?: boolean;
  canWriteSchedule?: boolean;
}

export interface UseScheduleDataEmits {
  (e: 'update:pending', count: number): void;
  (e: 'update:can-process', value: boolean): void;
}

export function useScheduleData(props: UseScheduleDataProps, emit: UseScheduleDataEmits) {
  const permissionsStore = usePermissionsStore();

  const canManageSchedule = computed(() => {
    return permissionsStore.hasPermission(AppPermission.SCHEDULE_MANAGE);
  });

  const canEditCell = computed(() => {
    return permissionsStore.hasPermission(AppPermission.SCHEDULE_MANAGE)
      || permissionsStore.hasPermission(AppPermission.SCHEDULE_REQUEST_APPROVAL);
  });

  // State
  const gridData = ref<ShiftsGridData | null>(null);
  const loading = ref(false);
  const error = ref<string | null>(null);
  const pendingChanges = ref<Map<string, Shift>>(new Map());
  const sending = ref(false);
  const searchQuery = ref('');

  // Indexed shifts map for O(1) lookup: key = `${userId}_${date}_${roleAtShift}`
  const indexedShifts = ref<Map<string, Shift>>(new Map());

  // User status cache for employment period checks: key = `${userId}_${date}_${roleAtShift}`
  const userStatusCache = ref<Map<string, boolean>>(new Map());

  // Get current date for highlighting
  const today = dayjs().tz('Europe/Moscow');

  // Computed: days in month
  const daysInMonth = computed(() => {
    return dayjs()
      .year(props.year)
      .month(props.month - 1)
      .startOf('month')
      .daysInMonth();
  });

  // Computed: dates array
  const dates = computed(() => {
    const datesArray = [];
    for (let i = 1; i <= daysInMonth.value; i++) {
      datesArray.push(i);
    }
    return datesArray;
  });

  // Computed: filtered managers
  const filteredManagers = computed(() => {
    if (!gridData.value) return [];
    
    const staffOnly = gridData.value.managers.filter(m =>
      hasActiveEmploymentInMonth(m.id, 'Manager')
    );

    if (!searchQuery.value.trim()) return staffOnly;
    const query = searchQuery.value.toLowerCase().trim();
    return staffOnly.filter(m => m.fullName.toLowerCase().includes(query));
  });

  // Computed: filtered maids
  const filteredMaids = computed(() => {
    if (!gridData.value) return [];
    
    const maidsOnly = gridData.value.maids.filter(m =>
      hasActiveEmploymentInMonth(m.id, 'Maid')
    );

    if (!searchQuery.value.trim()) return maidsOnly;
    const query = searchQuery.value.toLowerCase().trim();
    return maidsOnly.filter(m => m.fullName.toLowerCase().includes(query));
  });

  // Helper: check if user has any active employment period in the selected month
  const hasActiveEmploymentInMonth = (userId: string, role: 'Manager' | 'Maid'): boolean => {
    if (!gridData.value) return false;
    
    // Check if user has any employment periods at all for this role
    const userAllPeriods = gridData.value.employmentPeriods.filter(ep => ep.userId === userId);
    const relevantPeriods = userAllPeriods.filter(ep => {
      const check = ep.isMaid !== undefined ? ep.isMaid : (ep as any).is_maid;
      const finalIsMaid = check === true || Number(check) === 1;
      return finalIsMaid === (role === 'Maid');
    });

    if (relevantPeriods.length === 0) {
      return false;
    }

    // Check if any period overlaps with the selected month
    const monthStart = dayjs().year(props.year).month(props.month - 1).startOf('month');
    const monthEnd = dayjs().year(props.year).month(props.month - 1).endOf('month');

    return relevantPeriods.some(period => {
      const start = dayjs(period.startDate).startOf('day');
      const end = period.endDate ? dayjs(period.endDate).endOf('day') : dayjs().add(100, 'year');
      
      // Check for overlap: (StartA <= EndB) and (EndA >= StartB)
      return (start.isSame(monthEnd) || start.isBefore(monthEnd)) && (end.isSame(monthStart) || end.isAfter(monthStart));
    });
  };

  // Computed: check if there is any unapproved content for the current month
  const hasAnyUnapprovedContent = computed(() => {
    // 1. Check backend shifts for the current month only
    const hasUnapprovedInDb = gridData.value?.shifts.some(s => {
      const shiftDate = dayjs.utc(s.date);
      return (
        shiftDate.month() + 1 === props.month &&
        shiftDate.year() === props.year &&
        !s.isApproved
      );
    }) ?? false;

    // 2. Check local pending changes for current month
    let hasPendingChangesForCurrentMonth = false;
    for (const [key] of pendingChanges.value.entries()) {
      const match = key.match(/(\d{4}-\d{2}-\d{2})/);
      if (match) {
        const shiftDate = dayjs.utc(match[1]);
        if (shiftDate.month() + 1 === props.month && shiftDate.year() === props.year) {
          hasPendingChangesForCurrentMonth = true;
          break;
        }
      }
    }

    return hasUnapprovedInDb || hasPendingChangesForCurrentMonth;
  });

  // Helper: get shift by key
  const getShift = (userId: string, date: number, roleAtShift: string): Shift | null => {
    const key = `${userId}_${date}_${roleAtShift}`;
    return indexedShifts.value.get(key) || null;
  };

  // Helper: get pending shift
  const getPendingShift = (userId: string, date: number, roleAtShift: string): Shift | null => {
    const dateStr = dayjs()
      .utc()
      .year(props.year)
      .month(props.month - 1)
      .date(date)
      .format('YYYY-MM-DD');
    const key = `${userId}-${dateStr}-${roleAtShift}`;
    return pendingChanges.value.get(key) || null;
  };

  // Helper: get user by ID
  const getUserById = (userId: string): User | null => {
    if (!gridData.value) return null;
    return gridData.value.managers.find(u => u.id === userId) || 
           gridData.value.maids.find(u => u.id === userId) || null;
  };

  // Helper: check if date is within employment periods
  const isWithinEmploymentPeriods = (userId: string, date: number, roleAtShift: string): boolean => {
    const key = `${userId}_${date}_${roleAtShift}`;
    const cached = userStatusCache.value.get(key);
    return cached ?? true;
  };

  // Helper: validate no collisions
  const validateNoCollisions = () => {
    const workStatuses = ['Work', 'C'];
    if (!gridData.value) return { isValid: true };

    for (const day of dates.value) {
      const dayStr = dayjs().year(props.year).month(props.month - 1).date(day).format('YYYY-MM-DD');
      
      for (const role of ['Manager', 'Maid']) {
        const unapprovedShifts = gridData.value.shifts.filter(s => 
          dayjs(s.date).format('YYYY-MM-DD') === dayStr && 
          s.roleAtShift === role && 
          !s.isApproved && 
          workStatuses.includes(s.status)
        );

        const userCounts = new Map<string, number>();
        for (const s of unapprovedShifts) {
          userCounts.set(s.userId, (userCounts.get(s.userId) || 0) + 1);
        }

        for (const [uId, count] of userCounts) {
          if (count > 1) {
            const user = [...gridData.value.managers, ...gridData.value.maids].find(u => u.id === uId);
            return { 
              isValid: false, 
              error: `Ошибка: у сотрудника ${user?.fullName} более одной смены на ${day}-е число в роли ${role}. Исправьте график.` 
            };
          }
        }
      }
    }
    return { isValid: true };
  };

  // Helper: update cell pending state with Smart Removal logic
  const updateCellPendingState = (
    changes: Map<string, Shift>,
    userId: string,
    date: number,
    roleAtShift: string,
    newStatus: string,
    dateStr: string
  ): void => {
    const key = `${userId}-${dateStr}-${roleAtShift}`;
    const originalShift = getShift(userId, date, roleAtShift);
    const isOriginalEmpty = !originalShift || originalShift.status === 'Blank';
    
    const newShiftData = {
      id: originalShift?.id || '',
      date: dateStr,
      userId,
      roleAtShift,
      status: newStatus,
      isApproved: false,
      createdBy: props.currentUserId || 'unknown',
      createdAt: originalShift?.createdAt || new Date().toISOString(),
      updatedAt: new Date().toISOString(),
      archivedAt: originalShift?.archivedAt || null,
    };

    if (newStatus === 'Blank' && isOriginalEmpty) {
      changes.delete(key);
    } else if (originalShift && !originalShift.isApproved && originalShift.status === newStatus) {
      changes.delete(key);
    } else {
      changes.set(key, newShiftData);
    }
  };

  // Helper: apply synchronization logic
  const applySyncLogic = (
    userId: string,
    date: number,
    roleAtShift: string,
    newStatus: string
  ): Map<string, Shift> => {
    const changes = new Map(pendingChanges.value);
    const dateStr = dayjs()
      .utc()
      .year(props.year)
      .month(props.month - 1)
      .date(date)
      .format('YYYY-MM-DD');

    updateCellPendingState(changes, userId, date, roleAtShift, newStatus, dateStr);

    const isInManagers = gridData.value?.managers.some(m => m.id === userId);
    const isInMaids = gridData.value?.maids.some(m => m.id === userId);
    const isDualRole = isInManagers && isInMaids;

    if (isDualRole) {
      if (roleAtShift === 'Manager') {
        const canWorkAsMaid = isWithinEmploymentPeriods(userId, date, 'Maid');
        
        if (canWorkAsMaid) {
          if (newStatus === 'Work') {
            updateCellPendingState(changes, userId, date, 'Maid', 'Work', dateStr);
          } else if (newStatus === 'Blank') {
            const currentMaidStatus = getDisplayStatus(userId, date, 'Maid');
            if (currentMaidStatus === 'Work') {
              updateCellPendingState(changes, userId, date, 'Maid', 'Blank', dateStr);
            }
          } else if (newStatus === 'Holiday' || newStatus === 'Unavailable') {
            updateCellPendingState(changes, userId, date, 'Maid', newStatus, dateStr);
          }
        }
      } else if (roleAtShift === 'Maid') {
        const canWorkAsManager = isWithinEmploymentPeriods(userId, date, 'Manager');
        
        if (canWorkAsManager) {
          if (newStatus === 'Holiday' || newStatus === 'Unavailable') {
            updateCellPendingState(changes, userId, date, 'Manager', newStatus, dateStr);
          } else if (newStatus === 'Blank') {
            const currentManagerStatus = getDisplayStatus(userId, date, 'Manager');
            if (currentManagerStatus === 'Holiday' || currentManagerStatus === 'Unavailable') {
              updateCellPendingState(changes, userId, date, 'Manager', 'Blank', dateStr);
            }
          }
        }
      }
    }

    return changes;
  };

  // Helper: normalize status
  const normalizeStatus = (status: string): string => {
    const statusMap: Record<string, string> = {
      'C': 'Work',
      'О': 'Holiday',
      'X': 'Unavailable',
      'Blank': 'Blank',
      'Work': 'Work',
      'Holiday': 'Holiday',
      'Unavailable': 'Unavailable'
    };
    return statusMap[status] || 'Blank';
  };

  // Helper: get display status
  const getDisplayStatus = (userId: string, date: number, roleAtShift: string): string => {
    const pendingShift = getPendingShift(userId, date, roleAtShift);
    if (pendingShift) {
      return normalizeStatus(pendingShift.status);
    }

    const dateStr = dayjs()
      .utc()
      .year(props.year)
      .month(props.month - 1)
      .date(date)
      .format('YYYY-MM-DD');
    
    const draftShift = gridData.value?.shifts.find(s =>
      s.userId === userId &&
      dayjs.utc(s.date).format('YYYY-MM-DD') === dateStr &&
      s.roleAtShift === roleAtShift &&
      !s.isApproved
    );

    if (draftShift) {
      return normalizeStatus(draftShift.status);
    }

    const shift = getShift(userId, date, roleAtShift);
    return normalizeStatus(shift?.status || 'Blank');
  };

  // Helper: update pending count
  const updatePendingCount = () => {
    let count = 0;
    for (const [key, shift] of pendingChanges.value.entries()) {
      const match = key.match(/(\d{4}-\d{2}-\d{2})/);
      if (match) {
        const shiftDate = dayjs.utc(match[1]);
        if (shiftDate.month() + 1 === props.month && shiftDate.year() === props.year) {
          count++;
        }
      }
    }
    emit('update:pending', count);
  };

  // API Method: fetch grid data
  const fetchGridData = async () => {
    loading.value = true;
    error.value = null;
    try {
      const month = props.month;
      const year = props.year;

      if (isNaN(month) || isNaN(year)) {
        error.value = 'Некорректные параметры даты';
        loading.value = false;
        return;
      }

      const response = await scheduleRepository.getShiftsGrid(month, year);
      gridData.value = response;

      // Build indexed shifts map
      const newIndexedShifts = new Map<string, Shift>();
      if (gridData.value?.shifts) {
        for (const shift of gridData.value.shifts) {
          const shiftDate = dayjs(shift.date);
          const day = shiftDate.date();
          const key = `${shift.userId}_${day}_${shift.roleAtShift}`;
          newIndexedShifts.set(key, shift);
        }
      }
      indexedShifts.value = newIndexedShifts;

      // Build user status cache
      const newUserStatusCache = new Map<string, boolean>();
      if (gridData.value?.employmentPeriods) {
        const allUsers = [...(gridData.value.managers || []), ...(gridData.value.maids || [])];
        const uniqueUserIds = [...new Set(allUsers.map(u => u.id))];
        const roles: ('Manager' | 'Maid')[] = ['Manager', 'Maid'];

        for (const userId of uniqueUserIds) {
          for (const role of roles) {
            const userAllPeriods = gridData.value.employmentPeriods.filter(ep => ep.userId === userId);
            const relevantPeriods = userAllPeriods.filter(ep => {
              const check = ep.isMaid !== undefined ? ep.isMaid : (ep as any).is_maid;
              const finalIsMaid = check === true || Number(check) === 1;
              return finalIsMaid === (role === 'Maid');
            });

            for (let day = 1; day <= daysInMonth.value; day++) {
              const cellDate = dayjs().year(props.year).month(props.month - 1).date(day).startOf('day');
              const key = `${userId}_${day}_${role}`;

              if (relevantPeriods.length === 0) {
                newUserStatusCache.set(key, false);
              } else {
                const isWithin = relevantPeriods.some(period => {
                  const start = dayjs(period.startDate).startOf('day');
                  const end = period.endDate ? dayjs(period.endDate).endOf('day') : dayjs().add(100, 'year');
                  return (cellDate.isSame(start) || cellDate.isAfter(start)) && (cellDate.isSame(end) || cellDate.isBefore(end));
                });
                newUserStatusCache.set(key, isWithin);
              }
            }
          }
        }
      }
      userStatusCache.value = newUserStatusCache;
    } catch (err: any) {
      error.value = err.message || 'Failed to fetch schedule';
    } finally {
      loading.value = false;
    }
  };

  // API Method: save changes
  const saveChanges = async (): Promise<boolean> => {
    if (pendingChanges.value.size === 0) {
      return true;
    }

    const validation = validateNoCollisions();
    if (!validation.isValid) {
      throw new Error(validation.error || 'Ошибка валидации графика');
    }

    sending.value = true;
    try {
      const shiftsToSave: any[] = [];
      for (const [key, shift] of pendingChanges.value.entries()) {
        const match = key.match(/(\d{4}-\d{2}-\d{2})/);
        if (match) {
          const shiftDate = dayjs.utc(match[1]);
          if (shiftDate.month() + 1 === props.month && shiftDate.year() === props.year) {
            shiftsToSave.push({
              userId: shift.userId,
              date: new Date(shift.date + 'T00:00:00Z'),
              roleAtShift: shift.roleAtShift,
              status: shift.status,
            });
          }
        }
      }

      await scheduleRepository.bulkUpdateShifts({ shifts: shiftsToSave });

      const keysToClear: string[] = [];
      for (const [key, shift] of pendingChanges.value.entries()) {
        const match = key.match(/(\d{4}-\d{2}-\d{2})/);
        if (match) {
          const shiftDate = dayjs.utc(match[1]);
          if (shiftDate.month() + 1 === props.month && shiftDate.year() === props.year) {
            keysToClear.push(key);
          }
        }
      }
      keysToClear.forEach(key => pendingChanges.value.delete(key));
      
      updatePendingCount();
      await fetchGridData();
      
      return true;
    } catch (err: any) {
      const serverErrorMessage = err.response?.data?.error?.message || err.message || 'Ошибка при сохранении';
      throw new Error(serverErrorMessage);
    } finally {
      sending.value = false;
    }
  };

  // API Method: approve all shifts internal
  const approveAllShiftsInternal = async () => {
    sending.value = true;
    try {
      const validation = validateNoCollisions();
      if (!validation.isValid) {
        throw new Error(validation.error || 'Ошибка валидации графика');
      }

      if (pendingChanges.value.size > 0) {
        const isSaved = await saveChanges();
        if (!isSaved) {
          return;
        }
      }

      await scheduleRepository.approveShifts(props.month, props.year);
      await fetchGridData();
    } catch (err: any) {
      const errorMessage = err.response?.data?.error?.message || err.message || 'Ошибка при согласовании смен';
      throw new Error(errorMessage);
    } finally {
      sending.value = false;
    }
  };

  // API Method: reject all shifts internal
  const rejectAllShiftsInternal = async () => {
    sending.value = true;
    try {
      if (pendingChanges.value.size > 0) {
        const keysToDelete: string[] = [];
        for (const [key, shift] of pendingChanges.value.entries()) {
          const match = key.match(/(\d{4}-\d{2}-\d{2})/);
          if (match) {
            const shiftDate = dayjs.utc(match[1]);
            if (shiftDate.month() + 1 === props.month && shiftDate.year() === props.year) {
              keysToDelete.push(key);
            }
          }
        }
        for (const key of keysToDelete) {
          pendingChanges.value.delete(key);
        }
        updatePendingCount();
      } else {
        await scheduleRepository.rejectShifts(props.month, props.year);
        await fetchGridData();
      }
    } catch (err: any) {
      const errorMessage = err.response?.data?.error?.message || err.message || 'Ошибка при отклонении смен';
      throw new Error(errorMessage);
    } finally {
      sending.value = false;
    }
  };

  // Socket event handler
  const handleShiftUpdate = (data: { month: number; year: number }) => {
    if (data.month === props.month && data.year === props.year) {
      fetchGridData();
    }
  };

  // Lifecycle hooks
  onMounted(() => {
    fetchGridData();
    socket.on('shift:update', handleShiftUpdate);
  });

  onUnmounted(() => {
    socket.off('shift:update', handleShiftUpdate);
  });

  // Watchers
  watch([() => props.month, () => props.year], () => {
    fetchGridData();
    updatePendingCount();
  });

  watch(hasAnyUnapprovedContent, (newValue) => {
    emit('update:can-process', newValue);
  }, { immediate: true });

  return {
    canManageSchedule,
    canEditCell,

    // State
    gridData,
    loading,
    error,
    pendingChanges,
    sending,
    searchQuery,
    indexedShifts,
    userStatusCache,
    today,
    
    // Computed
    daysInMonth,
    dates,
    filteredManagers,
    filteredMaids,
    hasAnyUnapprovedContent,
    
    // Methods
    fetchGridData,
    saveChanges,
    approveAllShiftsInternal,
    rejectAllShiftsInternal,
    applySyncLogic,
    getShift,
    getPendingShift,
    getDisplayStatus,
    getUserById,
    isWithinEmploymentPeriods,
    validateNoCollisions,
    updatePendingCount,
    normalizeStatus,
    
    // Constants
    monthNames,
  };
}
